This commit is contained in:
Timo Bejan 2020-11-11 16:57:45 +02:00
parent b128588778
commit 3432d2e6e3
9 changed files with 231 additions and 222 deletions

View File

@ -18,17 +18,26 @@ export class TypeAnnotationIconComponent implements OnChanges {
ngOnChanges(): void {
if (this.annotation) {
if (this.annotation.isSuggestion) {
this.color = this.annotation.modifyDictionary
? this._appStateService.getDictionaryColor('suggestion-dictionary')
: this._appStateService.getDictionaryColor('suggestion');
if (
this.annotation.isSuggestion ||
this.annotation.isDeclinedSuggestion ||
this.annotation.isModifyDictionary ||
this.annotation.isIgnored ||
this.annotation.isReadyForAnalysis
) {
this.color = this._appStateService.getDictionaryColor(this.annotation.superType);
} else {
this.color = this.annotation.isIgnored
? this._appStateService.getDictionaryColor('ignore')
: this._appStateService.getDictionaryColor(this.annotation.dictionary);
this.color = this._appStateService.getDictionaryColor(this.annotation.dictionary);
}
this.type = this.annotation.isSuggestion ? 'rhombus' : this.annotation.isRedactedOrIgnored ? 'square' : 'circle';
this.label = this.annotation.isSuggestion ? 'S' : this.annotation.isIgnored ? 'I' : this.annotation.dictionary[0].toUpperCase();
this.type = this.annotation.isSuggestion || this.annotation.isDeclinedSuggestion ? 'rhombus' : this.annotation.isHint ? 'circle' : 'square';
this.label =
this.annotation.isSuggestion || this.annotation.isDeclinedSuggestion
? 'S'
: this.annotation.isIgnored
? 'I'
: this.annotation.isReadyForAnalysis
? 'A'
: this.annotation.dictionary[0].toUpperCase();
}
}
}

View File

@ -1,40 +1,38 @@
<div [class.visible]="menuOpen" *ngIf="canPerformAnnotationActions" class="annotation-actions">
<!-- Only owner can accept or reject suggestions-->
<ng-container *ngIf="permissionsService.isManagerAndOwner() && annotation.isSuggestion">
<button (click)="acceptSuggestion($event, annotation, annotation.modifyDictionary)" mat-icon-button>
<mat-icon svgIcon="red:check-alt"></mat-icon>
</button>
<button (click)="rejectSuggestion($event, annotation)" *ngIf="!canUndoAnnotation()" mat-icon-button>
<mat-icon svgIcon="red:close"></mat-icon>
</button>
</ng-container>
<button (click)="acceptSuggestion($event, annotation)" mat-icon-button *ngIf="canAcceptSuggestion">
<mat-icon svgIcon="red:check-alt"></mat-icon>
</button>
<!-- If we can undo the annotation - allow it-->
<button (click)="undoDirectAction($event, annotation)" *ngIf="canUndoAnnotation()" mat-icon-button>
<button (click)="undoDirectAction($event, annotation)" *ngIf="canUndoAnnotation" mat-icon-button>
<mat-icon svgIcon="red:undo"></mat-icon>
</button>
<!-- Everyone can suggest to remove annotations, manager will remove directly while user will make a suggestion-->
<button (click)="rejectSuggestion($event, annotation)" mat-icon-button *ngIf="canRejectSuggestion">
<mat-icon svgIcon="red:close"></mat-icon>
</button>
<!-- For Suggesting Removal, in case of hints, the user can suggest removal from dictionary -->
<ng-container *ngIf="!annotation.isSuggestion">
<button (click)="suggestRemoveAnnotation($event, annotation, true)" *ngIf="showSimpleSuggestRemove()" mat-icon-button>
<mat-icon svgIcon="red:close"></mat-icon>
</button>
<button (click)="suggestRemoveAnnotation($event, annotation, true)" mat-icon-button *ngIf="canDirectlySuggestToRemoveAnnotation">
<mat-icon svgIcon="red:close"></mat-icon>
</button>
<!-- For Suggesting Removal, in case of redactions, the user can suggest only-here or everywhere-->
<button (click)="openMenu($event)" *ngIf="showMenuSuggestRemove()" [class.active]="menuOpen" [matMenuTriggerFor]="menu" class="confirm" mat-icon-button>
<mat-icon svgIcon="red:close"></mat-icon>
</button>
<mat-menu #menu="matMenu" (closed)="onMenuClosed()" xPosition="before">
<div (click)="suggestRemoveAnnotation($event, annotation, true)" mat-menu-item>
<redaction-annotation-icon [type]="'rhombus'" [label]="'S'" [color]="dictionaryColor"></redaction-annotation-icon>
<div [translate]="'file-preview.tabs.annotations.remove-annotation.remove-from-dict'"></div>
</div>
<div (click)="suggestRemoveAnnotation($event, annotation, false)" mat-menu-item>
<redaction-annotation-icon [type]="'rhombus'" [label]="'S'" [color]="suggestionColor"></redaction-annotation-icon>
<div translate="file-preview.tabs.annotations.remove-annotation.only-here"></div>
</div>
</mat-menu>
</ng-container>
<button
(click)="openMenu($event)"
[class.active]="menuOpen"
[matMenuTriggerFor]="menu"
class="confirm"
mat-icon-button
*ngIf="requiresSuggestionRemoveMenu"
>
<mat-icon svgIcon="red:close"></mat-icon>
</button>
<mat-menu #menu="matMenu" (closed)="onMenuClosed()" xPosition="before">
<div (click)="suggestRemoveAnnotation($event, annotation, true)" mat-menu-item>
<redaction-annotation-icon [type]="'rhombus'" [label]="'S'" [color]="dictionaryColor"></redaction-annotation-icon>
<div [translate]="'file-preview.tabs.annotations.remove-annotation.remove-from-dict'"></div>
</div>
<div (click)="suggestRemoveAnnotation($event, annotation, false)" mat-menu-item>
<redaction-annotation-icon [type]="'rhombus'" [label]="'S'" [color]="suggestionColor"></redaction-annotation-icon>
<div translate="file-preview.tabs.annotations.remove-annotation.only-here"></div>
</div>
</mat-menu>
</div>

View File

@ -31,12 +31,33 @@ export class AnnotationActionsComponent implements OnInit {
}
get canAcceptSuggestion() {
return this.permissionsService.isManagerAndOwner() && (this.annotation.superType === 'suggestion' || this.annotation.superType === 'suggestion-remove');
return this.permissionsService.isManagerAndOwner() && this.annotation.isSuggestion;
}
acceptSuggestion($event: MouseEvent, annotation: AnnotationWrapper, addToDictionary: boolean) {
get canRejectSuggestion() {
// i can reject whatever i may not undo
return this.canAcceptSuggestion && !this.canUndoAnnotation;
}
get canDirectlySuggestToRemoveAnnotation() {
return this.annotation.isHint;
}
get requiresSuggestionRemoveMenu() {
return this.annotation.isRedacted;
}
get canUndoAnnotation() {
// suggestions of current user can be undone
const isSuggestionOfCurrentUser = this.annotation.isSuggestion && this.annotation.userId === this.permissionsService.currentUserId;
// or any performed manual actions and you are the manager, provided that it is not a suggestion
const isActionOfManger = this.permissionsService.isManagerAndOwner() && this.annotation.userId === this.permissionsService.currentUserId;
return isSuggestionOfCurrentUser || isActionOfManger;
}
acceptSuggestion($event: MouseEvent, annotation: AnnotationWrapper) {
$event.stopPropagation();
this._processObsAndEmit(this._manualAnnotationService.approveRequest(annotation.id, addToDictionary));
this._processObsAndEmit(this._manualAnnotationService.approveRequest(annotation.id, annotation.isModifyDictionary));
}
rejectSuggestion($event: MouseEvent, annotation: AnnotationWrapper) {
@ -79,33 +100,6 @@ export class AnnotationActionsComponent implements OnInit {
}
get dictionaryColor() {
return this.appStateService.getDictionaryColor('suggestion-dictionary');
}
showSimpleSuggestRemove() {
return this._canRemove() && !this._showComplexMenu();
}
showMenuSuggestRemove() {
return this._canRemove() && this._showComplexMenu();
}
private _showComplexMenu() {
// we can show the complex dialog only for redactions that are not manual, but this is already checked via canUndo
return this.annotation.superType === 'redaction' && this.annotation.dictionary !== 'manual';
}
canUndoAnnotation() {
// suggestions of current user can be undone
const isSuggestionOfCurrentUser = this.annotation.isSuggestion && this.annotation.userId === this.permissionsService.currentUserId;
// or any performed manual actions and you are the manager, provided that it is not a suggestion
const isActionOfManger = this.permissionsService.isManagerAndOwner() && this.annotation.userId === this.permissionsService.currentUserId;
return isSuggestionOfCurrentUser || isActionOfManger;
}
private _canRemove() {
const canUndo = this.canUndoAnnotation();
// you can do a simple remove for anything you cannot undo, that is not ignored
return !canUndo && !this.annotation.isIgnored;
return this.appStateService.getDictionaryColor('suggestion-add-dictionary');
}
}

View File

@ -374,7 +374,7 @@ export class FilePreviewScreenComponent implements OnInit {
this._rebuildFilters();
this._annotationDrawService.drawAnnotations(
this.instance,
this.annotations.filter((item) => (annotationIdToDraw ? item.id === annotationIdToDraw && item.shouldDraw : item.shouldDraw))
this.annotations.filter((item) => (annotationIdToDraw ? item.id === annotationIdToDraw : true))
);
});
}

