From 5612fc01eacd86c262c1a91b0391c9d1d6aa7849 Mon Sep 17 00:00:00 2001 From: Timo Bejan Date: Mon, 28 Feb 2022 10:56:25 +0200 Subject: [PATCH 1/2] text-highlights --- .../src/app/models/file/annotation.wrapper.ts | 35 ++++- .../src/app/models/file/file-data.model.ts | 20 +++ .../app/modules/dossier/dossiers.module.ts | 13 +- .../annotation-card.component.html | 8 +- .../annotations-list.component.html | 107 +++++++++------ .../annotations-list.component.scss | 4 + .../annotations-list.component.ts | 73 ++++++++++- .../file-workload.component.html | 7 +- .../file-workload.component.scss | 21 +-- .../file-workload/file-workload.component.ts | 17 +++ .../type-annotation-icon.component.ts | 9 +- .../view-switch/view-switch.component.html | 11 ++ .../highlight-action-dialog.component.html | 35 +++++ .../highlight-action-dialog.component.ts | 70 ++++++++++ .../file-preview-screen.component.ts | 21 ++- .../services/annotation-draw.service.ts | 123 ++++++++++-------- .../services/view-mode.service.ts | 8 ++ .../services/annotation-processing.service.ts | 24 +++- .../services/dossiers-dialog.service.ts | 7 +- .../services/pdf-viewer-data.service.ts | 8 +- .../services/text-highlight.service.ts | 40 ++++++ .../annotation-icon.component.html | 2 +- .../type-filter/type-filter.component.html | 5 +- .../annotation-types-translations.ts | 3 +- .../app/utils/sorters/super-type-sorter.ts | 1 + apps/red-ui/src/assets/i18n/en.json | 38 +++++- .../src/assets/styles/red-components.scss | 31 ++++- libs/common-ui | 2 +- libs/red-domain/src/index.ts | 1 + libs/red-domain/src/lib/shared/view-mode.ts | 2 +- .../lib/text-highlight/imported-redaction.ts | 6 + .../src/lib/text-highlight/index.ts | 4 + .../text-highlight-operation.ts | 5 + .../text-highlight/text-highlight.request.ts | 8 ++ .../text-highlight/text-highlight.response.ts | 9 ++ 35 files changed, 632 insertions(+), 146 deletions(-) create mode 100644 apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/dialogs/highlight-action-dialog/highlight-action-dialog.component.html create mode 100644 apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/dialogs/highlight-action-dialog/highlight-action-dialog.component.ts create mode 100644 apps/red-ui/src/app/modules/dossier/services/text-highlight.service.ts create mode 100644 libs/red-domain/src/lib/text-highlight/imported-redaction.ts create mode 100644 libs/red-domain/src/lib/text-highlight/index.ts create mode 100644 libs/red-domain/src/lib/text-highlight/text-highlight-operation.ts create mode 100644 libs/red-domain/src/lib/text-highlight/text-highlight.request.ts create mode 100644 libs/red-domain/src/lib/text-highlight/text-highlight.response.ts diff --git a/apps/red-ui/src/app/models/file/annotation.wrapper.ts b/apps/red-ui/src/app/models/file/annotation.wrapper.ts index 46a9a584a..618b9b02a 100644 --- a/apps/red-ui/src/app/models/file/annotation.wrapper.ts +++ b/apps/red-ui/src/app/models/file/annotation.wrapper.ts @@ -1,9 +1,10 @@ import { annotationTypesTranslations } from '../../translations/annotation-types-translations'; import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker'; -import { IComment, IManualChange, IPoint, IRectangle, LogEntryStatus, ManualRedactionType } from '@red/domain'; +import { IComment, IManualChange, ImportedRedaction, IPoint, IRectangle, LogEntryStatus, ManualRedactionType } from '@red/domain'; import { RedactionLogEntry } from '@models/file/redaction-log.entry'; export type AnnotationSuperType = + | 'text-highlight' | 'suggestion-change-legal-basis' | 'suggestion-recategorize-image' | 'suggestion-add-dictionary' @@ -121,6 +122,10 @@ export class AnnotationWrapper { } get filterKey() { + if (this.isHighlight) { + return this.color; + } + return this.topLevelFilter ? this.superType : this.superType + this.type; } @@ -154,6 +159,10 @@ export class AnnotationWrapper { return this.superType === 'hint'; } + get isHighlight() { + return this.superType === 'text-highlight'; + } + get isIgnoredHint() { return this.superType === 'ignored-hint'; } @@ -235,10 +244,34 @@ export class AnnotationWrapper { return this.legalBasisChangeValue || this.legalBasisValue; } + get width(): number { + return Math.floor(this.positions[0].width); + } + + get height(): number { + return Math.floor(this.positions[0].height); + } + get previewAnnotation() { return this.isRedacted || this.isSuggestionAdd; } + static fromHighlight(color: string, entry: ImportedRedaction) { + const annotationWrapper = new AnnotationWrapper(); + + annotationWrapper.annotationId = entry.id; + annotationWrapper.pageNumber = entry.positions[0].page; + annotationWrapper.superType = 'text-highlight'; + annotationWrapper.typeValue = 'text-highlight'; + annotationWrapper.value = 'Imported'; + annotationWrapper.color = color; + annotationWrapper.positions = entry.positions; + annotationWrapper.firstTopLeftPoint = entry.positions[0]?.topLeft; + annotationWrapper.typeLabel = annotationTypesTranslations[annotationWrapper.superType]; + + return annotationWrapper; + } + static fromData(redactionLogEntry?: RedactionLogEntry) { const annotationWrapper = new AnnotationWrapper(); diff --git a/apps/red-ui/src/app/models/file/file-data.model.ts b/apps/red-ui/src/app/models/file/file-data.model.ts index 9aa33ccb8..a66d13b3b 100644 --- a/apps/red-ui/src/app/models/file/file-data.model.ts +++ b/apps/red-ui/src/app/models/file/file-data.model.ts @@ -7,6 +7,7 @@ import { IViewedPage, LogEntryStatus, ManualRedactionType, + TextHighlightResponse, ViewMode, } from '@red/domain'; import { AnnotationWrapper } from './annotation.wrapper'; @@ -19,6 +20,8 @@ export class FileDataModel { allAnnotations: AnnotationWrapper[] = []; readonly hasChangeLog$ = new BehaviorSubject(false); missingTypes = new Set(); + _textHighlightResponse: TextHighlightResponse; + textHighlightAnnotations: AnnotationWrapper[] = []; constructor( private readonly _file: File, @@ -39,7 +42,24 @@ export class FileDataModel { this._buildAllAnnotations(); } + set textHighlights(textHighlightResponse: TextHighlightResponse) { + this._textHighlightResponse = textHighlightResponse; + + const highlights = []; + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument + for (const color of Object.keys(textHighlightResponse.redactionPerColor)) { + for (const entry of textHighlightResponse.redactionPerColor[color]) { + const annotation = AnnotationWrapper.fromHighlight(color, entry); + highlights.push(annotation); + } + } + this.textHighlightAnnotations = highlights; + } + getVisibleAnnotations(viewMode: ViewMode) { + if (viewMode === 'TEXT_HIGHLIGHTS') { + return this.textHighlightAnnotations; + } return this.allAnnotations.filter(annotation => { if (viewMode === 'STANDARD') { return !annotation.isChangeLogRemoved; diff --git a/apps/red-ui/src/app/modules/dossier/dossiers.module.ts b/apps/red-ui/src/app/modules/dossier/dossiers.module.ts index 9fd58b8cb..64171adb8 100644 --- a/apps/red-ui/src/app/modules/dossier/dossiers.module.ts +++ b/apps/red-ui/src/app/modules/dossier/dossiers.module.ts @@ -25,6 +25,8 @@ import { OverlayModule } from '@angular/cdk/overlay'; import { SharedDossiersModule } from './shared/shared-dossiers.module'; import { ResizeAnnotationDialogComponent } from './dialogs/resize-annotation-dialog/resize-annotation-dialog.component'; import { EditDossierTeamComponent } from './dialogs/edit-dossier-dialog/edit-dossier-team/edit-dossier-team.component'; +import { HighlightActionDialogComponent } from './screens/file-preview-screen/dialogs/highlight-action-dialog/highlight-action-dialog.component'; +import { ColorPickerModule } from 'ngx-color-picker'; const screens = [SearchScreenComponent]; @@ -39,6 +41,7 @@ const dialogs = [ AssignReviewerApproverDialogComponent, ChangeLegalBasisDialogComponent, RecategorizeImageDialogComponent, + HighlightActionDialogComponent, ]; const components = [ @@ -58,6 +61,14 @@ const services = [DossiersDialogService, ManualAnnotationService, AnnotationProc @NgModule({ declarations: [...components], providers: [...services], - imports: [CommonModule, SharedModule, SharedDossiersModule, FileUploadDownloadModule, DossiersRoutingModule, OverlayModule], + imports: [ + CommonModule, + SharedModule, + SharedDossiersModule, + FileUploadDownloadModule, + DossiersRoutingModule, + OverlayModule, + ColorPickerModule, + ], }) export class DossiersModule {} diff --git a/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/components/annotation-card/annotation-card.component.html b/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/components/annotation-card/annotation-card.component.html index 8698ee617..8eec26deb 100644 --- a/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/components/annotation-card/annotation-card.component.html +++ b/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/components/annotation-card/annotation-card.component.html @@ -5,7 +5,7 @@
{{ annotation.typeLabel | translate }}
-
+
{{ annotation.descriptor | translate }}: : {{ annotation.shortContent }}
+
+ : {{ annotation.color }} +
+
+ : {{ annotation.width }}x{{ annotation.height }} px +
diff --git a/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/components/annotations-list/annotations-list.component.html b/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/components/annotations-list/annotations-list.component.html index ed9e5f03b..75536064a 100644 --- a/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/components/annotations-list/annotations-list.component.html +++ b/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/components/annotations-list/annotations-list.component.html @@ -1,46 +1,75 @@ -
-
- -
- - -
-
- - {{ annotation.comments.length }} -
- -
- -
+ +
+
+ +
- +
+ + + +
- -
+
+
+ +
+ + +
+
+ + {{ annotation.comments.length }} +
+ +
+ +
+
+ + +
+ + +
+ div { + display: flex; +} diff --git a/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/components/annotations-list/annotations-list.component.ts b/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/components/annotations-list/annotations-list.component.ts index cb29ddd3a..f864b8bb4 100644 --- a/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/components/annotations-list/annotations-list.component.ts +++ b/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/components/annotations-list/annotations-list.component.ts @@ -1,11 +1,20 @@ import { ChangeDetectionStrategy, Component, EventEmitter, Input, OnChanges, Output, SimpleChanges, TemplateRef } from '@angular/core'; import { AnnotationWrapper } from '@models/file/annotation.wrapper'; -import { FilterService, IqserEventTarget } from '@iqser/common-ui'; +import { CircleButtonTypes, FilterService, IqserEventTarget } from '@iqser/common-ui'; import { MultiSelectService } from '../../services/multi-select.service'; import { AnnotationReferencesService } from '../../services/annotation-references.service'; -import { ViewModeService } from '../../services/view-mode.service'; import { FilePreviewStateService } from '../../services/file-preview-state.service'; -import { UserPreferenceService } from '../../../../../../services/user-preference.service'; +import { UserPreferenceService } from '@services/user-preference.service'; +import { ViewModeService } from '../../services/view-mode.service'; +import { BehaviorSubject } from 'rxjs'; +import { DossiersDialogService } from '../../../../services/dossiers-dialog.service'; +import { TextHighlightOperation } from '@red/domain'; + +interface HighlightGroup { + startIdx: number; + color: string; + length: number; +} @Component({ selector: 'redaction-annotations-list', @@ -14,6 +23,8 @@ import { UserPreferenceService } from '../../../../../../services/user-preferenc changeDetection: ChangeDetectionStrategy.OnPush, }) export class AnnotationsListComponent implements OnChanges { + readonly circleButtonTypes = CircleButtonTypes; + @Input() annotations: AnnotationWrapper[]; @Input() selectedAnnotations: AnnotationWrapper[]; @Input() annotationActionsTemplate: TemplateRef; @@ -23,19 +34,36 @@ export class AnnotationsListComponent implements OnChanges { @Output() readonly selectAnnotations = new EventEmitter(); @Output() readonly deselectAnnotations = new EventEmitter(); + highlightGroups$ = new BehaviorSubject([]); + constructor( readonly multiSelectService: MultiSelectService, - readonly viewModeService: ViewModeService, readonly annotationReferencesService: AnnotationReferencesService, + readonly state: FilePreviewStateService, private readonly _filterService: FilterService, - private readonly _state: FilePreviewStateService, private readonly _userPreferenceService: UserPreferenceService, + private readonly _viewModeService: ViewModeService, + private readonly _dialogService: DossiersDialogService, ) {} + convertHighlights(highlightGroup: HighlightGroup): void { + const data = this._getActionData(highlightGroup, TextHighlightOperation.CONVERT); + this._dialogService.openDialog('highlightAction', null, data); + } + + removeHighlights(highlightGroup: HighlightGroup): void { + const data = this._getActionData(highlightGroup, TextHighlightOperation.REMOVE); + this._dialogService.openDialog('highlightAction', null, data); + } + ngOnChanges(changes: SimpleChanges): void { - if (changes.annotations && this.annotations) { + if (changes.annotations && this.annotations && !this._viewModeService.isTextHighlights) { this.annotations = this.annotations.sort(this.annotationsPositionCompare); } + + if (this._viewModeService.isTextHighlights) { + this._updateHighlightGroups(); + } } annotationClicked(annotation: AnnotationWrapper, $event: MouseEvent): void { @@ -80,4 +108,37 @@ export class AnnotationsListComponent implements OnChanges { return first.x < second.y ? -1 : 1; } } + + showHighlightGroup(idx: number): HighlightGroup { + return this._viewModeService.isTextHighlights && this.highlightGroups$.value.find(h => h.startIdx === idx); + } + + private _getActionData(highlightGroup: HighlightGroup, operation: TextHighlightOperation) { + return { + dossierId: this.state.dossierId, + fileId: this.state.fileId, + color: highlightGroup.color, + operation, + }; + } + + private _updateHighlightGroups(): void { + if (!this.annotations?.length) { + return; + } + const highlightGroups: HighlightGroup[] = []; + let lastGroup: HighlightGroup; + for (let idx = 0; idx < this.annotations.length; ++idx) { + if (idx === 0 || this.annotations[idx].color !== this.annotations[idx - 1].color) { + if (lastGroup) { + highlightGroups.push(lastGroup); + } + lastGroup = { startIdx: idx, length: 1, color: this.annotations[idx].color }; + } else { + lastGroup.length += 1; + } + } + highlightGroups.push(lastGroup); + this.highlightGroups$.next(highlightGroups); + } } diff --git a/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/components/file-workload/file-workload.component.html b/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/components/file-workload/file-workload.component.html index c1e083ac7..528e8846d 100644 --- a/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/components/file-workload/file-workload.component.html +++ b/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/components/file-workload/file-workload.component.html @@ -10,7 +10,8 @@
-
+
+ {{ title$ | async | translate }}
diff --git a/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/components/file-workload/file-workload.component.scss b/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/components/file-workload/file-workload.component.scss index 95e122287..1b7ef781f 100644 --- a/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/components/file-workload/file-workload.component.scss +++ b/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/components/file-workload/file-workload.component.scss @@ -115,25 +115,6 @@ } } - .page-separator { - border-bottom: 1px solid variables.$separator; - height: 32px; - box-sizing: border-box; - padding: 0 10px; - display: flex; - align-items: center; - justify-content: space-between; - background-color: variables.$grey-6; - - > div { - display: flex; - - > div:not(:last-child) { - margin-right: 8px; - } - } - } - .annotations { overflow: hidden; width: 100%; @@ -157,7 +138,7 @@ padding-left: 0 !important; } -::ng-deep .page-separator iqser-circle-button mat-icon { +::ng-deep .workload-separator iqser-circle-button.excluded mat-icon { color: var(--iqser-primary); } diff --git a/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/components/file-workload/file-workload.component.ts b/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/components/file-workload/file-workload.component.ts index e92b5f771..fded2fb67 100644 --- a/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/components/file-workload/file-workload.component.ts +++ b/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/components/file-workload/file-workload.component.ts @@ -34,6 +34,8 @@ import { MultiSelectService } from '../../services/multi-select.service'; import { DocumentInfoService } from '../../services/document-info.service'; import { SkippedService } from '../../services/skipped.service'; import { FilePreviewStateService } from '../../services/file-preview-state.service'; +import { ViewModeService } from '../../services/view-mode.service'; +import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker'; const COMMAND_KEY_ARRAY = ['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown', 'Escape']; const ALL_HOTKEY_ARRAY = ['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown']; @@ -67,6 +69,8 @@ export class FileWorkloadComponent { readonly multiSelectActive$: Observable; readonly multiSelectInactive$: Observable; readonly showExcludedPages$: Observable; + readonly title$: Observable; + readonly isHighlights$: Observable; private _annotations$ = new BehaviorSubject([]); @ViewChild('annotationsElement') private readonly _annotationsElement: ElementRef; @ViewChild('quickNavigation') private readonly _quickNavigationElement: ElementRef; @@ -78,6 +82,7 @@ export class FileWorkloadComponent { readonly multiSelectService: MultiSelectService, readonly documentInfoService: DocumentInfoService, readonly excludedPagesService: ExcludedPagesService, + private readonly _viewModeService: ViewModeService, private readonly _changeDetectorRef: ChangeDetectorRef, private readonly _permissionsService: PermissionsService, private readonly _annotationProcessingService: AnnotationProcessingService, @@ -86,6 +91,8 @@ export class FileWorkloadComponent { this.multiSelectActive$ = this._multiSelectActive$; this.multiSelectInactive$ = this._multiSelectInactive$; this.showExcludedPages$ = this._showExcludedPages$; + this.isHighlights$ = this._isHighlights$; + this.title$ = this._title$; } @Input() @@ -112,6 +119,16 @@ export class FileWorkloadComponent { ); } + private get _title$(): Observable { + return this.isHighlights$.pipe( + map(isHighlights => (isHighlights ? _('file-preview.tabs.highlights.label') : _('file-preview.tabs.annotations.label'))), + ); + } + + private get _isHighlights$(): Observable { + return this._viewModeService.viewMode$.pipe(map(() => this._viewModeService.isTextHighlights)); + } + private get _multiSelectInactive$() { return this.multiSelectService.inactive$.pipe( tap(value => { diff --git a/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/components/type-annotation-icon/type-annotation-icon.component.ts b/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/components/type-annotation-icon/type-annotation-icon.component.ts index 1ffa10e30..2e5f280b1 100644 --- a/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/components/type-annotation-icon/type-annotation-icon.component.ts +++ b/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/components/type-annotation-icon/type-annotation-icon.component.ts @@ -26,9 +26,11 @@ export class TypeAnnotationIconComponent implements OnChanges { return; } - const { isSuggestion, isRecommendation, isSkipped, isDeclinedSuggestion, isHint, isIgnoredHint } = this.annotation; + const { isHighlight, isSuggestion, isRecommendation, isSkipped, isDeclinedSuggestion, isHint, isIgnoredHint } = this.annotation; - if (this.annotation.isSuperTypeBasedColor) { + if (isHighlight) { + this.color = this.annotation.color; + } else if (this.annotation.isSuperTypeBasedColor) { this.color = this._dictionariesMapService.getDictionaryColor(this.annotation.superType, this._dossierTemplateId); } else { this.color = this._dictionariesMapService.getDictionaryColor(this.annotation.type, this._dossierTemplateId); @@ -36,6 +38,7 @@ export class TypeAnnotationIconComponent implements OnChanges { this.type = isSuggestion || isDeclinedSuggestion ? 'rhombus' : isHint || isIgnoredHint ? 'circle' : isRecommendation ? 'hexagon' : 'square'; - this.label = isSuggestion || isDeclinedSuggestion ? 'S' : isSkipped ? 'S' : this.annotation.type[0].toUpperCase(); + + this.label = isHighlight ? '' : isSuggestion || isDeclinedSuggestion || isSkipped ? 'S' : this.annotation.type[0].toUpperCase(); } } diff --git a/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/components/view-switch/view-switch.component.html b/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/components/view-switch/view-switch.component.html index d1e001a44..d997c1c45 100644 --- a/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/components/view-switch/view-switch.component.html +++ b/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/components/view-switch/view-switch.component.html @@ -30,4 +30,15 @@ > {{ 'file-preview.redacted' | translate }} + + diff --git a/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/dialogs/highlight-action-dialog/highlight-action-dialog.component.html b/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/dialogs/highlight-action-dialog/highlight-action-dialog.component.html new file mode 100644 index 000000000..add8e2521 --- /dev/null +++ b/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/dialogs/highlight-action-dialog/highlight-action-dialog.component.html @@ -0,0 +1,35 @@ +
+
+
+ +
+
+ +
+ + +
+
+ +
+ + {{ confirmationMessage | translate }} + +
+
+ +
+ +
+
+
+ + +
diff --git a/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/dialogs/highlight-action-dialog/highlight-action-dialog.component.ts b/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/dialogs/highlight-action-dialog/highlight-action-dialog.component.ts new file mode 100644 index 000000000..59dd303e7 --- /dev/null +++ b/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/dialogs/highlight-action-dialog/highlight-action-dialog.component.ts @@ -0,0 +1,70 @@ +import { Component, Inject, Injector } from '@angular/core'; +import { FormBuilder, FormGroup, Validators } from '@angular/forms'; +import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; +import { TextHighlightOperation } from '@red/domain'; +import { BaseDialogComponent, LoadingService } from '@iqser/common-ui'; +import { TextHighlightService } from '../../../../services/text-highlight.service'; +import { firstValueFrom } from 'rxjs'; +import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker'; + +export interface HighlightActionData { + readonly operation: TextHighlightOperation; + readonly color: string; + readonly dossierId: string; + readonly fileId: string; +} + +@Component({ + templateUrl: './highlight-action-dialog.component.html', +}) +export class HighlightActionDialogComponent extends BaseDialogComponent { + readonly title: string; + readonly details: string; + readonly confirmationMessage: string; + readonly saveMessage: string; + + constructor( + private readonly _formBuilder: FormBuilder, + protected readonly _injector: Injector, + protected readonly _dialogRef: MatDialogRef, + private readonly _textHighlightService: TextHighlightService, + private readonly _loadingService: LoadingService, + @Inject(MAT_DIALOG_DATA) readonly data: HighlightActionData, + ) { + super(_injector, _dialogRef); + this.form = this._getForm(); + this.initialFormValue = this.form.getRawValue(); + + this.title = + data.operation === TextHighlightOperation.CONVERT + ? _('highlight-action-dialog.convert.title') + : _('highlight-action-dialog.remove.title'); + this.details = + data.operation === TextHighlightOperation.CONVERT + ? _('highlight-action-dialog.convert.details') + : _('highlight-action-dialog.remove.details'); + this.confirmationMessage = + data.operation === TextHighlightOperation.CONVERT + ? _('highlight-action-dialog.convert.confirmation') + : _('highlight-action-dialog.remove.confirmation'); + this.saveMessage = + data.operation === TextHighlightOperation.CONVERT + ? _('highlight-action-dialog.convert.save') + : _('highlight-action-dialog.remove.save'); + } + + async save(): Promise { + this._loadingService.start(); + const { dossierId, fileId, color, operation } = this.data; + await firstValueFrom(this._textHighlightService.performHighlightsAction(dossierId, fileId, [color], operation)); + this._loadingService.stop(); + this._dialogRef.close(true); + } + + private _getForm(): FormGroup { + return this._formBuilder.group({ + color: [{ value: this.data.color, disabled: true }, Validators.required], + confirmation: [false, Validators.requiredTrue], + }); + } +} diff --git a/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/file-preview-screen.component.ts b/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/file-preview-screen.component.ts index dc7183e8f..a4b57d3b2 100644 --- a/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/file-preview-screen.component.ts +++ b/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/file-preview-screen.component.ts @@ -155,6 +155,7 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni const ocrAnnotationIds = this._fileData.allAnnotations.filter(a => a.isOCR).map(a => a.id); const annotations = this._getAnnotations(a => a.getCustomData('redact-manager')); const redactions = annotations.filter(a => a.getCustomData('redaction')); + const highlights = annotations.filter(a => a.getCustomData('highlight')); switch (this.viewModeService.viewMode) { case 'STANDARD': { @@ -165,7 +166,7 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni const nonStandardEntries = annotations.filter(a => a.getCustomData('changeLogRemoved') === 'true'); this._setAnnotationsOpacity(standardEntries, true); this._show(standardEntries); - this._hide(nonStandardEntries); + this._hide([...nonStandardEntries, ...highlights]); break; } case 'DELTA': { @@ -174,7 +175,7 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni this._setAnnotationsColor(redactions, 'annotationColor'); this._setAnnotationsOpacity(changeLogEntries, true); this._show(changeLogEntries); - this._hide(nonChangeLogEntries); + this._hide([...nonChangeLogEntries, ...highlights]); break; } case 'REDACTED': { @@ -182,9 +183,23 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni this._setAnnotationsOpacity(redactions); this._setAnnotationsColor(redactions, 'redactionColor'); this._show(redactions); - this._hide(nonRedactionEntries); + this._hide([...nonRedactionEntries, ...highlights]); break; } + case 'TEXT_HIGHLIGHTS': { + this._loadingService.start(); + const textHighlights = await firstValueFrom(this._pdfViewerDataService.loadTextHighlightsFor(this.dossierId, this.fileId)); + this._hide(annotations); + this._fileData.textHighlights = textHighlights; + await this._annotationDrawService.drawAnnotations( + this.activeViewer, + this._fileData.textHighlightAnnotations, + this.dossierId, + this.fileId, + false, + ); + this._loadingService.stop(); + } } await this._stampPDF(); diff --git a/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/services/annotation-draw.service.ts b/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/services/annotation-draw.service.ts index 508bc4242..42de352b7 100644 --- a/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/services/annotation-draw.service.ts +++ b/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/services/annotation-draw.service.ts @@ -156,66 +156,85 @@ export class AnnotationDrawService { compareMode: boolean, ) { const pageNumber = compareMode ? annotationWrapper.pageNumber * 2 - 1 : annotationWrapper.pageNumber; - const dossierTemplateId = this._dossiersService.find(dossierId).dossierTemplateId; - let annotation: Core.Annotations.RectangleAnnotation | Core.Annotations.TextHighlightAnnotation; - if (annotationWrapper.rectangle || annotationWrapper.isImage) { - annotation = new activeViewer.Core.Annotations.RectangleAnnotation(); + if (annotationWrapper.superType === 'text-highlight') { + const rectangleAnnot = new activeViewer.Core.Annotations.RectangleAnnotation(); const pageHeight = activeViewer.Core.documentViewer.getPageHeight(pageNumber); - const firstPosition = annotationWrapper.positions[0]; - annotation.X = firstPosition.topLeft.x; - annotation.Y = pageHeight - (firstPosition.topLeft.y + firstPosition.height); - annotation.Width = firstPosition.width; - annotation.FillColor = this.getAndConvertColor( + const rectangle: IRectangle = annotationWrapper.positions[0]; + rectangleAnnot.PageNumber = pageNumber; + rectangleAnnot.X = rectangle.topLeft.x; + rectangleAnnot.Y = pageHeight - (rectangle.topLeft.y + rectangle.height); + rectangleAnnot.Width = rectangle.width; + rectangleAnnot.Height = rectangle.height; + rectangleAnnot.ReadOnly = true; + rectangleAnnot.StrokeColor = this.convertColor(activeViewer, annotationWrapper.color); + rectangleAnnot.StrokeThickness = 1; + rectangleAnnot.Id = annotationWrapper.id; + + return rectangleAnnot; + } else { + const dossierTemplateId = this._dossiersService.find(dossierId).dossierTemplateId; + + let annotation: Core.Annotations.RectangleAnnotation | Core.Annotations.TextHighlightAnnotation; + if (annotationWrapper.rectangle || annotationWrapper.isImage) { + annotation = new activeViewer.Core.Annotations.RectangleAnnotation(); + const pageHeight = activeViewer.Core.documentViewer.getPageHeight(pageNumber); + const firstPosition = annotationWrapper.positions[0]; + annotation.X = firstPosition.topLeft.x; + annotation.Y = pageHeight - (firstPosition.topLeft.y + firstPosition.height); + annotation.Width = firstPosition.width; + annotation.FillColor = this.getAndConvertColor( + activeViewer, + dossierTemplateId, + annotationWrapper.superType, + annotationWrapper.type, + ); + annotation.Opacity = annotationWrapper.isChangeLogRemoved + ? AnnotationDrawService.DEFAULT_REMOVED_ANNOTATION_OPACITY + : AnnotationDrawService.DEFAULT_RECTANGLE_ANNOTATION_OPACITY; + annotation.Height = firstPosition.height; + annotation.Intensity = 100; + } else { + annotation = new activeViewer.Core.Annotations.TextHighlightAnnotation(); + annotation.Quads = this._rectanglesToQuads(annotationWrapper.positions, activeViewer, pageNumber); + annotation.Opacity = annotationWrapper.isChangeLogRemoved + ? AnnotationDrawService.DEFAULT_REMOVED_ANNOTATION_OPACITY + : AnnotationDrawService.DEFAULT_TEXT_ANNOTATION_OPACITY; + } + + annotation.setContents(annotationWrapper.content); + + annotation.PageNumber = pageNumber; + annotation.StrokeColor = this.getAndConvertColor( activeViewer, dossierTemplateId, annotationWrapper.superType, annotationWrapper.type, ); - annotation.Opacity = annotationWrapper.isChangeLogRemoved - ? AnnotationDrawService.DEFAULT_REMOVED_ANNOTATION_OPACITY - : AnnotationDrawService.DEFAULT_RECTANGLE_ANNOTATION_OPACITY; - annotation.Height = firstPosition.height; - annotation.Intensity = 100; - } else { - annotation = new activeViewer.Core.Annotations.TextHighlightAnnotation(); - annotation.Quads = this._rectanglesToQuads(annotationWrapper.positions, activeViewer, pageNumber); - annotation.Opacity = annotationWrapper.isChangeLogRemoved - ? AnnotationDrawService.DEFAULT_REMOVED_ANNOTATION_OPACITY - : AnnotationDrawService.DEFAULT_TEXT_ANNOTATION_OPACITY; + annotation.Id = annotationWrapper.id; + annotation.ReadOnly = true; + // change log entries are drawn lighter + + annotation.Hidden = + annotationWrapper.isChangeLogRemoved || + (this._skippedService.hideSkipped && annotationWrapper.isSkipped) || + annotationWrapper.isOCR || + annotationWrapper.hidden; + annotation.setCustomData('redact-manager', 'true'); + annotation.setCustomData('redaction', String(annotationWrapper.previewAnnotation)); + annotation.setCustomData('highlight', String(annotationWrapper.isHighlight)); + annotation.setCustomData('skipped', String(annotationWrapper.isSkipped)); + annotation.setCustomData('changeLog', String(annotationWrapper.isChangeLogEntry)); + annotation.setCustomData('changeLogRemoved', String(annotationWrapper.isChangeLogRemoved)); + annotation.setCustomData('opacity', String(annotation.Opacity)); + annotation.setCustomData('redactionColor', String(this.getColor(activeViewer, dossierTemplateId, 'redaction', 'redaction'))); + annotation.setCustomData( + 'annotationColor', + String(this.getColor(activeViewer, dossierTemplateId, annotationWrapper.superType, annotationWrapper.type)), + ); + + return annotation; } - - annotation.setContents(annotationWrapper.content); - - annotation.PageNumber = pageNumber; - annotation.StrokeColor = this.getAndConvertColor( - activeViewer, - dossierTemplateId, - annotationWrapper.superType, - annotationWrapper.type, - ); - annotation.Id = annotationWrapper.id; - annotation.ReadOnly = true; - // change log entries are drawn lighter - - annotation.Hidden = - annotationWrapper.isChangeLogRemoved || - (this._skippedService.hideSkipped && annotationWrapper.isSkipped) || - annotationWrapper.isOCR || - annotationWrapper.hidden; - annotation.setCustomData('redact-manager', 'true'); - annotation.setCustomData('redaction', String(annotationWrapper.previewAnnotation)); - annotation.setCustomData('skipped', String(annotationWrapper.isSkipped)); - annotation.setCustomData('changeLog', String(annotationWrapper.isChangeLogEntry)); - annotation.setCustomData('changeLogRemoved', String(annotationWrapper.isChangeLogRemoved)); - annotation.setCustomData('opacity', String(annotation.Opacity)); - annotation.setCustomData('redactionColor', String(this.getColor(activeViewer, dossierTemplateId, 'redaction', 'redaction'))); - annotation.setCustomData( - 'annotationColor', - String(this.getColor(activeViewer, dossierTemplateId, annotationWrapper.superType, annotationWrapper.type)), - ); - - return annotation; } private _rectanglesToQuads(positions: IRectangle[], activeViewer: WebViewerInstance, pageNumber: number): any[] { diff --git a/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/services/view-mode.service.ts b/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/services/view-mode.service.ts index f24d66389..31b824ce8 100644 --- a/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/services/view-mode.service.ts +++ b/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/services/view-mode.service.ts @@ -43,6 +43,10 @@ export class ViewModeService { return this._viewMode$.value === 'REDACTED'; } + get isTextHighlights() { + return this._viewMode$.value === 'TEXT_HIGHLIGHTS'; + } + get isCompare() { return this._compareMode$.value; } @@ -63,6 +67,10 @@ export class ViewModeService { this._switchTo('REDACTED'); } + switchToHighlights() { + this._switchTo('TEXT_HIGHLIGHTS'); + } + private _switchTo(mode: ViewMode) { this._viewMode$.next(mode); } diff --git a/apps/red-ui/src/app/modules/dossier/services/annotation-processing.service.ts b/apps/red-ui/src/app/modules/dossier/services/annotation-processing.service.ts index 06bbf924f..de2bdaab0 100644 --- a/apps/red-ui/src/app/modules/dossier/services/annotation-processing.service.ts +++ b/apps/red-ui/src/app/modules/dossier/services/annotation-processing.service.ts @@ -58,7 +58,13 @@ export class AnnotationProcessingService { } else { // top level filter if (topLevelFilter) { - this._createParentFilter(a.superType, filterMap, filters); + this._createParentFilter( + a.isHighlight ? a.filterKey : a.superType, + filterMap, + filters, + a.isHighlight, + a.isHighlight ? a.color : null, + ); } else { let parentFilter = filterMap.get(a.superType); if (!parentFilter) { @@ -124,18 +130,28 @@ export class AnnotationProcessingService { } obj.forEach((values, page) => { - obj.set(page, this._sortAnnotations(values)); + if (!values[0].isHighlight) { + obj.set(page, this._sortAnnotations(values)); + } }); return obj; } - private _createParentFilter(key: string, filterMap: Map, filters: INestedFilter[]) { + private _createParentFilter( + key: string, + filterMap: Map, + filters: INestedFilter[], + skipTranslation = false, + color?: string, + ) { const filter: INestedFilter = new NestedFilter({ id: key, topLevelFilter: true, matches: 1, - label: annotationTypesTranslations[key], + label: skipTranslation ? key : annotationTypesTranslations[key], + skipTranslation, + color, }); filterMap.set(key, filter); filters.push(filter); diff --git a/apps/red-ui/src/app/modules/dossier/services/dossiers-dialog.service.ts b/apps/red-ui/src/app/modules/dossier/services/dossiers-dialog.service.ts index ca5aef32e..9e5d17f5f 100644 --- a/apps/red-ui/src/app/modules/dossier/services/dossiers-dialog.service.ts +++ b/apps/red-ui/src/app/modules/dossier/services/dossiers-dialog.service.ts @@ -11,6 +11,7 @@ import { ChangeLegalBasisDialogComponent } from '../dialogs/change-legal-basis-d import { RecategorizeImageDialogComponent } from '../dialogs/recategorize-image-dialog/recategorize-image-dialog.component'; import { ConfirmationDialogComponent, DialogConfig, DialogService, largeDialogConfig } from '@iqser/common-ui'; import { ResizeAnnotationDialogComponent } from '../dialogs/resize-annotation-dialog/resize-annotation-dialog.component'; +import { HighlightActionDialogComponent } from '../screens/file-preview-screen/dialogs/highlight-action-dialog/highlight-action-dialog.component'; type DialogType = | 'confirm' @@ -23,7 +24,8 @@ type DialogType = | 'removeAnnotations' | 'resizeAnnotation' | 'forceAnnotation' - | 'manualAnnotation'; + | 'manualAnnotation' + | 'highlightAction'; @Injectable() export class DossiersDialogService extends DialogService { @@ -67,6 +69,9 @@ export class DossiersDialogService extends DialogService { component: ManualAnnotationDialogComponent, dialogConfig: { autoFocus: true }, }, + highlightAction: { + component: HighlightActionDialogComponent, + }, }; constructor(protected readonly _dialog: MatDialog) { diff --git a/apps/red-ui/src/app/modules/dossier/services/pdf-viewer-data.service.ts b/apps/red-ui/src/app/modules/dossier/services/pdf-viewer-data.service.ts index cfd3e816d..394baf523 100644 --- a/apps/red-ui/src/app/modules/dossier/services/pdf-viewer-data.service.ts +++ b/apps/red-ui/src/app/modules/dossier/services/pdf-viewer-data.service.ts @@ -3,7 +3,7 @@ import { forkJoin, Observable, of } from 'rxjs'; import { catchError, map, tap } from 'rxjs/operators'; import { FileDataModel } from '@models/file/file-data.model'; import { PermissionsService } from '@services/permissions.service'; -import { Dictionary, File, IRedactionLog, IViewedPage } from '@red/domain'; +import { Dictionary, File, IRedactionLog, IViewedPage, TextHighlightResponse } from '@red/domain'; import { RedactionLogService } from './redaction-log.service'; import { ViewedPagesService } from '@services/entity-services/viewed-pages.service'; import { UserPreferenceService } from '@services/user-preference.service'; @@ -11,12 +11,14 @@ import { FilePreviewStateService } from '../screens/file-preview-screen/services import { Toaster } from '@iqser/common-ui'; import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker'; import { DictionariesMapService } from '@services/entity-services/dictionaries-map.service'; +import { TextHighlightService } from './text-highlight.service'; @Injectable() export class PdfViewerDataService { constructor( private readonly _permissionsService: PermissionsService, private readonly _redactionLogService: RedactionLogService, + private readonly _textHighlightService: TextHighlightService, private readonly _viewedPagesService: ViewedPagesService, private readonly _userPreferenceService: UserPreferenceService, private readonly _stateService: FilePreviewStateService, @@ -31,6 +33,10 @@ export class PdfViewerDataService { ); } + loadTextHighlightsFor(dossierId: string, fileId: string): Observable { + return this._textHighlightService.getTextHighlights(dossierId, fileId).pipe(catchError(() => of({}))); + } + loadDataFor(newFile: File): Observable { const redactionLog$ = this.loadRedactionLogFor(newFile.dossierId, newFile.fileId); const viewedPages$ = this.getViewedPagesFor(newFile); diff --git a/apps/red-ui/src/app/modules/dossier/services/text-highlight.service.ts b/apps/red-ui/src/app/modules/dossier/services/text-highlight.service.ts new file mode 100644 index 000000000..4f50d4502 --- /dev/null +++ b/apps/red-ui/src/app/modules/dossier/services/text-highlight.service.ts @@ -0,0 +1,40 @@ +import { Injectable, Injector } from '@angular/core'; +import { GenericService, RequiredParam, Toaster, Validate } from '@iqser/common-ui'; +import { TextHighlightOperation, TextHighlightRequest, TextHighlightResponse } from '@red/domain'; +import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker'; +import { tap } from 'rxjs/operators'; + +@Injectable({ + providedIn: 'root', +}) +export class TextHighlightService extends GenericService { + constructor(protected readonly _injector: Injector, private readonly _toaster: Toaster) { + super(_injector, ''); + } + + @Validate() + getTextHighlights(@RequiredParam() dossierId: string, @RequiredParam() fileId: string) { + const request: TextHighlightRequest = { + dossierId, + fileId, + operation: TextHighlightOperation.INFO, + }; + + return this._post(request, 'texthighlights-conversion'); + } + + @Validate() + performHighlightsAction( + @RequiredParam() dossierId: string, + @RequiredParam() fileId: string, + @RequiredParam() colors: string[], + @RequiredParam() operation: TextHighlightOperation, + ) { + const request: TextHighlightRequest = { dossierId, fileId, colors, operation }; + return this._post(request, 'texthighlights-conversion').pipe( + tap(() => { + this._toaster.success(_('highlight-action-dialog.success'), { params: { operation } }); + }), + ); + } +} diff --git a/apps/red-ui/src/app/modules/shared/components/annotation-icon/annotation-icon.component.html b/apps/red-ui/src/app/modules/shared/components/annotation-icon/annotation-icon.component.html index 854591c74..4f119b5df 100644 --- a/apps/red-ui/src/app/modules/shared/components/annotation-icon/annotation-icon.component.html +++ b/apps/red-ui/src/app/modules/shared/components/annotation-icon/annotation-icon.component.html @@ -6,5 +6,5 @@ [class.request]="isRequest" class="icon" > - {{ label || dictionary.label.charAt(0) }} + {{ label || dictionary?.label?.charAt(0) }}
diff --git a/apps/red-ui/src/app/modules/shared/components/type-filter/type-filter.component.html b/apps/red-ui/src/app/modules/shared/components/type-filter/type-filter.component.html index 2c08380b1..f8ef2c738 100644 --- a/apps/red-ui/src/app/modules/shared/components/type-filter/type-filter.component.html +++ b/apps/red-ui/src/app/modules/shared/components/type-filter/type-filter.component.html @@ -60,10 +60,13 @@
+ + -{{ filter.label | translate }} + {{ filter.label }} +{{ filter.label | translate }} diff --git a/apps/red-ui/src/app/translations/annotation-types-translations.ts b/apps/red-ui/src/app/translations/annotation-types-translations.ts index 65ad140ef..0ea4d69c1 100644 --- a/apps/red-ui/src/app/translations/annotation-types-translations.ts +++ b/apps/red-ui/src/app/translations/annotation-types-translations.ts @@ -1,7 +1,8 @@ import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker'; -import { AnnotationSuperType } from '../models/file/annotation.wrapper'; +import { AnnotationSuperType } from '@models/file/annotation.wrapper'; export const annotationTypesTranslations: { [key in AnnotationSuperType]: string } = { + 'text-highlight': _('annotation-type.text-highlight'), 'declined-suggestion': _('annotation-type.declined-suggestion'), hint: _('annotation-type.hint'), 'ignored-hint': _('annotation-type.ignored-hint'), diff --git a/apps/red-ui/src/app/utils/sorters/super-type-sorter.ts b/apps/red-ui/src/app/utils/sorters/super-type-sorter.ts index 644827a55..c7d976872 100644 --- a/apps/red-ui/src/app/utils/sorters/super-type-sorter.ts +++ b/apps/red-ui/src/app/utils/sorters/super-type-sorter.ts @@ -1,6 +1,7 @@ import { AnnotationSuperType } from '../../models/file/annotation.wrapper'; export const SuperTypeSorter: { [key in AnnotationSuperType]: number } = { + 'text-highlight': 100, 'suggestion-change-legal-basis': 14, 'suggestion-force-redaction': 15, 'suggestion-force-hint': 16, diff --git a/apps/red-ui/src/assets/i18n/en.json b/apps/red-ui/src/assets/i18n/en.json index ed148fdc3..59b6449b4 100644 --- a/apps/red-ui/src/assets/i18n/en.json +++ b/apps/red-ui/src/assets/i18n/en.json @@ -310,7 +310,8 @@ "suggestion-recategorize-image": "Suggested recategorize image", "suggestion-remove": "Suggested local removal", "suggestion-remove-dictionary": "Suggested dictionary removal", - "suggestion-resize": "Suggested Resize" + "suggestion-resize": "Suggested Resize", + "text-highlight": "Highlight" }, "annotations": "Annotations", "assign-dossier-owner": { @@ -393,6 +394,7 @@ }, "header": "Edit Redaction Reason" }, + "color": "Color", "comments": { "add-comment": "Enter comment", "comments": "{count} {count, plural, one{comment} other{comments}}", @@ -1207,6 +1209,10 @@ "exclude-pages": "Exclude pages from redaction", "excluded-from-redaction": "excluded", "fullscreen": "Full Screen (F)", + "highlights": { + "convert": "Convert highlights", + "remove": "Remove highlights" + }, "last-reviewer": "Last Reviewed by:", "no-data": { "title": "There have been no changes to this page." @@ -1254,8 +1260,13 @@ "put-back": "Undo", "removed-from-redaction": "Removed from redaction" }, + "highlights": { + "label": "Highlights" + }, "is-excluded": "Redaction is disabled for this document." }, + "text-highlights": "Highlights", + "text-highlights-tooltip": "Shows all text-highlights and allows removing or importing them as redactions", "toggle-analysis": { "disable": "Disable redaction", "enable": "Enable for redaction", @@ -1363,6 +1374,30 @@ "text": "Help Mode", "welcome-to-help-mode": " Welcome to Help Mode!
Clicking on interactive elements will open info about them in new tab.
" }, + "highlight-action-dialog": { + "actions": { + "cancel": "Cancel" + }, + "convert": { + "confirmation": "All highlights in the document will be converted", + "details": "All highlights from the document will be converted to Imported Redactions, using the color set up in the Default Colors section of the app.", + "save": "Convert Highlights", + "title": "Convert highlights to imported redactions" + }, + "form": { + "color": { + "label": "Highlight HEX Color" + } + }, + "remove": { + "confirmation": "All highlights in this HEX Color will be removed from the document", + "details": "Removing highlights from the document will delete all the rectangles and leave a white background behind the highlighted text.", + "save": "Remove Highlights", + "title": "Remove highlights" + }, + "success": "{operation, select, CONVERT{Converting} REMOVE{Removing} other{}} highlights in progress..." + }, + "highlights": "{color} - {length} {length, plural, one{highlight} other{highlights}}", "hint": "Hint", "image-category": { "formula": "Formula", @@ -1697,6 +1732,7 @@ "placeholder": "Search documents...", "this-dossier": "in this dossier" }, + "size": "Size", "smtp-auth-config": { "actions": { "cancel": "Cancel", diff --git a/apps/red-ui/src/assets/styles/red-components.scss b/apps/red-ui/src/assets/styles/red-components.scss index ab69f2933..29cb5e9ed 100644 --- a/apps/red-ui/src/assets/styles/red-components.scss +++ b/apps/red-ui/src/assets/styles/red-components.scss @@ -2,13 +2,13 @@ @use 'common-mixins'; .NEW { - stroke: variables.$grey-5; - background-color: variables.$grey-5; + stroke: var(--iqser-grey-5); + background-color: var(--iqser-grey-5); } .UNPROCESSED { - stroke: variables.$grey-3; - background-color: variables.$grey-3; + stroke: var(--iqser-grey-3); + background-color: var(--iqser-grey-3); } .UNDER_REVIEW, @@ -70,8 +70,8 @@ } .INACTIVE { - stroke: variables.$grey-5; - background-color: variables.$grey-5; + stroke: var(--iqser-grey-5); + background-color: var(--iqser-grey-5); } .MANAGER, @@ -79,3 +79,22 @@ stroke: variables.$primary; background-color: variables.$primary; } + +.workload-separator { + border-bottom: 1px solid var(--iqser-separator); + height: 32px; + box-sizing: border-box; + padding: 0 10px; + display: flex; + align-items: center; + justify-content: space-between; + background-color: var(--iqser-grey-6); + + > div { + display: flex; + + > div:not(:last-child) { + margin-right: 8px; + } + } +} diff --git a/libs/common-ui b/libs/common-ui index dd87dd682..17008f447 160000 --- a/libs/common-ui +++ b/libs/common-ui @@ -1 +1 @@ -Subproject commit dd87dd6821ecc8d591b03191964e77fb5fc6e8f8 +Subproject commit 17008f44757a29ae3d09581a94520c2a05670432 diff --git a/libs/red-domain/src/index.ts b/libs/red-domain/src/index.ts index e2d4bf0e4..2858caf86 100644 --- a/libs/red-domain/src/index.ts +++ b/libs/red-domain/src/index.ts @@ -20,3 +20,4 @@ export * from './lib/signature'; export * from './lib/legal-basis'; export * from './lib/dossier-stats'; export * from './lib/dossier-state'; +export * from './lib/text-highlight'; diff --git a/libs/red-domain/src/lib/shared/view-mode.ts b/libs/red-domain/src/lib/shared/view-mode.ts index e4f649334..6db1dbf5a 100644 --- a/libs/red-domain/src/lib/shared/view-mode.ts +++ b/libs/red-domain/src/lib/shared/view-mode.ts @@ -1 +1 @@ -export type ViewMode = 'STANDARD' | 'DELTA' | 'REDACTED'; +export type ViewMode = 'STANDARD' | 'DELTA' | 'REDACTED' | 'TEXT_HIGHLIGHTS'; diff --git a/libs/red-domain/src/lib/text-highlight/imported-redaction.ts b/libs/red-domain/src/lib/text-highlight/imported-redaction.ts new file mode 100644 index 000000000..9d7cf6c69 --- /dev/null +++ b/libs/red-domain/src/lib/text-highlight/imported-redaction.ts @@ -0,0 +1,6 @@ +import { IRectangle } from '../geometry/rectangle'; + +export interface ImportedRedaction { + id: string; + positions: IRectangle[]; +} diff --git a/libs/red-domain/src/lib/text-highlight/index.ts b/libs/red-domain/src/lib/text-highlight/index.ts new file mode 100644 index 000000000..6010350eb --- /dev/null +++ b/libs/red-domain/src/lib/text-highlight/index.ts @@ -0,0 +1,4 @@ +export * from './imported-redaction'; +export * from './text-highlight-operation'; +export * from './text-highlight.response'; +export * from './text-highlight.request'; diff --git a/libs/red-domain/src/lib/text-highlight/text-highlight-operation.ts b/libs/red-domain/src/lib/text-highlight/text-highlight-operation.ts new file mode 100644 index 000000000..c198d7dda --- /dev/null +++ b/libs/red-domain/src/lib/text-highlight/text-highlight-operation.ts @@ -0,0 +1,5 @@ +export enum TextHighlightOperation { + REMOVE = 'REMOVE', + CONVERT = 'CONVERT', + INFO = 'INFO', +} diff --git a/libs/red-domain/src/lib/text-highlight/text-highlight.request.ts b/libs/red-domain/src/lib/text-highlight/text-highlight.request.ts new file mode 100644 index 000000000..0fe3df4a9 --- /dev/null +++ b/libs/red-domain/src/lib/text-highlight/text-highlight.request.ts @@ -0,0 +1,8 @@ +import { TextHighlightOperation } from './text-highlight-operation'; + +export interface TextHighlightRequest { + dossierId: string; + fileId: string; + operation: TextHighlightOperation; + colors?: string[]; +} diff --git a/libs/red-domain/src/lib/text-highlight/text-highlight.response.ts b/libs/red-domain/src/lib/text-highlight/text-highlight.response.ts new file mode 100644 index 000000000..e87e48612 --- /dev/null +++ b/libs/red-domain/src/lib/text-highlight/text-highlight.response.ts @@ -0,0 +1,9 @@ +import { ImportedRedaction } from './imported-redaction'; +import { TextHighlightOperation } from './text-highlight-operation'; + +export interface TextHighlightResponse { + dossierId?: string; + fileId?: string; + operation?: TextHighlightOperation; + redactionPerColor?: { [key: string]: ImportedRedaction[] }; +} From c807524991ec556cbf61db82d53f0db1342ad9ca Mon Sep 17 00:00:00 2001 From: Timo Bejan Date: Mon, 28 Feb 2022 19:32:21 +0200 Subject: [PATCH 2/2] Text highlight cleanup --- .../file-preview-screen.component.ts | 17 +++++++++++++---- libs/red-domain/src/lib/files/file.model.ts | 4 +++- libs/red-domain/src/lib/files/file.ts | 5 +++++ 3 files changed, 21 insertions(+), 5 deletions(-) diff --git a/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/file-preview-screen.component.ts b/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/file-preview-screen.component.ts index a4b57d3b2..2dd141fea 100644 --- a/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/file-preview-screen.component.ts +++ b/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/file-preview-screen.component.ts @@ -152,10 +152,19 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni return; } + const textHighlightAnnotationIds = this._fileData.textHighlightAnnotations.map(a => a.id); + const textHighlightAnnotations = this._getAnnotations((a: Core.Annotations.Annotation) => + textHighlightAnnotationIds.includes(a.Id), + ); + + this._instance.Core.annotationManager.deleteAnnotations(textHighlightAnnotations, { + imported: true, + force: true, + }); + const ocrAnnotationIds = this._fileData.allAnnotations.filter(a => a.isOCR).map(a => a.id); const annotations = this._getAnnotations(a => a.getCustomData('redact-manager')); const redactions = annotations.filter(a => a.getCustomData('redaction')); - const highlights = annotations.filter(a => a.getCustomData('highlight')); switch (this.viewModeService.viewMode) { case 'STANDARD': { @@ -166,7 +175,7 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni const nonStandardEntries = annotations.filter(a => a.getCustomData('changeLogRemoved') === 'true'); this._setAnnotationsOpacity(standardEntries, true); this._show(standardEntries); - this._hide([...nonStandardEntries, ...highlights]); + this._hide([...nonStandardEntries]); break; } case 'DELTA': { @@ -175,7 +184,7 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni this._setAnnotationsColor(redactions, 'annotationColor'); this._setAnnotationsOpacity(changeLogEntries, true); this._show(changeLogEntries); - this._hide([...nonChangeLogEntries, ...highlights]); + this._hide([...nonChangeLogEntries]); break; } case 'REDACTED': { @@ -183,7 +192,7 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni this._setAnnotationsOpacity(redactions); this._setAnnotationsColor(redactions, 'redactionColor'); this._show(redactions); - this._hide([...nonRedactionEntries, ...highlights]); + this._hide([...nonRedactionEntries]); break; } case 'TEXT_HIGHLIGHTS': { diff --git a/libs/red-domain/src/lib/files/file.model.ts b/libs/red-domain/src/lib/files/file.model.ts index b5930769c..deeddd3fd 100644 --- a/libs/red-domain/src/lib/files/file.model.ts +++ b/libs/red-domain/src/lib/files/file.model.ts @@ -40,6 +40,7 @@ export class File extends Entity implements IFile { readonly hasSuggestions: boolean; readonly processingStatus: ProcessingFileStatus; readonly workflowStatus: WorkflowFileStatus; + readonly fileManipulationDate: string; readonly statusSort: number; readonly cacheIdentifier?: string; @@ -96,9 +97,10 @@ export class File extends Entity implements IFile { this.uploader = file.uploader; this.excludedPages = file.excludedPages || []; this.hasSuggestions = !!file.hasSuggestions; + this.fileManipulationDate = file.fileManipulationDate; this.statusSort = StatusSorter[this.workflowStatus]; - this.cacheIdentifier = btoa((this.lastUploaded ?? '') + (this.lastOCRTime ?? '')); + this.cacheIdentifier = btoa(this.fileManipulationDate ?? ''); this.hintsOnly = this.hasHints && !this.hasRedactions; this.hasNone = !this.hasRedactions && !this.hasHints && !this.hasSuggestions; this.isProcessing = isProcessingStatuses.includes(this.processingStatus); diff --git a/libs/red-domain/src/lib/files/file.ts b/libs/red-domain/src/lib/files/file.ts index 6208d4fab..8f8c29446 100644 --- a/libs/red-domain/src/lib/files/file.ts +++ b/libs/red-domain/src/lib/files/file.ts @@ -147,4 +147,9 @@ export interface IFile { readonly processingStatus: ProcessingFileStatus; readonly workflowStatus: WorkflowFileStatus; + + /** + * Last time the actual file was touched + */ + readonly fileManipulationDate: string; }