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 7c2876e8f..51a75e509 100644 --- a/apps/red-ui/src/app/models/file/annotation.wrapper.ts +++ b/apps/red-ui/src/app/models/file/annotation.wrapper.ts @@ -58,6 +58,12 @@ export class AnnotationWrapper { changeLogType?: 'ADDED' | 'REMOVED' | 'CHANGED'; engines?: string[]; + hasBeenResized: boolean; + hasBeenRecategorized: boolean; + hasLegalBasisChanged: boolean; + hasBeenForced: boolean; + hasBeenRemovedByManualOverride: boolean; + private _origin: RedactionLogEntryWrapper; get isChangeLogRemoved() { @@ -169,6 +175,16 @@ export class AnnotationWrapper { return this.dictionaryOperation; } + get hasRedactionChanges(): boolean { + return ( + this.hasBeenResized || + this.hasBeenRecategorized || + this.hasLegalBasisChanged || + this.hasBeenForced || + this.hasBeenRemovedByManualOverride + ); + } + get isConvertedRecommendation() { return this.isRecommendation && this.superType === 'suggestion-add-dictionary'; } @@ -221,6 +237,11 @@ export class AnnotationWrapper { annotationWrapper.engines = redactionLogEntry.engines; annotationWrapper.section = redactionLogEntry.section; annotationWrapper.rectangle = redactionLogEntry.rectangle; + annotationWrapper.hasBeenResized = redactionLogEntry.hasBeenResized; + annotationWrapper.hasBeenRecategorized = redactionLogEntry.hasBeenRecategorized; + annotationWrapper.hasLegalBasisChanged = redactionLogEntry.hasLegalBasisChanged; + annotationWrapper.hasBeenForced = redactionLogEntry.hasBeenForced; + annotationWrapper.hasBeenRemovedByManualOverride = redactionLogEntry.hasBeenRemovedByManualOverride; this._createContent(annotationWrapper, redactionLogEntry); this._setSuperType(annotationWrapper, redactionLogEntry); diff --git a/apps/red-ui/src/app/models/file/redaction-log-entry.wrapper.ts b/apps/red-ui/src/app/models/file/redaction-log-entry.wrapper.ts index 9dc800a87..f3bcacbd8 100644 --- a/apps/red-ui/src/app/models/file/redaction-log-entry.wrapper.ts +++ b/apps/red-ui/src/app/models/file/redaction-log-entry.wrapper.ts @@ -46,4 +46,10 @@ export interface RedactionLogEntryWrapper { recategorizationType?: string; legalBasisChangeValue?: string; engines?: string[]; + + hasBeenResized?: boolean; + hasBeenRecategorized?: boolean; + hasLegalBasisChanged?: boolean; + hasBeenForced?: boolean; + hasBeenRemovedByManualOverride?: boolean; } diff --git a/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/components/annotation-source/annotation-source.component.html b/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/components/annotation-details/annotation-details.component.html similarity index 69% rename from apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/components/annotation-source/annotation-source.component.html rename to apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/components/annotation-details/annotation-details.component.html index 766e23aa6..01bdc134e 100644 --- a/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/components/annotation-source/annotation-source.component.html +++ b/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/components/annotation-details/annotation-details.component.html @@ -1,15 +1,19 @@ +
+ +
+ -
+
diff --git a/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/components/annotation-source/annotation-source.component.scss b/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/components/annotation-details/annotation-details.component.scss similarity index 85% rename from apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/components/annotation-source/annotation-source.component.scss rename to apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/components/annotation-details/annotation-details.component.scss index c731a1878..d8356a43f 100644 --- a/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/components/annotation-source/annotation-source.component.scss +++ b/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/components/annotation-details/annotation-details.component.scss @@ -1,5 +1,12 @@ @use '../../../../../../../assets/styles/variables'; +:host { + display: flex; + position: absolute; + top: 6px; + right: 8px; +} + .popover { width: 260px; padding: 10px; @@ -44,6 +51,10 @@ margin-right: 8px; } } + + &:not(:last-child) { + margin-right: 2px; + } } mat-icon { diff --git a/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/components/annotation-details/annotation-details.component.ts b/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/components/annotation-details/annotation-details.component.ts new file mode 100644 index 000000000..745b6212a --- /dev/null +++ b/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/components/annotation-details/annotation-details.component.ts @@ -0,0 +1,89 @@ +import { ChangeDetectionStrategy, Component, Input, OnChanges, SimpleChanges } from '@angular/core'; +import { AnnotationWrapper } from '@models/file/annotation.wrapper'; +import { TranslateService } from '@ngx-translate/core'; +import { annotationChangesTranslations } from '../../../../../../translations/annotation-changes-translations'; +import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker'; + +interface Engine { + readonly icon: string; + readonly description: string; + readonly show: boolean; +} + +const Engines = { + DICTIONARY: 'DICTIONARY', + NER: 'NER', + RULE: 'RULE', +} as const; + +type EngineName = keyof typeof Engines; + +@Component({ + selector: 'redaction-annotation-details', + templateUrl: './annotation-details.component.html', + styleUrls: ['./annotation-details.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class AnnotationDetailsComponent implements OnChanges { + @Input() annotation: AnnotationWrapper; + + isPopoverOpen = false; + hasEnginesToShow: boolean; + engines: readonly Engine[]; + + hasChangesToShow: boolean; + changesTooltip: string; + + constructor(private readonly _translateService: TranslateService) {} + + ngOnChanges(changes: SimpleChanges) { + if (changes.annotation) { + this._updateChanges(); + this._updateEngines(); + } + } + + private _updateChanges(): void { + const changesProperties = [ + 'hasBeenResized', + 'hasBeenRecategorized', + 'hasLegalBasisChanged', + 'hasBeenForced', + 'hasBeenRemovedByManualOverride', + ]; + const changes = changesProperties.filter(key => this.annotation[key]); + const header = this._translateService.instant(_('annotation-changes.header')); + const details = changes + .map(change => this._translateService.instant(annotationChangesTranslations[change])) + .map(change => `• ${change}`); + this.changesTooltip = [header, ...details].join('\n'); + + this.hasChangesToShow = changes.length > 0; + } + + private _updateEngines(): void { + this.engines = [ + { + icon: 'red:dictionary', + description: this._translateService.instant('annotation-engines.dictionary', { isHint: this.annotation.hint }), + show: this._isBasedOn(Engines.DICTIONARY), + }, + { + icon: 'red:ai', + description: this._translateService.instant('annotation-engines.ner'), + show: this._isBasedOn(Engines.NER), + }, + { + icon: 'red:rule', + description: this._translateService.instant('annotation-engines.rule', { rule: this.annotation.legalBasisValue }), + show: this._isBasedOn(Engines.RULE), + }, + ]; + + this.hasEnginesToShow = this.engines.length && this.engines.some(source => source.show); + } + + private _isBasedOn(engineName: EngineName) { + return !!this.annotation.engines?.includes(engineName); + } +} diff --git a/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/components/annotation-source/annotation-source.component.ts b/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/components/annotation-source/annotation-source.component.ts deleted file mode 100644 index 434b1dd03..000000000 --- a/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/components/annotation-source/annotation-source.component.ts +++ /dev/null @@ -1,63 +0,0 @@ -import { ChangeDetectionStrategy, Component, Input } from '@angular/core'; -import { AnnotationWrapper } from '@models/file/annotation.wrapper'; -import { OnChange } from '@iqser/common-ui'; -import { TranslateService } from '@ngx-translate/core'; - -interface Engine { - readonly icon: string; - readonly description: string; - readonly show: boolean; -} - -const Engines = { - DICTIONARY: 'DICTIONARY', - NER: 'NER', - RULE: 'RULE', -} as const; - -type EngineName = keyof typeof Engines; - -@Component({ - selector: 'redaction-annotation-source', - templateUrl: './annotation-source.component.html', - styleUrls: ['./annotation-source.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush, -}) -export class AnnotationSourceComponent { - @Input() - @OnChange('updateEngines') - annotation: AnnotationWrapper; - - isPopoverOpen = false; - engines: readonly Engine[]; - - constructor(private readonly _translateService: TranslateService) {} - - get hasEnginesToShow(): boolean { - return this.engines.length && this.engines.some(source => source.show); - } - - updateEngines(): void { - this.engines = [ - { - icon: 'red:dictionary', - description: this._translateService.instant('annotation-engines.dictionary', { isHint: this.annotation.hint }), - show: this._isBasedOn(Engines.DICTIONARY), - }, - { - icon: 'red:ai', - description: this._translateService.instant('annotation-engines.ner'), - show: this._isBasedOn(Engines.NER), - }, - { - icon: 'red:rule', - description: this._translateService.instant('annotation-engines.rule', { rule: this.annotation.legalBasisValue }), - show: this._isBasedOn(Engines.RULE), - }, - ]; - } - - private _isBasedOn(engineName: EngineName) { - return !!this.annotation.engines?.includes(engineName); - } -} 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 b4a0acb70..368df6874 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 @@ -4,8 +4,8 @@ [attr.annotation-id]="annotation.id" [attr.annotation-page]="activeViewerPage" [class.active]="isSelected(annotation.annotationId)" - [class.multi-select-active]="multiSelectService.active$ | async" [class.help-mode]="helpModeService.isHelpModeActive$ | async" + [class.multi-select-active]="multiSelectService.active$ | async" class="annotation-wrapper" >
@@ -59,5 +59,5 @@
- +
diff --git a/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/components/annotations-list/annotations-list.component.scss b/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/components/annotations-list/annotations-list.component.scss index c9df84618..e159e24af 100644 --- a/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/components/annotations-list/annotations-list.component.scss +++ b/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/components/annotations-list/annotations-list.component.scss @@ -90,9 +90,3 @@ } } } - -redaction-annotation-source { - position: absolute; - top: 6px; - right: 8px; -} 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 8a063381a..7111f4d66 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 @@ -19,6 +19,7 @@ iqserHelpMode="bulk-select-annotations" translate="file-preview.tabs.annotations.select" > + @@ -118,99 +119,103 @@
- -
- - +
+ + - - {{ activeViewerPage }} - - {{ activeAnnotations?.length || 0 }} - - + + {{ activeViewerPage }} - + {{ activeAnnotations?.length || 0 }} + + -
-
-
+
+
+
+
+
+ +
+ + + + {{ 'file-preview.tabs.annotations.page-is' | translate }} + . + + + +
+ + +
-
+ -
- - - - {{ 'file-preview.tabs.annotations.page-is' | 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 64f58538a..78609b428 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 @@ -151,3 +151,7 @@ ::ng-deep .page-separator iqser-circle-button mat-icon { color: var(--iqser-primary); } + +[hidden] { + display: none !important; +} 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 999da819d..0607b4055 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 @@ -21,7 +21,6 @@ import { IconButtonTypes, INestedFilter, IqserEventTarget, - Required, shareDistinctLast, shareLast, } from '@iqser/common-ui'; @@ -38,7 +37,7 @@ const COMMAND_KEY_ARRAY = ['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown', 'E const ALL_HOTKEY_ARRAY = ['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown']; @Component({ - selector: 'redaction-file-workload', + selector: 'redaction-file-workload [file]', templateUrl: './file-workload.component.html', styleUrls: ['./file-workload.component.scss'], changeDetection: ChangeDetectionStrategy.OnPush, @@ -53,7 +52,7 @@ export class FileWorkloadComponent { @Input() shouldDeselectAnnotationsOnPageChange: boolean; @Input() dialogRef: MatDialogRef; @Input() viewedPages: IViewedPage[]; - @Input() @Required() file!: File; + @Input() file!: File; @Input() hideSkipped: boolean; @Input() annotationActionsTemplate: TemplateRef; @Input() viewer: WebViewerInstance; @@ -161,7 +160,7 @@ export class FileWorkloadComponent { } } - hasOnlyManualRedactionsAndNotExcluded(pageNumber: number): boolean { + hasOnlyManualRedactionsAndIsExcluded(pageNumber: number): boolean { const hasOnlyManualRedactions = this.displayedAnnotations.get(pageNumber).every(annotation => annotation.manual); return hasOnlyManualRedactions && this.file.excludedPages.includes(pageNumber); } diff --git a/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/components/page-exclusion/page-exclusion.component.ts b/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/components/page-exclusion/page-exclusion.component.ts index 58dc2bc0c..ba2eb3b72 100644 --- a/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/components/page-exclusion/page-exclusion.component.ts +++ b/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/components/page-exclusion/page-exclusion.component.ts @@ -67,7 +67,6 @@ export class PageExclusionComponent implements OnChanges { ) .toPromise(); this._inputComponent.reset(); - this.excludedPagesService.toggle(); } catch (e) { this._toaster.error(_('file-preview.tabs.exclude-pages.error')); } @@ -86,7 +85,6 @@ export class PageExclusionComponent implements OnChanges { ) .toPromise(); this._inputComponent.reset(); - this.excludedPagesService.toggle(); this._loadingService.stop(); } } diff --git a/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/components/page-indicator/page-indicator.component.ts b/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/components/page-indicator/page-indicator.component.ts index 2ae2b8f06..dc417f73d 100644 --- a/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/components/page-indicator/page-indicator.component.ts +++ b/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/components/page-indicator/page-indicator.component.ts @@ -14,12 +14,8 @@ import { FilesMapService } from '@services/entity-services/files-map.service'; changeDetection: ChangeDetectionStrategy.OnPush, }) export class PageIndicatorComponent extends AutoUnsubscribe implements OnDestroy, OnChanges { - @Input() - file: File; - - @Input() - active = false; - + @Input() file: File; + @Input() active = false; @Input() showDottedIcon = false; @Input() number: number; @Input() viewedPages: IViewedPage[]; @@ -84,23 +80,6 @@ export class PageIndicatorComponent extends AutoUnsubscribe implements OnDestroy } } - // @HostListener('window:keydown', ['$event']) - // handleKeyDown(event: KeyboardEvent) { - // if (this.canMarkPagesAsViewed) { - // if (this.active && event.key === ' ') { - // if ( - // document.activeElement && - // document.activeElement.className.indexOf('activePanel') >= 0 - // ) { - // this.toggleReadState(); - // event.stopPropagation(); - // event.preventDefault(); - // return false; - // } - // } - // } - // } - private async _markPageRead() { await this._viewedPagesService.addPage({ page: this.number }, this.file.dossierId, this.file.fileId).toPromise(); if (this.activePage) { diff --git a/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/components/pdf-viewer/pdf-viewer.component.ts b/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/components/pdf-viewer/pdf-viewer.component.ts index daa248d77..059d44dc7 100644 --- a/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/components/pdf-viewer/pdf-viewer.component.ts +++ b/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/components/pdf-viewer/pdf-viewer.component.ts @@ -284,7 +284,7 @@ export class PdfViewerComponent implements OnInit, OnChanges { this._selectedText = selectedText; const textActions = [dataElements.ADD_DICTIONARY, dataElements.ADD_FALSE_POSITIVE]; - if (selectedText.length > 2 && this.canPerformActions && !this.utils.isCurrentPageExcluded) { + if (selectedText.length > 2 && this.canPerformActions && !this.utils.isCurrentPageExcluded(this.file)) { this.instance.UI.enableElements(textActions); } else { this.instance.UI.disableElements(textActions); @@ -588,7 +588,7 @@ export class PdfViewerComponent implements OnInit, OnChanges { ANNOTATION_POPUP, ]; - if (this.canPerformActions && !this.utils.isCurrentPageExcluded) { + if (this.canPerformActions && !this.utils.isCurrentPageExcluded(this.file)) { try { this.instance.UI.enableTools(['AnnotationCreateRectangle']); } catch (e) { @@ -605,7 +605,7 @@ export class PdfViewerComponent implements OnInit, OnChanges { let elementsToDisable = [...elements, ADD_RECTANGLE]; - if (this.utils.isCurrentPageExcluded) { + if (this.utils.isCurrentPageExcluded(this.file)) { const allowedActionsWhenPageExcluded: string[] = [ANNOTATION_POPUP, ADD_RECTANGLE, ADD_REDACTION, SHAPE_TOOL_GROUP_BUTTON]; elementsToDisable = elementsToDisable.filter(element => !allowedActionsWhenPageExcluded.includes(element)); } else { 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 ec3849327..16976197e 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 @@ -330,7 +330,7 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni .map(p => p.page) .filter((item, pos, self) => self.indexOf(item) === pos); for (const page of distinctPages) { - await this._cleanupAndRedrawManualAnnotationsForEntirePage(page); + await this._reloadAnnotationsForPage(page); } } }, @@ -398,7 +398,7 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni this._instance = $event; this.ready = true; await this._stampPDF(); - await this._cleanupAndRedrawManualAnnotations(); + await this._reloadAnnotations(); this._setExcludedPageStyles(); this._instance.Core.documentViewer.addEventListener('pageComplete', () => { @@ -419,7 +419,7 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni } async annotationsChangedByReviewAction(annotation: AnnotationWrapper) { - await this._cleanupAndRedrawManualAnnotationsForEntirePage(annotation?.pageNumber || this.activeViewerPage); + await this._reloadAnnotationsForPage(annotation?.pageNumber || this.activeViewerPage); } ocredFile(): void { @@ -548,27 +548,32 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni await this._loadFileData(file, !this._reloadFileOnReanalysis); this._reloadFileOnReanalysis = false; this._loadingService.stop(); - await this._cleanupAndRedrawManualAnnotations(); + await this._reloadAnnotations(); }); } - private async _loadFileData(file: File, performUpdate = false): Promise { + private async _loadFileData(file: File, performUpdate = false): Promise { + if (!file || file.isError) { + return this._router.navigate([this._dossiersService.find(this.dossierId).routerLink]); + } + const fileData = await this._fileDownloadService.loadDataFor(file).toPromise(); - if (!file?.isPending && !file?.isError) { + if (!file.isPending) { + let excludedOrIncludedPages = new Set(); + let currentPageAnnotations: AnnotationWrapper[] = []; + if (performUpdate && !!this.fileData) { this.fileData.redactionLog = fileData.redactionLog; this.fileData.viewedPages = fileData.viewedPages; + excludedOrIncludedPages = new Set([...this.fileData.file.excludedPages, ...file.excludedPages]); + currentPageAnnotations = this.annotations.filter(a => excludedOrIncludedPages.has(a.pageNumber)); + this.fileData.file = file; } else { this.fileData = fileData; } - this.rebuildFilters(); - return; - } - - if (file.isError) { - await this._router.navigate([this._dossiersService.find(this.dossierId).routerLink]); + return this._cleanupAndRedrawAnnotations(currentPageAnnotations, a => excludedOrIncludedPages.has(a.pageNumber)); } } @@ -578,36 +583,32 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni this._workloadComponent?.scrollAnnotations(); } - private async _cleanupAndRedrawManualAnnotations() { - const currentAnnotations = this.annotations; + private async _reloadAnnotations() { this.fileData.redactionLog = await this._fileDownloadService.loadRedactionLogFor(this.dossierId, this.fileId).toPromise(); - this.rebuildFilters(); - - if (this.viewModeService.viewMode === 'STANDARD') { - currentAnnotations.forEach(annotation => { - this._findAndDeleteAnnotation(annotation.id); - }); - const newAnnotations = this.annotations; - this._handleDeltaAnnotationFilters(currentAnnotations, newAnnotations); - await this._redrawAnnotations(newAnnotations); - } + await this._cleanupAndRedrawAnnotations(this.annotations); } - private async _cleanupAndRedrawManualAnnotationsForEntirePage(page: number) { + private async _reloadAnnotationsForPage(page: number) { const currentPageAnnotations = this.annotations.filter(a => a.pageNumber === page); - const currentPageAnnotationIds = currentPageAnnotations.map(a => a.id); await this._filesService.reload(this.dossierId, this.fileId).toPromise(); this.fileData.redactionLog = await this._fileDownloadService.loadRedactionLogFor(this.dossierId, this.fileId).toPromise(); + await this._cleanupAndRedrawAnnotations(currentPageAnnotations, annotation => annotation.pageNumber === page); + } + + private async _cleanupAndRedrawAnnotations( + annotationsToDelete: AnnotationWrapper[], + newAnnotationsFilter?: (annotation: AnnotationWrapper) => boolean, + ) { this.rebuildFilters(); if (this.viewModeService.viewMode === 'STANDARD') { - currentPageAnnotationIds.forEach(id => { - this._findAndDeleteAnnotation(id); + annotationsToDelete?.forEach(annotation => { + this._findAndDeleteAnnotation(annotation.id); }); - const newPageAnnotations = this.annotations.filter(item => item.pageNumber === page); - this._handleDeltaAnnotationFilters(currentPageAnnotations, newPageAnnotations); + const newPageAnnotations = newAnnotationsFilter ? this.annotations.filter(newAnnotationsFilter) : this.annotations; + this._handleDeltaAnnotationFilters(annotationsToDelete ?? [], newPageAnnotations); await this._redrawAnnotations(newPageAnnotations); } } 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 1943e4aae..6e9c902ae 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 @@ -7,7 +7,7 @@ import { SharedModule } from '@shared/shared.module'; import { SharedDossiersModule } from '../../shared/shared-dossiers.module'; import { FilePreviewScreenComponent } from './file-preview-screen.component'; import { FileWorkloadComponent } from './components/file-workload/file-workload.component'; -import { AnnotationSourceComponent } from './components/annotation-source/annotation-source.component'; +import { AnnotationDetailsComponent } from './components/annotation-details/annotation-details.component'; import { AnnotationsListComponent } from './components/annotations-list/annotations-list.component'; import { PageIndicatorComponent } from './components/page-indicator/page-indicator.component'; import { PageExclusionComponent } from './components/page-exclusion/page-exclusion.component'; @@ -33,7 +33,7 @@ const routes: Routes = [ declarations: [ FilePreviewScreenComponent, FileWorkloadComponent, - AnnotationSourceComponent, + AnnotationDetailsComponent, AnnotationsListComponent, PageIndicatorComponent, PageExclusionComponent, 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 4c4ac1f28..a8c467bd8 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 @@ -19,12 +19,12 @@ export class AnnotationProcessingService { checker: (annotation: AnnotationWrapper) => annotation?.comments?.length > 0, }, { - id: 'with-reason-changes', - icon: 'red:reason', - label: _('filter-menu.with-reason-changes'), + id: 'redaction-changes', + icon: 'red:redaction-changes', + label: _('filter-menu.redaction-changes'), checked: false, topLevelFilter: true, - checker: (annotation: AnnotationWrapper) => annotation?.legalBasisChangeValue?.length > 0, + checker: (annotation: AnnotationWrapper) => annotation?.hasRedactionChanges, }, { id: 'unseen-pages', diff --git a/apps/red-ui/src/app/modules/dossier/utils/pdf-viewer.utils.ts b/apps/red-ui/src/app/modules/dossier/utils/pdf-viewer.utils.ts index 42bcbf6d6..5b29b9c07 100644 --- a/apps/red-ui/src/app/modules/dossier/utils/pdf-viewer.utils.ts +++ b/apps/red-ui/src/app/modules/dossier/utils/pdf-viewer.utils.ts @@ -2,6 +2,7 @@ import { translateQuads } from '@utils/pdf-coordinates'; import { AnnotationWrapper } from '@models/file/annotation.wrapper'; import { Core, WebViewerInstance } from '@pdftron/webviewer'; import { ViewModeService } from '../screens/file-preview-screen/services/view-mode.service'; +import { File } from '@red/domain'; import Annotation = Core.Annotations.Annotation; const DISABLED_HOTKEYS = [ @@ -39,7 +40,6 @@ const DISABLED_HOTKEYS = [ export class PdfViewerUtils { ready = false; - excludedPages: number[] = []; constructor(readonly instance: WebViewerInstance, readonly viewModeService: ViewModeService) {} @@ -47,11 +47,6 @@ export class PdfViewerUtils { return this.viewModeService.isCompare ? 2 : 1; } - get isCurrentPageExcluded() { - const currentPage = this.currentPage; - return !!this.excludedPages?.includes(currentPage); - } - get currentPage() { try { return this.viewModeService.isCompare ? Math.ceil(this._currentInternalPage / 2) : this._currentInternalPage; @@ -88,6 +83,11 @@ export class PdfViewerUtils { return this.instance?.Core.documentViewer?.getPageCount(); } + isCurrentPageExcluded(file: File) { + const currentPage = this.currentPage; + return !!file?.excludedPages?.includes(currentPage); + } + navigateToPage(pageNumber: string | number) { const parsedNumber = typeof pageNumber === 'string' ? parseInt(pageNumber, 10) : pageNumber; this._navigateToPage(this.paginationOffset === 2 ? parsedNumber * this.paginationOffset - 1 : parsedNumber); 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 2448bfd6a..0945fd73e 100644 --- a/apps/red-ui/src/app/modules/icons/icons.module.ts +++ b/apps/red-ui/src/app/modules/icons/icons.module.ts @@ -50,6 +50,7 @@ export class IconsModule { 'ready-for-approval', 'reanalyse', 'reason', + 'redaction-changes', 'remove-from-dict', 'report', 'resize', diff --git a/apps/red-ui/src/app/services/entity-services/files.service.ts b/apps/red-ui/src/app/services/entity-services/files.service.ts index 485854529..258c93870 100644 --- a/apps/red-ui/src/app/services/entity-services/files.service.ts +++ b/apps/red-ui/src/app/services/entity-services/files.service.ts @@ -42,9 +42,9 @@ export class FilesService extends EntitiesService { } @Validate() - setUnderApprovalFor(@RequiredParam() fileIds: List, @RequiredParam() dossierId: string, asigneeId: string) { + setUnderApprovalFor(@RequiredParam() fileIds: List, @RequiredParam() dossierId: string, assigneeId: string) { const url = `${this._defaultModelPath}/under-approval/${dossierId}/bulk`; - return this._post(fileIds, url, [{ key: 'assigneeId', value: asigneeId }]).pipe(switchMap(() => this.loadAll(dossierId))); + return this._post(fileIds, url, [{ key: 'assigneeId', value: assigneeId }]).pipe(switchMap(() => this.loadAll(dossierId))); } /** diff --git a/apps/red-ui/src/app/services/user-preference.service.ts b/apps/red-ui/src/app/services/user-preference.service.ts index e2ac56f9e..ac318c13d 100644 --- a/apps/red-ui/src/app/services/user-preference.service.ts +++ b/apps/red-ui/src/app/services/user-preference.service.ts @@ -68,8 +68,8 @@ export class UserPreferenceService extends GenericService { window.location.reload(); } - reload(): void { - this.getAll() + reload(): Promise { + return this.getAll() .toPromise() .then(attributes => { this._userAttributes = attributes ?? {}; diff --git a/apps/red-ui/src/app/translations/annotation-changes-translations.ts b/apps/red-ui/src/app/translations/annotation-changes-translations.ts new file mode 100644 index 000000000..7bd481fa1 --- /dev/null +++ b/apps/red-ui/src/app/translations/annotation-changes-translations.ts @@ -0,0 +1,11 @@ +import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker'; +import { AnnotationWrapper } from '../models/file/annotation.wrapper'; +import { KeysOf } from '@iqser/common-ui'; + +export const annotationChangesTranslations: { [key in KeysOf]?: string } = { + hasBeenResized: _('annotation-changes.resized'), + hasBeenRecategorized: _('annotation-changes.recategorized'), + hasLegalBasisChanged: _('annotation-changes.legal-basis'), + hasBeenForced: _('annotation-changes.forced'), + hasBeenRemovedByManualOverride: _('annotation-changes.removed-manual'), +} as const; diff --git a/apps/red-ui/src/app/translations/notifications-translations.ts b/apps/red-ui/src/app/translations/notifications-translations.ts index adf4ca5bb..aa36fcc86 100644 --- a/apps/red-ui/src/app/translations/notifications-translations.ts +++ b/apps/red-ui/src/app/translations/notifications-translations.ts @@ -13,4 +13,5 @@ export const notificationsTranslations: { [key in NotificationType]: string } = [NotificationTypes.USER_DEGRADED_TO_REVIEWER]: _('notification.user-demoted-to-reviewer'), [NotificationTypes.USER_PROMOTED_TO_APPROVER]: _('notification.user-promoted-to-approver'), [NotificationTypes.USER_REMOVED_AS_DOSSIER_MEMBER]: _('notification.user-removed-as-dossier-member'), + [NotificationTypes.DOWNLOAD_READY]: _('notification.download-ready'), } as const; diff --git a/apps/red-ui/src/app/utils/configuration.initializer.ts b/apps/red-ui/src/app/utils/configuration.initializer.ts index 7350337e9..2f31bef07 100644 --- a/apps/red-ui/src/app/utils/configuration.initializer.ts +++ b/apps/red-ui/src/app/utils/configuration.initializer.ts @@ -23,8 +23,7 @@ export function configurationInitializer( switchMap(loggedIn => (!loggedIn ? throwError('Not Logged In') : of({}))), mergeMapTo(generalSettingsService.getGeneralConfigurations()), tap(configuration => configService.updateDisplayName(configuration.displayName)), - tap(() => userPreferenceService.reload()), - tap(() => languageService.chooseAndSetInitialLanguage()), + tap(() => userPreferenceService.reload().then(() => languageService.chooseAndSetInitialLanguage())), catchError(() => { title.setTitle('RedactManager'); return of({}); diff --git a/apps/red-ui/src/assets/i18n/en.json b/apps/red-ui/src/assets/i18n/en.json index 4b1931c00..771207a5f 100644 --- a/apps/red-ui/src/assets/i18n/en.json +++ b/apps/red-ui/src/assets/i18n/en.json @@ -261,6 +261,14 @@ "show": "Show", "undo": "Undo" }, + "annotation-changes": { + "forced": "Redaction forced", + "header": "Manual changes:", + "legal-basis": "Reason changed", + "recategorized": "Image category changed", + "removed-manual": "Redaction/Hint removed", + "resized": "Redaction area has been modified" + }, "annotation-engines": { "dictionary": "{isHint, select, true{Hint} other{Redaction}} based on dictionary", "ner": "Redaction based on AI", @@ -356,12 +364,12 @@ "save": "Save Changes" }, "content": { + "classification": "Value / Classification", "comment": "Comment", "legalBasis": "Legal Basis", "reason": "Select redaction reason", - "section": "Paragraph / Location", - "classification": "Value / Classification", - "reason-placeholder": "Select a reason..." + "reason-placeholder": "Select a reason...", + "section": "Paragraph / Location" }, "header": "Edit Redaction Reason" }, @@ -1118,9 +1126,9 @@ "filter-options": "Filter options", "filter-types": "Filter", "label": "Filter", + "redaction-changes": "Only annotations with redaction changes", "unseen-pages": "Only annotations on unseen pages", - "with-comments": "Only annotations with comments", - "with-reason-changes": "Only redactions with reason changes" + "with-comments": "Only annotations with comments" }, "filter": { "analysis": "Analysis pending", @@ -1263,14 +1271,14 @@ "save": "Save" }, "content": { + "classification": "Value / Classification", "comment": "Comment", "dictionary": "Dictionary", "legalBasis": "Legal Basis", "reason": "Reason", - "section": "Paragraph / Location", - "classification": "Value / Classification", "reason-placeholder": "Select a reason ...", "rectangle": "Custom Rectangle", + "section": "Paragraph / Location", "text": "Selected text:" }, "header": { @@ -1305,6 +1313,7 @@ "dossier-deleted": "Dossier: {dossierName} has been deleted!", "dossier-owner-removed": "{dossierName} owner removed!", "dossier-owner-set": " {dossierName} owner changed to {user}!", + "download-ready": "Your download is ready!", "no-data": "You currently have no notifications", "unassigned-from-file": "You have been unassigned from {fileName} in the {dossierName}!", "user-becomes-dossier-member": "{user} joined dossier: {dossierName}!", @@ -1497,9 +1506,9 @@ }, "search-screen": { "cols": { + "assignee": "Assignee", "document": "Document", "dossier": "Dossier", - "assignee": "Assignee", "pages": "Pages", "status": "Status" }, diff --git a/apps/red-ui/src/assets/icons/general/redaction-changes.svg b/apps/red-ui/src/assets/icons/general/redaction-changes.svg new file mode 100644 index 000000000..4e00eeffe --- /dev/null +++ b/apps/red-ui/src/assets/icons/general/redaction-changes.svg @@ -0,0 +1,10 @@ + + + + + + + + diff --git a/libs/common-ui b/libs/common-ui index caf4838be..0610684e8 160000 --- a/libs/common-ui +++ b/libs/common-ui @@ -1 +1 @@ -Subproject commit caf4838be63574740e5380c74ad96fe21f7a456b +Subproject commit 0610684e8fb963fab1f7dd51fd850a7b47287fca diff --git a/libs/red-domain/src/lib/notifications/types.ts b/libs/red-domain/src/lib/notifications/types.ts index fb1465a6f..4e59d8bbb 100644 --- a/libs/red-domain/src/lib/notifications/types.ts +++ b/libs/red-domain/src/lib/notifications/types.ts @@ -10,6 +10,7 @@ export const NotificationTypes = { USER_REMOVED_AS_DOSSIER_MEMBER: 'USER_REMOVED_AS_DOSSIER_MEMBER', // "You have been removed from a dossier" USER_PROMOTED_TO_APPROVER: 'USER_PROMOTED_TO_APPROVER', // "You are promoted to approver" USER_DEGRADED_TO_REVIEWER: 'USER_DEGRADED_TO_REVIEWER', // "You are denoted to reviewer" + DOWNLOAD_READY: 'DOWNLOAD_READY', } as const; export type NotificationType = keyof typeof NotificationTypes; diff --git a/package.json b/package.json index 287c958c5..91a9f08b4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "redaction", - "version": "3.105.0", + "version": "3.111.0", "private": true, "license": "MIT", "scripts": { diff --git a/paligo-theme.tar.gz b/paligo-theme.tar.gz index 646cc9b83..840317a80 100644 Binary files a/paligo-theme.tar.gz and b/paligo-theme.tar.gz differ diff --git a/paligo-theme/paligo-styles/redacto-theme.css b/paligo-theme/paligo-styles/redacto-theme.css index bdb03936c..5ceef3777 100644 --- a/paligo-theme/paligo-styles/redacto-theme.css +++ b/paligo-theme/paligo-styles/redacto-theme.css @@ -319,6 +319,7 @@ li.searchresultitem { --iqser-grey-5: #d3d5da; --iqser-grey-6: #f0f1f4; --iqser-grey-7: #9398a0; + --iqser-grey-8: #f9fafb; --iqser-green-1: #00ff00; --iqser-green-2: #5ce594; --iqser-yellow-1: #ffb83b;