View File

@ -11,7 +11,18 @@ export const SuperTypeSorter = {
};
export class AnnotationWrapper {
superType: 'suggestion' | 'redaction' | 'hint' | 'ignore' | 'suggestion-remove';
superType:
| 'add-dictionary'
| 'remove-dictionary'
| 'suggestion-add-dictionary'
| 'suggestion-remove-dictionary'
| 'suggestion-add'
| 'suggestion-remove'
| 'ignore'
| 'redaction'
| 'manual'
| 'hint'
| 'declined-suggestion';
dictionary: string;
color: string;
comments: Comment[] = [];
@ -20,11 +31,9 @@ export class AnnotationWrapper {
content: string;
manual: boolean;
userId: string;
modifyDictionary: boolean;
typeLabel: string;
pageNumber: number;
hint: boolean;
shouldDraw: boolean;
redaction: boolean;
positions: Rectangle[];
@ -36,8 +45,36 @@ export class AnnotationWrapper {
return this.superType === 'ignore' || this.superType === 'redaction';
}
get isDeclinedSuggestion() {
return this.superType === 'declined-suggestion';
}
get isReadyForAnalysis() {
return this.superType === 'add-dictionary' || this.superType === 'remove-dictionary';
}
get isHint() {
return this.superType === 'hint';
}
get isRedacted() {
return this.superType === 'redaction';
}
get isSuggestion() {
return this.superType === 'suggestion' || this.superType === 'suggestion-remove';
return this.isSuggestionAdd || this.isSuggestionRemove;
}
get isSuggestionAdd() {
return this.superType === 'suggestion-add' || this.superType === 'suggestion-add-dictionary';
}
get isSuggestionRemove() {
return this.superType === 'suggestion-remove' || this.superType === 'suggestion-remove-dictionary';
}
get isModifyDictionary() {
return this.superType === 'suggestion-add-dictionary' || this.superType === 'suggestion-remove-dictionary';
}
static fromData(
@ -52,7 +89,6 @@ export class AnnotationWrapper {
const annotationWrapper = new AnnotationWrapper();
annotationWrapper.comments = comments ? comments : [];
annotationWrapper.shouldDraw = true;
annotationWrapper.userId = manualRedactionEntry?.user || idRemoval?.user;
if (redactionLogEntry) {
@ -64,27 +100,6 @@ export class AnnotationWrapper {
annotationWrapper.pageNumber = redactionLogEntry.positions[0]?.page;
annotationWrapper.positions = redactionLogEntry.positions;
annotationWrapper.content = AnnotationWrapper.createContent(redactionLogEntry);
// either marked as manual or idRemove or manualRedactionEntry exists
annotationWrapper.manual = redactionLogEntry.manual;
annotationWrapper.modifyDictionary = !!manualRedactionEntry?.addToDictionary || !!idRemoval?.removeFromDictionary;
if (redactionLogEntry.manual) {
// marked as add but the entryToAdd has been undone
if (!manualRedactionEntry && redactionLogEntry.manualRedactionType === 'ADD') {
// do not draw if it is no longer in the manual redactions
annotationWrapper.shouldDraw = false;
}
if (manualRedactionEntry) {
annotationWrapper.shouldDraw = manualRedactionEntry.status === 'REQUESTED' || manualRedactionEntry.status === 'APPROVED';
}
// marked as remove but the idRemoval has been undone
if (redactionLogEntry.manualRedactionType === 'REMOVE' && !idRemoval) {
const dictionary = dictionaryData[redactionLogEntry.type];
annotationWrapper.redaction = !dictionary.hint;
annotationWrapper.hint = dictionary.hint;
}
}
} else {
// no redaction log entry - not yet processed
const dictionary = dictionaryData[manualRedactionEntry.type];
@ -97,15 +112,12 @@ export class AnnotationWrapper {
annotationWrapper.positions = manualRedactionEntry.positions;
annotationWrapper.content = manualRedactionEntry.addToDictionary ? null : AnnotationWrapper.createContent(manualRedactionEntry);
annotationWrapper.manual = true;
annotationWrapper.shouldDraw = manualRedactionEntry.status === 'REQUESTED' || manualRedactionEntry.status === 'APPROVED';
annotationWrapper.comments = comments[manualRedactionEntry.id] ? comments[manualRedactionEntry.id] : [];
annotationWrapper.userId = manualRedactionEntry.user;
annotationWrapper.modifyDictionary = manualRedactionEntry.addToDictionary;
}
AnnotationWrapper._setSuperType(annotationWrapper, redactionLogEntry, manualRedactionEntry, idRemoval);
AnnotationWrapper._setTypeLabel(annotationWrapper);
annotationWrapper.typeLabel = annotationWrapper.superType;
return annotationWrapper;
}
@ -115,63 +127,42 @@ export class AnnotationWrapper {
manualRedactionEntry?: ManualRedactionEntry,
idRemoval?: IdRemoval
) {
if (idRemoval && manualRedactionEntry) {
const handleManualRedactionFirst = new Date(idRemoval.requestDate).getTime() > new Date(manualRedactionEntry.requestDate).getTime();
if (handleManualRedactionFirst) {
AnnotationWrapper._handleManualRedaction(annotationWrapper, manualRedactionEntry);
AnnotationWrapper._handleIdRemoval(annotationWrapper, idRemoval);
if (idRemoval) {
if (idRemoval.status === 'DECLINED') {
annotationWrapper.superType = 'declined-suggestion';
} else {
AnnotationWrapper._handleIdRemoval(annotationWrapper, idRemoval);
AnnotationWrapper._handleManualRedaction(annotationWrapper, manualRedactionEntry);
if (idRemoval.removeFromDictionary) {
annotationWrapper.superType = idRemoval.status === 'REQUESTED' ? 'suggestion-remove-dictionary' : 'remove-dictionary';
} else {
// TODO check me
annotationWrapper.superType = idRemoval.status === 'REQUESTED' ? 'suggestion-remove' : 'ignore';
}
}
} else {
AnnotationWrapper._handleIdRemoval(annotationWrapper, idRemoval);
AnnotationWrapper._handleManualRedaction(annotationWrapper, manualRedactionEntry);
}
if (manualRedactionEntry) {
if (manualRedactionEntry.status === 'DECLINED') {
annotationWrapper.superType = 'declined-suggestion';
} else {
if (manualRedactionEntry.addToDictionary) {
annotationWrapper.superType = manualRedactionEntry.status === 'REQUESTED' ? 'suggestion-add-dictionary' : 'add-dictionary';
} else {
// TODO check me
annotationWrapper.superType = manualRedactionEntry.status === 'REQUESTED' ? 'suggestion-add' : 'manual';
}
}
}
if (!annotationWrapper.superType) {
annotationWrapper.superType = annotationWrapper.redaction ? 'redaction' : annotationWrapper.hint ? 'hint' : 'ignore';
}
}
private static _handleIdRemoval(annotationWrapper: AnnotationWrapper, idRemoval?: IdRemoval) {
if (idRemoval) {
if (idRemoval.status === 'REQUESTED') {
annotationWrapper.superType = 'suggestion-remove';
}
if (idRemoval.status === 'APPROVED') {
annotationWrapper.superType = 'ignore';
}
}
}
private static _handleManualRedaction(annotationWrapper: AnnotationWrapper, manualRedactionEntry?: ManualRedactionEntry) {
if (manualRedactionEntry) {
if (manualRedactionEntry.status === 'REQUESTED') {
annotationWrapper.superType = 'suggestion';
}
if (manualRedactionEntry.status === 'APPROVED') {
annotationWrapper.superType = annotationWrapper.redaction ? 'redaction' : annotationWrapper.hint ? 'hint' : 'ignore';
}
if (annotationWrapper.superType === 'manual') {
console.log(annotationWrapper.superType, manualRedactionEntry, idRemoval);
}
}
private static _setTypeLabel(annotationWrapper: AnnotationWrapper) {
let label = 'annotation-type.' + annotationWrapper.superType;
switch (annotationWrapper.superType) {
case 'suggestion':
case 'suggestion-remove':
if (annotationWrapper.hint) {
label += '-hint';
}
if (annotationWrapper.redaction) {
label += '-redaction';
if (annotationWrapper.modifyDictionary) {
label += '-dictionary';
}
}
break;
}
annotationWrapper.typeLabel = label;
annotationWrapper.typeLabel = annotationWrapper.superType;
}
constructor() {}

View File

@ -52,31 +52,27 @@ export class FileDataModel {
const pairs: AnnotationPair[] = [];
this.redactionLog.redactionLogEntry.forEach((rdl) => {
if (rdl.status !== 'DECLINED') {
pairs.push({
redactionLogEntry: rdl,
// only not declined
manualRedactionEntry: this.manualRedactions.entriesToAdd.find((eta) => eta.id === rdl.id && eta.status !== 'DECLINED'),
// only not declined
idRemoval: this.manualRedactions.idsToRemove.find((idr) => idr.id === rdl.id && idr.status !== 'DECLINED'),
comments: this.manualRedactions.comments[rdl.id]
});
}
pairs.push({
redactionLogEntry: rdl,
// only not declined
manualRedactionEntry: this.manualRedactions.entriesToAdd.find((eta) => eta.id === rdl.id),
// only not declined
idRemoval: this.manualRedactions.idsToRemove.find((idr) => idr.id === rdl.id),
comments: this.manualRedactions.comments[rdl.id]
});
});
this.manualRedactions.entriesToAdd.forEach((eta) => {
// only not declined
if (eta.status !== 'DECLINED') {
const redactionLogEntry = this.redactionLog.redactionLogEntry.find((rdl) => rdl.id === eta.id);
if (!redactionLogEntry) {
pairs.push({
redactionLogEntry: null,
manualRedactionEntry: eta,
// only not declined
idRemoval: this.manualRedactions.idsToRemove.find((idr) => idr.id === eta.id && idr.status !== 'DECLINED'),
comments: this.manualRedactions.comments[eta.id]
});
}
const redactionLogEntry = this.redactionLog.redactionLogEntry.find((rdl) => rdl.id === eta.id);
if (!redactionLogEntry) {
pairs.push({
redactionLogEntry: null,
manualRedactionEntry: eta,
// only not declined
idRemoval: this.manualRedactions.idsToRemove.find((idr) => idr.id === eta.id),
comments: this.manualRedactions.comments[eta.id]
});
}
});

View File

@ -34,12 +34,6 @@ export class AnnotationDrawService {
private _getColor(activeViewer: WebViewerInstance, annotationWrapper: AnnotationWrapper) {
let color;
switch (annotationWrapper.superType) {
case 'suggestion':
case 'suggestion-remove':
color = annotationWrapper.modifyDictionary
? this._appStateService.getDictionaryColor('suggestion-dictionary')
: this._appStateService.getDictionaryColor(annotationWrapper.superType);
break;
case 'hint':
case 'redaction':
color = this._appStateService.getDictionaryColor(annotationWrapper.dictionary);
@ -48,7 +42,7 @@ export class AnnotationDrawService {
color = this._appStateService.getDictionaryColor('ignore');
break;
default:
color = this._appStateService.getDictionaryColor('default');
color = this._appStateService.getDictionaryColor(annotationWrapper.superType);
break;
}
const rgbColor = hexToRgb(color);

View File

@ -57,39 +57,37 @@ export class AnnotationProcessingService {
flatFilters.push(...filter.filters);
});
for (const annotation of annotations) {
if (annotation.shouldDraw) {
const pageNumber = annotation.pageNumber;
const type = annotation.superType;
const pageNumber = annotation.pageNumber;
const type = annotation.superType;
if (hasActiveFilters) {
let found = false;
for (const filter of flatFilters) {
if (
filter.checked &&
((filter.key === annotation.dictionary && (annotation.superType === 'hint' || annotation.superType === 'redaction')) ||
filter.key === annotation.superType)
) {
found = true;
break;
}
}
if (!found) {
continue;
if (hasActiveFilters) {
let found = false;
for (const filter of flatFilters) {
if (
filter.checked &&
((filter.key === annotation.dictionary && (annotation.superType === 'hint' || annotation.superType === 'redaction')) ||
filter.key === annotation.superType)
) {
found = true;
break;
}
}
if (!obj[pageNumber]) {
obj[pageNumber] = {
annotations: [],
hint: 0,
redaction: 0,
request: 0,
ignore: 0
};
if (!found) {
continue;
}
obj[pageNumber].annotations.push(annotation);
obj[pageNumber][type]++;
}
if (!obj[pageNumber]) {
obj[pageNumber] = {
annotations: [],
hint: 0,
redaction: 0,
request: 0,
ignore: 0
};
}
obj[pageNumber].annotations.push(annotation);
obj[pageNumber][type]++;
}
Object.keys(obj).map((page) => {

View File

@ -365,26 +365,64 @@ export class AppStateService {
);
const colorsObs = this._dictionaryControllerService.getColors().pipe(
tap((colors) => {
this._dictionaryData['suggestion'] = {
hexColor: colors.requestAdd,
type: 'suggestion',
// declined
this._dictionaryData['declined-suggestion'] = {
hexColor: colors.notRedacted,
type: 'declined-suggestion',
virtual: true
};
// manual
this._dictionaryData['manual'] = {
hexColor: colors.defaultColor,
type: 'manual',
virtual: true
};
this._dictionaryData['suggestion-dictionary'] = {
hexColor: '#5B97DB',
type: 'suggestion-dictionary',
// dictionary actions
this._dictionaryData['add-dictionary'] = {
hexColor: '#dd4d50',
type: 'add-dictionary',
virtual: true
};
this._dictionaryData['remove-dictionary'] = {
hexColor: '#dd4d50',
type: 'remove-dictionary',
virtual: true
};
// generic suggestions
this._dictionaryData['suggestion'] = {
hexColor: colors.requestAdd,
type: 'suggestion',
virtual: true
};
// add suggestions
this._dictionaryData['suggestion-add'] = {
hexColor: colors.requestAdd,
type: 'suggestion-add',
virtual: true
};
this._dictionaryData['suggestion-add-dictionary'] = {
hexColor: '#5B97DB',
type: 'suggestion-add',
virtual: true
};
// suggestion remove
this._dictionaryData['suggestion-remove'] = {
hexColor: colors.requestRemove,
type: 'suggestion-remove',
type: 'suggestion-add',
virtual: true
};
this._dictionaryData['suggestion-remove-dictionary'] = {
hexColor: '#5B97DB',
type: 'suggestion-add',
virtual: true
};
this._dictionaryData['manual'] = {
hexColor: colors.defaultColor,
type: 'manual',
virtual: true
};
this._dictionaryData['ignore'] = {
hexColor: colors.notRedacted,
type: 'ignore',
@ -429,15 +467,6 @@ export class AppStateService {
return data ? data : this._dictionaryData['default'];
}
getDictionaryTypeValueForAnnotation(annotation: AnnotationWrapper) {
if (annotation.superType === 'suggestion' || annotation.superType === 'ignore' || annotation.superType === 'suggestion-remove') {
return this._dictionaryData[annotation.superType];
}
if (annotation.superType === 'redaction' || annotation.superType === 'hint') {
return this._dictionaryData[annotation.dictionary];
}
}
async updateDictionaryVersion() {
const result = await this._versionsControllerService.getVersions().toPromise();
this._appState.dictionaryVersion = result.dictionaryVersion;