diff --git a/apps/red-ui/src/app/components/type-annotation-icon/type-annotation-icon.component.ts b/apps/red-ui/src/app/components/type-annotation-icon/type-annotation-icon.component.ts index 7479b0cca..7e11ff1fe 100644 --- a/apps/red-ui/src/app/components/type-annotation-icon/type-annotation-icon.component.ts +++ b/apps/red-ui/src/app/components/type-annotation-icon/type-annotation-icon.component.ts @@ -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(); } } } diff --git a/apps/red-ui/src/app/screens/file/annotation-actions/annotation-actions.component.html b/apps/red-ui/src/app/screens/file/annotation-actions/annotation-actions.component.html index 58f6ae7ac..84dbd9f0f 100644 --- a/apps/red-ui/src/app/screens/file/annotation-actions/annotation-actions.component.html +++ b/apps/red-ui/src/app/screens/file/annotation-actions/annotation-actions.component.html @@ -1,40 +1,38 @@
- - - - - + - - - + - - - + - - - -
- -
-
-
- -
-
-
-
+ + +
+ +
+
+
+ +
+
+
diff --git a/apps/red-ui/src/app/screens/file/annotation-actions/annotation-actions.component.ts b/apps/red-ui/src/app/screens/file/annotation-actions/annotation-actions.component.ts index 929dd4b08..88cb55b1f 100644 --- a/apps/red-ui/src/app/screens/file/annotation-actions/annotation-actions.component.ts +++ b/apps/red-ui/src/app/screens/file/annotation-actions/annotation-actions.component.ts @@ -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'); } } diff --git a/apps/red-ui/src/app/screens/file/file-preview-screen/file-preview-screen.component.ts b/apps/red-ui/src/app/screens/file/file-preview-screen/file-preview-screen.component.ts index 889ac97f3..22847d60a 100644 --- a/apps/red-ui/src/app/screens/file/file-preview-screen/file-preview-screen.component.ts +++ b/apps/red-ui/src/app/screens/file/file-preview-screen/file-preview-screen.component.ts @@ -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)) ); }); } diff --git a/apps/red-ui/src/app/screens/file/model/annotation.wrapper.ts b/apps/red-ui/src/app/screens/file/model/annotation.wrapper.ts index 3249d77bb..89fed774b 100644 --- a/apps/red-ui/src/app/screens/file/model/annotation.wrapper.ts +++ b/apps/red-ui/src/app/screens/file/model/annotation.wrapper.ts @@ -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() {} diff --git a/apps/red-ui/src/app/screens/file/model/file-data.model.ts b/apps/red-ui/src/app/screens/file/model/file-data.model.ts index 5436a67e0..ebcfff5cb 100644 --- a/apps/red-ui/src/app/screens/file/model/file-data.model.ts +++ b/apps/red-ui/src/app/screens/file/model/file-data.model.ts @@ -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] + }); } }); diff --git a/apps/red-ui/src/app/screens/file/service/annotation-draw.service.ts b/apps/red-ui/src/app/screens/file/service/annotation-draw.service.ts index 9362e3db3..c64112b71 100644 --- a/apps/red-ui/src/app/screens/file/service/annotation-draw.service.ts +++ b/apps/red-ui/src/app/screens/file/service/annotation-draw.service.ts @@ -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); diff --git a/apps/red-ui/src/app/screens/file/service/annotation-processing.service.ts b/apps/red-ui/src/app/screens/file/service/annotation-processing.service.ts index 59abab578..8e198148c 100644 --- a/apps/red-ui/src/app/screens/file/service/annotation-processing.service.ts +++ b/apps/red-ui/src/app/screens/file/service/annotation-processing.service.ts @@ -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) => { diff --git a/apps/red-ui/src/app/state/app-state.service.ts b/apps/red-ui/src/app/state/app-state.service.ts index 05af2ef8a..37b4e2f84 100644 --- a/apps/red-ui/src/app/state/app-state.service.ts +++ b/apps/red-ui/src/app/state/app-state.service.ts @@ -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;