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 41bd46e73..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,6 +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 3356e3ce5..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,14 +42,31 @@ 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; } else if (viewMode === 'DELTA') { return annotation.isChangeLogEntry; } else { - return annotation.isRedacted; + return annotation.previewAnnotation; } }); } diff --git a/apps/red-ui/src/app/modules/admin/admin-routing.module.ts b/apps/red-ui/src/app/modules/admin/admin-routing.module.ts index 180801557..8595b149b 100644 --- a/apps/red-ui/src/app/modules/admin/admin-routing.module.ts +++ b/apps/red-ui/src/app/modules/admin/admin-routing.module.ts @@ -76,7 +76,6 @@ const routes: Routes = [ path: 'rules', component: BaseDossierTemplateScreenComponent, canActivate: [CompositeRouteGuard], - canDeactivate: [PendingChangesGuard], data: { routeGuards: [AuthGuard, RedRoleGuard], }, diff --git a/apps/red-ui/src/app/modules/admin/dialogs/add-edit-dictionary-dialog/add-edit-dictionary-dialog.component.ts b/apps/red-ui/src/app/modules/admin/dialogs/add-edit-dictionary-dialog/add-edit-dictionary-dialog.component.ts index 1e713ac18..c1748e7f8 100644 --- a/apps/red-ui/src/app/modules/admin/dialogs/add-edit-dictionary-dialog/add-edit-dictionary-dialog.component.ts +++ b/apps/red-ui/src/app/modules/admin/dialogs/add-edit-dictionary-dialog/add-edit-dictionary-dialog.component.ts @@ -41,7 +41,7 @@ export class AddEditDictionaryDialogComponent extends BaseDialogComponent { @Inject(MAT_DIALOG_DATA) private readonly _data: { readonly dictionary: Dictionary; readonly dossierTemplateId: string }, ) { - super(_injector, _dialogRef); + super(_injector, _dialogRef, !!_data.dictionary); this.form = this._getForm(this.dictionary); this.initialFormValue = this.form.getRawValue(); this.hasColor$ = this._colorEmpty$; diff --git a/apps/red-ui/src/app/modules/admin/dialogs/add-edit-dossier-attribute-dialog/add-edit-dossier-attribute-dialog.component.ts b/apps/red-ui/src/app/modules/admin/dialogs/add-edit-dossier-attribute-dialog/add-edit-dossier-attribute-dialog.component.ts index 94dd875f6..1c1e6b7ee 100644 --- a/apps/red-ui/src/app/modules/admin/dialogs/add-edit-dossier-attribute-dialog/add-edit-dossier-attribute-dialog.component.ts +++ b/apps/red-ui/src/app/modules/admin/dialogs/add-edit-dossier-attribute-dialog/add-edit-dossier-attribute-dialog.component.ts @@ -27,7 +27,7 @@ export class AddEditDossierAttributeDialogComponent extends BaseDialogComponent @Inject(MAT_DIALOG_DATA) readonly data: { readonly dossierAttribute: IDossierAttributeConfig; dossierTemplateId: string }, ) { - super(_injector, _dialogRef); + super(_injector, _dialogRef, !!data.dossierAttribute); this.form = this._getForm(this.dossierAttribute); this.initialFormValue = this.form.getRawValue(); } diff --git a/apps/red-ui/src/app/modules/admin/dialogs/add-edit-dossier-state-dialog/add-edit-dossier-state-dialog.component.ts b/apps/red-ui/src/app/modules/admin/dialogs/add-edit-dossier-state-dialog/add-edit-dossier-state-dialog.component.ts index 7a5828ea7..a0a0ac413 100644 --- a/apps/red-ui/src/app/modules/admin/dialogs/add-edit-dossier-state-dialog/add-edit-dossier-state-dialog.component.ts +++ b/apps/red-ui/src/app/modules/admin/dialogs/add-edit-dossier-state-dialog/add-edit-dossier-state-dialog.component.ts @@ -22,7 +22,7 @@ export class AddEditDossierStateDialogComponent extends BaseDialogComponent { protected readonly _dialogRef: MatDialogRef, @Inject(MAT_DIALOG_DATA) readonly data: DialogData, ) { - super(_injector, _dialogRef); + super(_injector, _dialogRef, !!data.dossierState); this.form = this.#getForm(); this.initialFormValue = this.form.getRawValue(); } diff --git a/apps/red-ui/src/app/modules/admin/dialogs/add-edit-dossier-template-dialog/add-edit-dossier-template-dialog.component.ts b/apps/red-ui/src/app/modules/admin/dialogs/add-edit-dossier-template-dialog/add-edit-dossier-template-dialog.component.ts index 5973b7db4..6297f906f 100644 --- a/apps/red-ui/src/app/modules/admin/dialogs/add-edit-dossier-template-dialog/add-edit-dossier-template-dialog.component.ts +++ b/apps/red-ui/src/app/modules/admin/dialogs/add-edit-dossier-template-dialog/add-edit-dossier-template-dialog.component.ts @@ -41,7 +41,7 @@ export class AddEditDossierTemplateDialogComponent extends BaseDialogComponent { private readonly _loadingService: LoadingService, @Inject(MAT_DIALOG_DATA) readonly dossierTemplateId: string, ) { - super(_injector, _dialogRef); + super(_injector, _dialogRef, !!dossierTemplateId); this.dossierTemplate = this._dossierTemplatesService.find(this.dossierTemplateId); this.form = this._getForm(); this.initialFormValue = this.form.getRawValue(); diff --git a/apps/red-ui/src/app/modules/admin/dialogs/add-edit-file-attribute-dialog/add-edit-file-attribute-dialog.component.ts b/apps/red-ui/src/app/modules/admin/dialogs/add-edit-file-attribute-dialog/add-edit-file-attribute-dialog.component.ts index c1870a01a..ebc79cfcf 100644 --- a/apps/red-ui/src/app/modules/admin/dialogs/add-edit-file-attribute-dialog/add-edit-file-attribute-dialog.component.ts +++ b/apps/red-ui/src/app/modules/admin/dialogs/add-edit-file-attribute-dialog/add-edit-file-attribute-dialog.component.ts @@ -34,7 +34,7 @@ export class AddEditFileAttributeDialogComponent extends BaseDialogComponent { numberOfFilterableAttrs: number; }, ) { - super(_injector, _dialogRef); + super(_injector, _dialogRef, !!data.fileAttribute); this.canSetDisplayed = data.numberOfDisplayedAttrs < this.DISPLAYED_FILTERABLE_LIMIT || data.fileAttribute?.displayedInFileList; this.canSetFilterable = data.numberOfFilterableAttrs < this.DISPLAYED_FILTERABLE_LIMIT || data.fileAttribute?.filterable; this.form = this._getForm(this.fileAttribute); diff --git a/apps/red-ui/src/app/modules/admin/dialogs/add-edit-user-dialog/add-edit-user-dialog.component.ts b/apps/red-ui/src/app/modules/admin/dialogs/add-edit-user-dialog/add-edit-user-dialog.component.ts index a35e42d2e..a215c215d 100644 --- a/apps/red-ui/src/app/modules/admin/dialogs/add-edit-user-dialog/add-edit-user-dialog.component.ts +++ b/apps/red-ui/src/app/modules/admin/dialogs/add-edit-user-dialog/add-edit-user-dialog.component.ts @@ -10,24 +10,15 @@ import { BaseDialogComponent } from '@iqser/common-ui'; styleUrls: ['./add-edit-user-dialog.component.scss'], }) export class AddEditUserDialogComponent extends BaseDialogComponent { - @ViewChild(UserDetailsComponent) private readonly _userDetailsComponent: UserDetailsComponent; - resettingPassword = false; + @ViewChild(UserDetailsComponent) private readonly _userDetailsComponent: UserDetailsComponent; constructor( protected readonly _injector: Injector, protected readonly _dialogRef: MatDialogRef, @Inject(MAT_DIALOG_DATA) readonly user: User, ) { - super(_injector, _dialogRef); - } - - toggleResetPassword() { - this.resettingPassword = !this.resettingPassword; - } - - async save(): Promise { - await this._userDetailsComponent.save(); + super(_injector, _dialogRef, !!user); } get changed(): boolean { @@ -38,6 +29,14 @@ export class AddEditUserDialogComponent extends BaseDialogComponent { return this._userDetailsComponent.valid; } + toggleResetPassword() { + this.resettingPassword = !this.resettingPassword; + } + + async save(): Promise { + await this._userDetailsComponent.save(); + } + closeDialog(event) { this._dialogRef.close(event); } diff --git a/apps/red-ui/src/app/modules/admin/dialogs/edit-color-dialog/edit-color-dialog.component.ts b/apps/red-ui/src/app/modules/admin/dialogs/edit-color-dialog/edit-color-dialog.component.ts index 2f544c1d0..d10739d47 100644 --- a/apps/red-ui/src/app/modules/admin/dialogs/edit-color-dialog/edit-color-dialog.component.ts +++ b/apps/red-ui/src/app/modules/admin/dialogs/edit-color-dialog/edit-color-dialog.component.ts @@ -33,7 +33,7 @@ export class EditColorDialogComponent extends BaseDialogComponent { @Inject(MAT_DIALOG_DATA) readonly data: IEditColorData, ) { - super(_injector, _dialogRef); + super(_injector, _dialogRef, true); this._dossierTemplateId = data.dossierTemplateId; this.form = this._getForm(); diff --git a/apps/red-ui/src/app/modules/admin/dialogs/file-attributes-configurations-dialog/file-attributes-configurations-dialog.component.ts b/apps/red-ui/src/app/modules/admin/dialogs/file-attributes-configurations-dialog/file-attributes-configurations-dialog.component.ts index 37bb5c850..6c20af995 100644 --- a/apps/red-ui/src/app/modules/admin/dialogs/file-attributes-configurations-dialog/file-attributes-configurations-dialog.component.ts +++ b/apps/red-ui/src/app/modules/admin/dialogs/file-attributes-configurations-dialog/file-attributes-configurations-dialog.component.ts @@ -28,7 +28,7 @@ export class FileAttributesConfigurationsDialogComponent extends BaseDialogCompo protected readonly _dialogRef: MatDialogRef, @Inject(MAT_DIALOG_DATA) private _data: { config: IFileAttributesConfig; dossierTemplateId: string }, ) { - super(_injector, _dialogRef); + super(_injector, _dialogRef, true); this.form = this._getForm(); this.initialFormValue = this.form.getRawValue(); } diff --git a/apps/red-ui/src/app/modules/admin/screens/justifications/add-edit-justification-dialog/add-edit-justification-dialog.component.ts b/apps/red-ui/src/app/modules/admin/screens/justifications/add-edit-justification-dialog/add-edit-justification-dialog.component.ts index c19723be9..5a53c1d98 100644 --- a/apps/red-ui/src/app/modules/admin/screens/justifications/add-edit-justification-dialog/add-edit-justification-dialog.component.ts +++ b/apps/red-ui/src/app/modules/admin/screens/justifications/add-edit-justification-dialog/add-edit-justification-dialog.component.ts @@ -23,7 +23,7 @@ export class AddEditJustificationDialogComponent extends BaseDialogComponent { protected readonly _dialogRef: MatDialogRef, @Inject(MAT_DIALOG_DATA) public data: { justification?: Justification; dossierTemplateId: string }, ) { - super(_injector, _dialogRef); + super(_injector, _dialogRef, !!data.justification); this.form = this._getForm(); this.initialFormValue = this.form.getRawValue(); diff --git a/apps/red-ui/src/app/modules/admin/screens/rules/rules.module.ts b/apps/red-ui/src/app/modules/admin/screens/rules/rules.module.ts index e2824d79e..9e7d816ba 100644 --- a/apps/red-ui/src/app/modules/admin/screens/rules/rules.module.ts +++ b/apps/red-ui/src/app/modules/admin/screens/rules/rules.module.ts @@ -4,8 +4,9 @@ import { RouterModule } from '@angular/router'; import { SharedModule } from '../../../shared/shared.module'; import { RulesScreenComponent } from './rules-screen/rules-screen.component'; import { MonacoEditorModule } from '@materia-ui/ngx-monaco-editor'; +import { PendingChangesGuard } from '../../../../guards/can-deactivate.guard'; -const routes = [{ path: '', component: RulesScreenComponent }]; +const routes = [{ path: '', component: RulesScreenComponent, canDeactivate: [PendingChangesGuard] }]; @NgModule({ declarations: [RulesScreenComponent], diff --git a/apps/red-ui/src/app/modules/dossier/dialogs/change-legal-basis-dialog/change-legal-basis-dialog.component.ts b/apps/red-ui/src/app/modules/dossier/dialogs/change-legal-basis-dialog/change-legal-basis-dialog.component.ts index 379c21e15..d597b8def 100644 --- a/apps/red-ui/src/app/modules/dossier/dialogs/change-legal-basis-dialog/change-legal-basis-dialog.component.ts +++ b/apps/red-ui/src/app/modules/dossier/dialogs/change-legal-basis-dialog/change-legal-basis-dialog.component.ts @@ -31,7 +31,7 @@ export class ChangeLegalBasisDialogComponent extends BaseDialogComponent impleme protected readonly _dialogRef: MatDialogRef, @Inject(MAT_DIALOG_DATA) private readonly _data: { annotations: AnnotationWrapper[]; dossier: Dossier }, ) { - super(_injector, _dialogRef); + super(_injector, _dialogRef, true); this.form = this._getForm(); } diff --git a/apps/red-ui/src/app/modules/dossier/dialogs/edit-dossier-dialog/edit-dossier-dialog.component.ts b/apps/red-ui/src/app/modules/dossier/dialogs/edit-dossier-dialog/edit-dossier-dialog.component.ts index 775e2cc5c..345a6de54 100644 --- a/apps/red-ui/src/app/modules/dossier/dialogs/edit-dossier-dialog/edit-dossier-dialog.component.ts +++ b/apps/red-ui/src/app/modules/dossier/dialogs/edit-dossier-dialog/edit-dossier-dialog.component.ts @@ -55,7 +55,7 @@ export class EditDossierDialogComponent extends BaseDialogComponent implements A section?: Section; }, ) { - super(_injector, _dialogRef); + super(_injector, _dialogRef, true); this.navItems = [ { key: 'dossierInfo', 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 d11543139..d28eed19e 100644 --- a/apps/red-ui/src/app/modules/dossier/dossiers.module.ts +++ b/apps/red-ui/src/app/modules/dossier/dossiers.module.ts @@ -12,6 +12,8 @@ import { SearchScreenComponent } from './screens/search-screen/search-screen.com 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 { HighlightActionDialogComponent } from './screens/file-preview-screen/dialogs/highlight-action-dialog/highlight-action-dialog.component'; +import { ColorPickerModule } from 'ngx-color-picker'; const screens = [SearchScreenComponent]; @@ -22,12 +24,21 @@ const dialogs = [ ResizeAnnotationDialogComponent, ChangeLegalBasisDialogComponent, RecategorizeImageDialogComponent, + HighlightActionDialogComponent, ]; const components = [...screens, ...dialogs]; @NgModule({ declarations: [...components], - 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..01df0b5df 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,51 @@ -
-
- -
- - -
-
- - {{ annotation.comments.length }} -
- -
- -
-
- - + +
+
- -
+
+
+ +
+ + +
+
+ + {{ annotation.comments.length }} +
+ +
+ +
+
+ + +
+ + +
+ (); @Output() readonly deselectAnnotations = new EventEmitter(); + highlightGroups$ = new BehaviorSubject([]); + constructor( readonly multiSelectService: MultiSelectService, - readonly viewModeService: ViewModeService, readonly annotationReferencesService: AnnotationReferencesService, private readonly _filterService: FilterService, - private readonly _state: FilePreviewStateService, private readonly _userPreferenceService: UserPreferenceService, + private readonly _viewModeService: ViewModeService, + private readonly _dialogService: DossiersDialogService, ) {} 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 +88,28 @@ export class AnnotationsListComponent implements OnChanges { return first.x < second.y ? -1 : 1; } } + + showHighlightGroup(idx: number): TextHighlightsGroup { + return this._viewModeService.isTextHighlights && this.highlightGroups$.value.find(h => h.startIdx === idx); + } + + private _updateHighlightGroups(): void { + if (!this.annotations?.length) { + return; + } + const highlightGroups: TextHighlightsGroup[] = []; + let lastGroup: TextHighlightsGroup; + 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 f2c3f2066..b839f8921 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 }}
@@ -160,7 +163,7 @@ 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 49e0d9989..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,7 @@ 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']; @@ -48,8 +49,6 @@ const ALL_HOTKEY_ARRAY = ['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown']; export class FileWorkloadComponent { readonly iconButtonTypes = IconButtonTypes; readonly circleButtonTypes = CircleButtonTypes; - readonly noDataI18NKey = _('file-preview.no-data.title'); - readonly resetFiltersI18NKey = _('file-preview.reset-filters'); displayedAnnotations = new Map(); @Input() selectedAnnotations: AnnotationWrapper[]; @@ -70,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; @@ -81,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, @@ -89,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() @@ -115,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/highlights-separator/highlights-separator.component.html b/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/components/highlights-separator/highlights-separator.component.html new file mode 100644 index 000000000..3ffe33de5 --- /dev/null +++ b/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/components/highlights-separator/highlights-separator.component.html @@ -0,0 +1,25 @@ +
+ + +
+ +
+ + + +
diff --git a/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/components/highlights-separator/highlights-separator.component.scss b/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/components/highlights-separator/highlights-separator.component.scss new file mode 100644 index 000000000..930ad62b5 --- /dev/null +++ b/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/components/highlights-separator/highlights-separator.component.scss @@ -0,0 +1,7 @@ +:host { + display: contents; + + > div { + display: flex; + } +} diff --git a/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/components/highlights-separator/highlights-separator.component.ts b/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/components/highlights-separator/highlights-separator.component.ts new file mode 100644 index 000000000..5ebdab14c --- /dev/null +++ b/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/components/highlights-separator/highlights-separator.component.ts @@ -0,0 +1,41 @@ +import { ChangeDetectionStrategy, Component, Input } from '@angular/core'; +import { CircleButtonTypes } from '@iqser/common-ui'; +import { TextHighlightOperation, TextHighlightsGroup } from '@red/domain'; +import { DossiersDialogService } from '../../../../services/dossiers-dialog.service'; +import { FilePreviewStateService } from '../../services/file-preview-state.service'; +import { AnnotationWrapper } from '@models/file/annotation.wrapper'; + +@Component({ + selector: 'redaction-highlights-separator [highlightGroup] [annotation]', + templateUrl: './highlights-separator.component.html', + styleUrls: ['./highlights-separator.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class HighlightsSeparatorComponent { + @Input() highlightGroup: TextHighlightsGroup; + @Input() annotation: AnnotationWrapper; + + readonly circleButtonTypes = CircleButtonTypes; + readonly isWritable$ = this._state.isWritable$; + + constructor(private readonly _dialogService: DossiersDialogService, private readonly _state: FilePreviewStateService) {} + + convertHighlights(highlightGroup: TextHighlightsGroup): void { + const data = this._getActionData(highlightGroup, TextHighlightOperation.CONVERT); + this._dialogService.openDialog('highlightAction', null, data); + } + + removeHighlights(highlightGroup: TextHighlightsGroup): void { + const data = this._getActionData(highlightGroup, TextHighlightOperation.REMOVE); + this._dialogService.openDialog('highlightAction', null, data); + } + + private _getActionData(highlightGroup: TextHighlightsGroup, operation: TextHighlightOperation) { + return { + dossierId: this._state.dossierId, + fileId: this._state.fileId, + color: highlightGroup.color, + operation, + }; + } +} 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-providers.ts b/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/file-preview-providers.ts index a6de718d8..a1e8ab953 100644 --- a/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/file-preview-providers.ts +++ b/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/file-preview-providers.ts @@ -12,6 +12,7 @@ import { AnnotationReferencesService } from './services/annotation-references.se import { FilterService } from '@iqser/common-ui'; import { ManualAnnotationService } from '../../services/manual-annotation.service'; import { AnnotationProcessingService } from '../../services/annotation-processing.service'; +import { dossiersServiceProvider } from '../../../../services/entity-services/dossiers.service.provider'; export const filePreviewScreenProviders = [ FilterService, @@ -28,4 +29,5 @@ export const filePreviewScreenProviders = [ AnnotationReferencesService, ManualAnnotationService, AnnotationProcessingService, + dossiersServiceProvider, ]; 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 5f6b2ba18..dec3e5aad 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 @@ -34,7 +34,6 @@ import { clearStamps, stampPDFPage } from '@utils/page-stamper'; import { TranslateService } from '@ngx-translate/core'; import { handleFilterDelta } from '@utils/filter-utils'; import { FilesService } from '@services/entity-services/files.service'; -import { ActiveDossiersService } from '@services/entity-services/active-dossiers.service'; import { FileManagementService } from '@services/entity-services/file-management.service'; import { catchError, map, switchMap, tap } from 'rxjs/operators'; import { FilesMapService } from '@services/entity-services/files-map.service'; @@ -50,6 +49,7 @@ import { FilePreviewStateService } from './services/file-preview-state.service'; import { FileDataModel } from '../../../../models/file/file-data.model'; import { filePreviewScreenProviders } from './file-preview-providers'; import { ManualAnnotationService } from '../../services/manual-annotation.service'; +import { DossiersService } from '../../../../services/entity-services/dossiers.service'; import Annotation = Core.Annotations.Annotation; import PDFNet = Core.PDFNet; @@ -104,7 +104,7 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni private readonly _filterService: FilterService, private readonly _translateService: TranslateService, private readonly _filesMapService: FilesMapService, - private readonly _activeDossiersService: ActiveDossiersService, + private readonly _dossiersService: DossiersService, private readonly _reanalysisService: ReanalysisService, private readonly _errorService: ErrorService, private readonly _skippedService: SkippedService, @@ -152,6 +152,16 @@ 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')); @@ -185,6 +195,20 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni this._hide(nonRedactionEntries); 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(); @@ -200,7 +224,7 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni async ngOnAttach(previousRoute: ActivatedRouteSnapshot): Promise { const file = await this.stateService.file; if (!file.canBeOpened) { - return this._router.navigate([this._activeDossiersService.find(this.dossierId)?.routerLink]); + return this._router.navigate([this._dossiersService.find(this.dossierId)?.routerLink]); } this.viewModeService.compareMode = false; this.viewModeService.switchToStandard(); @@ -491,7 +515,7 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni await clearStamps(pdfDoc, pdfNet, allPages); if (this.viewModeService.isRedacted) { - const dossier = this._activeDossiersService.find(this.dossierId); + const dossier = this._dossiersService.find(this.dossierId); if (dossier.watermarkPreviewEnabled) { await this._stampPreview(pdfDoc, dossier.dossierTemplateId); } @@ -548,7 +572,7 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni ) .subscribe(); - this.addActiveScreenSubscription = this._activeDossiersService + this.addActiveScreenSubscription = this._dossiersService .getEntityDeleted$(this.dossierId) .pipe(tap(() => this._handleDeletedDossier())) .subscribe(); @@ -577,7 +601,7 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni private async _loadFileData(file: File): Promise { if (!file || file.isError) { - return this._router.navigate([this._activeDossiersService.find(this.dossierId).routerLink]); + return this._router.navigate([this._dossiersService.find(this.dossierId).routerLink]); } if (file.isUnprocessed) { @@ -705,7 +729,7 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni } } - private _getAnnotations(predicate: (value) => unknown) { + private _getAnnotations(predicate: (value) => boolean) { const annotations = this._instance.Core.annotationManager.getAnnotationsList(); return predicate ? annotations.filter(predicate) : annotations; } @@ -726,7 +750,9 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni private _setAnnotationsColor(annotations: Annotation[], customData: string) { annotations.forEach(annotation => { - annotation['StrokeColor'] = this._annotationDrawService.convertColor(this._instance, annotation.getCustomData(customData)); + const color = this._annotationDrawService.convertColor(this._instance, annotation.getCustomData(customData)); + annotation['StrokeColor'] = color; + annotation['FillColor'] = color; }); } } diff --git a/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/file-preview.module.ts b/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/file-preview.module.ts index 386d9c254..a93b7966c 100644 --- a/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/file-preview.module.ts +++ b/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/file-preview.module.ts @@ -23,6 +23,7 @@ import { AnnotationReferencesListComponent } from './components/annotation-refer import { AcceptRecommendationDialogComponent } from './dialogs/accept-recommendation-dialog/accept-recommendation-dialog.component'; import { AnnotationCardComponent } from './components/annotation-card/annotation-card.component'; import { AnnotationReferencesPageIndicatorComponent } from './components/annotation-references-page-indicator/annotation-references-page-indicator.component'; +import { HighlightsSeparatorComponent } from './components/highlights-separator/highlights-separator.component'; const routes: Routes = [ { @@ -33,26 +34,28 @@ const routes: Routes = [ }, ]; +const components = [ + FileWorkloadComponent, + AnnotationDetailsComponent, + AnnotationsListComponent, + PageIndicatorComponent, + PageExclusionComponent, + PdfViewerComponent, + AnnotationActionsComponent, + CommentsComponent, + DocumentInfoComponent, + TypeAnnotationIconComponent, + ViewSwitchComponent, + UserManagementComponent, + AcceptRecommendationDialogComponent, + AnnotationReferencesListComponent, + AnnotationCardComponent, + AnnotationReferencesPageIndicatorComponent, + HighlightsSeparatorComponent, +]; + @NgModule({ - declarations: [ - FilePreviewScreenComponent, - FileWorkloadComponent, - AnnotationDetailsComponent, - AnnotationsListComponent, - PageIndicatorComponent, - PageExclusionComponent, - PdfViewerComponent, - AnnotationActionsComponent, - CommentsComponent, - DocumentInfoComponent, - TypeAnnotationIconComponent, - ViewSwitchComponent, - UserManagementComponent, - AcceptRecommendationDialogComponent, - AnnotationReferencesListComponent, - AnnotationCardComponent, - AnnotationReferencesPageIndicatorComponent, - ], + declarations: [FilePreviewScreenComponent, ...components], imports: [ RouterModule.forChild(routes), CommonModule, 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 ba68fc852..b8ced6c25 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 @@ -3,7 +3,6 @@ import { Core, WebViewerInstance } from '@pdftron/webviewer'; import { hexToRgb } from '@utils/functions'; import { AnnotationWrapper } from '@models/file/annotation.wrapper'; import { UserPreferenceService } from '@services/user-preference.service'; -import { ActiveDossiersService } from '@services/entity-services/active-dossiers.service'; import { RedactionLogService } from '../../../services/redaction-log.service'; import { environment } from '@environments/environment'; @@ -11,6 +10,7 @@ import { IRectangle, ISectionGrid, ISectionRectangle } from '@red/domain'; import { SkippedService } from './skipped.service'; import { firstValueFrom } from 'rxjs'; import { DictionariesMapService } from '@services/entity-services/dictionaries-map.service'; +import { DossiersService } from '../../../../../services/entity-services/dossiers.service'; import Annotation = Core.Annotations.Annotation; @Injectable() @@ -21,7 +21,7 @@ export class AnnotationDrawService { constructor( private readonly _dictionariesMapService: DictionariesMapService, - private readonly _activeDossiersService: ActiveDossiersService, + private readonly _dossiersService: DossiersService, private readonly _redactionLogService: RedactionLogService, private readonly _userPreferenceService: UserPreferenceService, private readonly _skippedService: SkippedService, @@ -128,7 +128,7 @@ export class AnnotationDrawService { } private _computeSection(activeViewer: WebViewerInstance, pageNumber: number, sectionRectangle: ISectionRectangle, dossierId: string) { - const dossierTemplateId = this._activeDossiersService.find(dossierId).dossierTemplateId; + const dossierTemplateId = this._dossiersService.find(dossierId).dossierTemplateId; const rectangleAnnot = new activeViewer.Core.Annotations.RectangleAnnotation(); const pageHeight = activeViewer.Core.documentViewer.getPageHeight(pageNumber); const rectangle: IRectangle = { @@ -156,66 +156,84 @@ export class AnnotationDrawService { compareMode: boolean, ) { const pageNumber = compareMode ? annotationWrapper.pageNumber * 2 - 1 : annotationWrapper.pageNumber; - const dossierTemplateId = this._activeDossiersService.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('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.isRedacted)); - 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 2f0f150b4..9b930ab89 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'; import { ConfirmArchiveDossierDialogComponent } from '../dialogs/confirm-archive-dossier-dialog/confirm-archive-dossier-dialog.component'; type DialogType = @@ -25,7 +26,8 @@ type DialogType = | 'removeAnnotations' | 'resizeAnnotation' | 'forceAnnotation' - | 'manualAnnotation'; + | 'manualAnnotation' + | 'highlightAction'; @Injectable() export class DossiersDialogService extends DialogService { @@ -72,6 +74,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/icons/icons.module.ts b/apps/red-ui/src/app/modules/icons/icons.module.ts index 06fa9e338..f85071c77 100644 --- a/apps/red-ui/src/app/modules/icons/icons.module.ts +++ b/apps/red-ui/src/app/modules/icons/icons.module.ts @@ -23,6 +23,7 @@ export class IconsModule { 'color-picker', 'comment', 'comment-fill', + 'convert', 'csv', 'dictionary', 'denied', 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/services/entity-services/dossier-stats.service.ts b/apps/red-ui/src/app/services/entity-services/dossier-stats.service.ts index f49e77cc0..52085cd92 100644 --- a/apps/red-ui/src/app/services/entity-services/dossier-stats.service.ts +++ b/apps/red-ui/src/app/services/entity-services/dossier-stats.service.ts @@ -2,12 +2,23 @@ import { Injectable, Injector } from '@angular/core'; import { StatsService } from '@iqser/common-ui'; import { DossierStats, IDossierStats } from '@red/domain'; import { DOSSIER_ID } from '@utils/constants'; +import { Observable, of } from 'rxjs'; +import { UserService } from '@services/user.service'; @Injectable({ providedIn: 'root', }) export class DossierStatsService extends StatsService { - constructor(protected readonly _injector: Injector) { + constructor(protected readonly _injector: Injector, private readonly _userService: UserService) { super(_injector, DOSSIER_ID, DossierStats, 'dossier-stats'); } + + getFor(ids: string[]): Observable { + const isUserAdminOnly = this._userService.currentUser.roles.length === 1 && this._userService.currentUser.isUserAdmin; + if (isUserAdminOnly) { + return of([]); + } + + return super.getFor(ids); + } } diff --git a/apps/red-ui/src/app/services/permissions.service.ts b/apps/red-ui/src/app/services/permissions.service.ts index 75c9f0798..716dd44d5 100644 --- a/apps/red-ui/src/app/services/permissions.service.ts +++ b/apps/red-ui/src/app/services/permissions.service.ts @@ -4,6 +4,7 @@ import { Dossier, File, IComment, IDossier } from '@red/domain'; import { DossiersService } from '@services/entity-services/dossiers.service'; import { ActivatedRoute } from '@angular/router'; import { dossiersServiceResolver } from '@services/entity-services/dossiers.service.provider'; +import { FilesMapService } from '@services/entity-services/files-map.service'; @Injectable({ providedIn: 'root' }) export class PermissionsService { @@ -11,6 +12,7 @@ export class PermissionsService { private readonly _userService: UserService, private readonly _route: ActivatedRoute, private readonly _injector: Injector, + private readonly _filesMapService: FilesMapService, ) {} private get _dossiersService(): DossiersService { @@ -23,7 +25,7 @@ export class PermissionsService { } displayReanalyseBtn(dossier: Dossier): boolean { - return dossier.isActive && this.isApprover(dossier); + return dossier.isActive && this.isApprover(dossier) && this._filesMapService.get(dossier.dossierId).length > 0; } canUploadFiles(dossier: Dossier): boolean { diff --git a/apps/red-ui/src/app/services/user.service.ts b/apps/red-ui/src/app/services/user.service.ts index aca9a8b42..c610856bc 100644 --- a/apps/red-ui/src/app/services/user.service.ts +++ b/apps/red-ui/src/app/services/user.service.ts @@ -37,6 +37,10 @@ export class UserService extends EntitiesService { } async initialize(): Promise { + if (!this.currentUser) { + return; + } + if (this.currentUser.isUserAdmin || this.currentUser.isUser || this.currentUser.isAdmin) { await firstValueFrom(this.loadAll()); } 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/help-mode/links.json b/apps/red-ui/src/assets/help-mode/links.json index 5062488e9..7dacb0fa5 100644 --- a/apps/red-ui/src/assets/help-mode/links.json +++ b/apps/red-ui/src/assets/help-mode/links.json @@ -221,13 +221,25 @@ "it": "", "fr": "" }, - "dossiers_scroll_up_and_down": { + "dossiers_scroll_up_button": { "en": "/en/index-en.html?contextId=dossiers_scroll_up_and_down", "de": "", "it": "", "fr": "" }, - "documents_scroll_up_and_down": { + "dossiers_scroll_down_button": { + "en": "/en/index-en.html?contextId=dossiers_scroll_up_and_down", + "de": "", + "it": "", + "fr": "" + }, + "documents_scroll_up_button": { + "en": "/en/index-en.html?contextId=documents_scroll_up_and_down", + "de": "", + "it": "", + "fr": "" + }, + "documents_scroll_down_button": { "en": "/en/index-en.html?contextId=documents_scroll_up_and_down", "de": "", "it": "", diff --git a/apps/red-ui/src/assets/i18n/en.json b/apps/red-ui/src/assets/i18n/en.json index 1cce3154e..6048b7695 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", "archived-dossiers-listing": { @@ -410,6 +411,7 @@ }, "header": "Edit Redaction Reason" }, + "color": "Color", "comments": { "add-comment": "Enter comment", "comments": "{count} {count, plural, one{comment} other{comments}}", @@ -1240,6 +1242,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." @@ -1288,8 +1294,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", @@ -1397,6 +1408,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", @@ -1732,6 +1767,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/icons/general/convert.svg b/apps/red-ui/src/assets/icons/general/convert.svg new file mode 100644 index 000000000..dcdeceaa2 --- /dev/null +++ b/apps/red-ui/src/assets/icons/general/convert.svg @@ -0,0 +1,9 @@ + + + + + + + + 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/red-domain/src/index.ts b/libs/red-domain/src/index.ts index 7349bef8b..8220a575e 100644 --- a/libs/red-domain/src/index.ts +++ b/libs/red-domain/src/index.ts @@ -21,3 +21,4 @@ export * from './lib/legal-basis'; export * from './lib/dossier-stats'; export * from './lib/dossier-state'; export * from './lib/trash-dossier'; +export * from './lib/text-highlight'; diff --git a/libs/red-domain/src/lib/files/file.model.ts b/libs/red-domain/src/lib/files/file.model.ts index 56e4c1458..b9cf2cd36 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, IRouterPath { 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, IRouterPath { 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; } 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..7a08fee0d --- /dev/null +++ b/libs/red-domain/src/lib/text-highlight/imported-redaction.ts @@ -0,0 +1,6 @@ +import { IRectangle } from '../geometry'; + +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..f49b5580a --- /dev/null +++ b/libs/red-domain/src/lib/text-highlight/index.ts @@ -0,0 +1,5 @@ +export * from './imported-redaction'; +export * from './text-highlight-operation'; +export * from './text-highlight.response'; +export * from './text-highlight.request'; +export * from './text-highlights-group'; 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[] }; +} diff --git a/libs/red-domain/src/lib/text-highlight/text-highlights-group.ts b/libs/red-domain/src/lib/text-highlight/text-highlights-group.ts new file mode 100644 index 000000000..30755ce59 --- /dev/null +++ b/libs/red-domain/src/lib/text-highlight/text-highlights-group.ts @@ -0,0 +1,5 @@ +export interface TextHighlightsGroup { + startIdx: number; + color: string; + length: number; +} diff --git a/package.json b/package.json index 81e9aee99..357bd0be1 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "redaction", - "version": "3.261.0", + "version": "3.270.0", "private": true, "license": "MIT", "scripts": { diff --git a/paligo-theme.tar.gz b/paligo-theme.tar.gz index 4541f9ed7..a0399a8f7 100644 Binary files a/paligo-theme.tar.gz and b/paligo-theme.tar.gz differ