From ef8566598531416cb74ca09f30f02272d98075e6 Mon Sep 17 00:00:00 2001 From: Dan Percic Date: Mon, 19 Sep 2022 17:08:14 +0300 Subject: [PATCH] RED-5219: add button to load all annotations in dev mode --- .../file-preview-screen.component.ts | 44 ++++++++++++++----- .../modules/file-preview/utils/constants.ts | 19 +++----- .../services/viewer-header.service.ts | 40 ++++++++++++++--- .../app/modules/pdf-viewer/utils/constants.ts | 4 ++ .../src/app/modules/pdf-viewer/utils/types.ts | 4 ++ apps/red-ui/src/assets/config/config.json | 3 +- apps/red-ui/src/assets/i18n/de.json | 14 +++++- apps/red-ui/src/assets/i18n/en.json | 23 +++++----- docker/red-ui/docker-entrypoint.sh | 2 + libs/red-domain/src/lib/shared/app-config.ts | 1 + 10 files changed, 112 insertions(+), 42 deletions(-) diff --git a/apps/red-ui/src/app/modules/file-preview/file-preview-screen.component.ts b/apps/red-ui/src/app/modules/file-preview/file-preview-screen.component.ts index 1b86f0235..e21d93713 100644 --- a/apps/red-ui/src/app/modules/file-preview/file-preview-screen.component.ts +++ b/apps/red-ui/src/app/modules/file-preview/file-preview-screen.component.ts @@ -5,7 +5,6 @@ import { Component, ElementRef, HostListener, - Injector, NgZone, OnDestroy, OnInit, @@ -28,6 +27,7 @@ import { OnAttach, OnDetach, processFilters, + Toaster, } from '@iqser/common-ui'; import { MatDialogState } from '@angular/material/dialog'; import { ManualRedactionEntryWrapper } from '@models/file/manual-redaction-entry.wrapper'; @@ -60,12 +60,13 @@ import { StampService } from './services/stamp.service'; import { PdfViewer } from '../pdf-viewer/services/pdf-viewer.service'; import { REDAnnotationManager } from '../pdf-viewer/services/annotation-manager.service'; import { ViewerHeaderService } from '../pdf-viewer/services/viewer-header.service'; -import { ROTATION_ACTION_BUTTONS } from '../pdf-viewer/utils/constants'; +import { ROTATION_ACTION_BUTTONS, ViewerEvents } from '../pdf-viewer/utils/constants'; import { SkippedService } from './services/skipped.service'; import { REDDocumentViewer } from '../pdf-viewer/services/document-viewer.service'; import { AnnotationsListingService } from './services/annotations-listing.service'; import { PdfProxyService } from './services/pdf-proxy.service'; import { ConfigService } from '@services/config.service'; +import { TranslateService } from '@ngx-translate/core'; import Annotation = Core.Annotations.Annotation; const textActions = [TextPopups.ADD_DICTIONARY, TextPopups.ADD_FALSE_POSITIVE]; @@ -122,7 +123,12 @@ export class FilePreviewScreenComponent private readonly _annotationDrawService: AnnotationDrawService, private readonly _annotationProcessingService: AnnotationProcessingService, private readonly _stampService: StampService, - private readonly _injector: Injector, + private readonly _reanalysisService: ReanalysisService, + private readonly _toaster: Toaster, + private readonly _manualRedactionService: ManualRedactionService, + private readonly _filesService: FilesService, + private readonly _fileManagementService: FileManagementService, + private readonly _translateService: TranslateService, ) { super(); document.documentElement.addEventListener('fullscreenchange', () => { @@ -278,8 +284,7 @@ export class FilePreviewScreenComponent this._subscribeToFileUpdates(); if (file?.analysisRequired && !file.excludedFromAutomaticAnalysis) { - const reanalysisService = this._injector.get(ReanalysisService); - const reanalyzeFiles = reanalysisService.reanalyzeFilesForDossier([file], this.dossierId, { force: true }); + const reanalyzeFiles = this._reanalysisService.reanalyzeFilesForDossier([file], this.dossierId, { force: true }); await firstValueFrom(reanalyzeFiles); } @@ -306,15 +311,15 @@ export class FilePreviewScreenComponent if (selectedAnnotations.length > 0) { this._annotationManager.delete([selectedAnnotations[0].Id]); } - const manualRedactionService = this._injector.get(ManualRedactionService); - const add$ = manualRedactionService.addAnnotation( + + const add$ = this._manualRedactionService.addAnnotation( result.annotations.map(w => w.manualRedactionEntry).filter(e => e.positions[0].page <= file.numberOfPages), this.dossierId, this.fileId, result.dictionary?.label, ); - const filesService = this._injector.get(FilesService); - const addAndReload$ = add$.pipe(switchMap(() => filesService.reload(this.dossierId, file))); + + const addAndReload$ = add$.pipe(switchMap(() => this._filesService.reload(this.dossierId, file))); return firstValueFrom(addAndReload$.pipe(catchError(() => of(undefined)))); }, ); @@ -389,8 +394,7 @@ export class FilePreviewScreenComponent } async downloadOriginalFile({ cacheIdentifier, dossierId, fileId, filename }: File) { - const fileManagementService = this._injector.get(FileManagementService); - const originalFile = fileManagementService.downloadOriginal(dossierId, fileId, 'response', cacheIdentifier); + const originalFile = this._fileManagementService.downloadOriginal(dossierId, fileId, 'response', cacheIdentifier); download(await firstValueFrom(originalFile), filename); } @@ -610,7 +614,25 @@ export class FilePreviewScreenComponent this.addActiveScreenSubscription = this.pdfProxyService.pageChanged$.subscribe(page => this._ngZone.run(() => this.#updateQueryParamsPage(page)), ); + this.addActiveScreenSubscription = this.pdfProxyService.annotationSelected$.subscribe(); + + this.addActiveScreenSubscription = this._viewerHeaderService.events$ + .pipe( + filter(event => event.type === ViewerEvents.LOAD_ALL_ANNOTATIONS), + switchMap(() => this._fileDataService.annotations), + tap(annotations => { + if (annotations.length >= this.configService.values.ANNOTATIONS_THRESHOLD) { + this._toaster.warning(_('load-all-annotations-threshold-exceeded'), { + params: { + threshold: this.configService.values.ANNOTATIONS_THRESHOLD, + }, + }); + } + }), + switchMap(annotations => this.drawChangedAnnotations([], annotations)), + ) + .subscribe(); } private _handleDeletedDossier(): void { diff --git a/apps/red-ui/src/app/modules/file-preview/utils/constants.ts b/apps/red-ui/src/app/modules/file-preview/utils/constants.ts index b03361b6f..9b1b824c2 100644 --- a/apps/red-ui/src/app/modules/file-preview/utils/constants.ts +++ b/apps/red-ui/src/app/modules/file-preview/utils/constants.ts @@ -1,4 +1,4 @@ -import { List } from '@iqser/common-ui'; +import { List, ValuesOf } from '@iqser/common-ui'; export const ActionsHelpModeKeys = { redaction: 'redaction_text', @@ -13,27 +13,20 @@ export const ActionsHelpModeKeys = { export const ALL_HOTKEYS: List = ['Escape', 'F', 'f', 'ArrowUp', 'ArrowDown'] as const; -export type HeaderElementType = - | 'SHAPE_TOOL_GROUP_BUTTON' - | 'ROTATE_LEFT_BUTTON' - | 'ROTATE_RIGHT_BUTTON' - | 'APPLY_ROTATION' - | 'DISCARD_ROTATION' - | 'TOGGLE_TOOLTIPS' - | 'COMPARE_BUTTON' - | 'CLOSE_COMPARE_BUTTON'; - -export const HeaderElements: Record = { +export const HeaderElements = { SHAPE_TOOL_GROUP_BUTTON: 'SHAPE_TOOL_GROUP_BUTTON', ROTATE_LEFT_BUTTON: 'ROTATE_LEFT_BUTTON', ROTATE_RIGHT_BUTTON: 'ROTATE_RIGHT_BUTTON', APPLY_ROTATION: 'APPLY_ROTATION', DISCARD_ROTATION: 'DISCARD_ROTATION', - TOGGLE_TOOLTIPS: 'TOGGLE_TOOLTIPS', COMPARE_BUTTON: 'COMPARE_BUTTON', CLOSE_COMPARE_BUTTON: 'CLOSE_COMPARE_BUTTON', + TOGGLE_TOOLTIPS: 'TOGGLE_TOOLTIPS', + LOAD_ALL_ANNOTATIONS: 'LOAD_ALL_ANNOTATIONS', } as const; +export type HeaderElementType = ValuesOf; + export const TextPopups = { ADD_REDACTION: 'add-redaction', ADD_DICTIONARY: 'add-dictionary', diff --git a/apps/red-ui/src/app/modules/pdf-viewer/services/viewer-header.service.ts b/apps/red-ui/src/app/modules/pdf-viewer/services/viewer-header.service.ts index 9e5dad968..3707a128c 100644 --- a/apps/red-ui/src/app/modules/pdf-viewer/services/viewer-header.service.ts +++ b/apps/red-ui/src/app/modules/pdf-viewer/services/viewer-header.service.ts @@ -1,4 +1,4 @@ -import { Inject, Injectable, Injector } from '@angular/core'; +import { Inject, Injectable } from '@angular/core'; import { IHeaderElement, RotationTypes } from '@red/domain'; import { HeaderElements, HeaderElementType } from '../../file-preview/utils/constants'; import { TranslateService } from '@ngx-translate/core'; @@ -6,9 +6,13 @@ import { BASE_HREF_FN, BaseHrefFn } from '@iqser/common-ui'; import { TooltipsService } from './tooltips.service'; import { PageRotationService } from './page-rotation.service'; import { PdfViewer } from './pdf-viewer.service'; -import { ROTATION_ACTION_BUTTONS } from '../utils/constants'; +import { ROTATION_ACTION_BUTTONS, ViewerEvents } from '../utils/constants'; import { FilesMapService } from '@services/files/files-map.service'; import { REDDocumentViewer } from './document-viewer.service'; +import { UserPreferenceService } from '@users/user-preference.service'; +import { ActivatedRoute, Router } from '@angular/router'; +import { Observable, Subject } from 'rxjs'; +import { ViewerEvent } from '../utils/types'; const divider: IHeaderElement = { type: 'divider', @@ -16,10 +20,12 @@ const divider: IHeaderElement = { @Injectable() export class ViewerHeaderService { + readonly events$: Observable; #buttons: Map; readonly #config = new Map([ [HeaderElements.SHAPE_TOOL_GROUP_BUTTON, true], [HeaderElements.TOGGLE_TOOLTIPS, true], + [HeaderElements.LOAD_ALL_ANNOTATIONS, false], [HeaderElements.COMPARE_BUTTON, true], [HeaderElements.CLOSE_COMPARE_BUTTON, false], [HeaderElements.ROTATE_LEFT_BUTTON, true], @@ -28,16 +34,22 @@ export class ViewerHeaderService { [HeaderElements.DISCARD_ROTATION, false], ]); #docBeforeCompare: Blob; + readonly #events$ = new Subject(); constructor( @Inject(BASE_HREF_FN) private readonly _convertPath: BaseHrefFn, - private readonly _injector: Injector, + private readonly _filesMapService: FilesMapService, private readonly _translateService: TranslateService, private readonly _pdf: PdfViewer, private readonly _documentViewer: REDDocumentViewer, private readonly _rotationService: PageRotationService, private readonly _tooltipsService: TooltipsService, - ) {} + private readonly _userPreferenceService: UserPreferenceService, + private readonly _activatedRoute: ActivatedRoute, + private readonly _router: Router, + ) { + this.events$ = this.#events$.asObservable(); + } private get _rectangle(): IHeaderElement { return { @@ -62,6 +74,17 @@ export class ViewerHeaderService { }; } + private get _loadAllAnnotations(): IHeaderElement { + return { + type: 'actionButton', + element: HeaderElements.LOAD_ALL_ANNOTATIONS, + dataElement: HeaderElements.LOAD_ALL_ANNOTATIONS, + title: this._translateService.instant('viewer-header.load-all-annotations'), + img: this._tooltipsService.toggleTooltipsBtnIcon, + onClick: () => this.#events$.next({ type: ViewerEvents.LOAD_ALL_ANNOTATIONS }), + }; + } + private get _closeCompare(): IHeaderElement { return { type: 'actionButton', @@ -167,11 +190,16 @@ export class ViewerHeaderService { [HeaderElements.APPLY_ROTATION, this._applyRotation], [HeaderElements.DISCARD_ROTATION, this._discardRotation], [HeaderElements.TOGGLE_TOOLTIPS, this._toggleTooltips], + [HeaderElements.LOAD_ALL_ANNOTATIONS, this._loadAllAnnotations], [HeaderElements.COMPARE_BUTTON, this._compare], [HeaderElements.CLOSE_COMPARE_BUTTON, this._closeCompare], ]); this.updateElements(); + + if (this._userPreferenceService.areDevFeaturesEnabled) { + this.enable([HeaderElements.LOAD_ALL_ANNOTATIONS]); + } } enable(elements: HeaderElementType[]): void { @@ -186,7 +214,7 @@ export class ViewerHeaderService { this._pdf.instance?.UI.setHeaderItems(header => { const enabledItems: IHeaderElement[] = []; const groups: HeaderElementType[][] = [ - [HeaderElements.COMPARE_BUTTON, HeaderElements.CLOSE_COMPARE_BUTTON], + [HeaderElements.COMPARE_BUTTON, HeaderElements.CLOSE_COMPARE_BUTTON, HeaderElements.LOAD_ALL_ANNOTATIONS], [HeaderElements.TOGGLE_TOOLTIPS], [HeaderElements.SHAPE_TOOL_GROUP_BUTTON], [ @@ -217,7 +245,7 @@ export class ViewerHeaderService { private _closeCompareMode() { this._pdf.closeCompareMode(); const { dossierId, fileId } = this._pdf; - const file = this._injector.get(FilesMapService).get(dossierId, fileId); + const file = this._filesMapService.get(dossierId, fileId); const filename = file.filename ?? 'document.pdf'; this._pdf.instance.UI.loadDocument(this.#docBeforeCompare, { filename }); diff --git a/apps/red-ui/src/app/modules/pdf-viewer/utils/constants.ts b/apps/red-ui/src/app/modules/pdf-viewer/utils/constants.ts index 8753b2f53..a232b876f 100644 --- a/apps/red-ui/src/app/modules/pdf-viewer/utils/constants.ts +++ b/apps/red-ui/src/app/modules/pdf-viewer/utils/constants.ts @@ -31,6 +31,10 @@ export const SEARCH_OPTIONS = { ambientString: true, // return ambient string as part of the result }; +export const ViewerEvents = { + LOAD_ALL_ANNOTATIONS: 'LOAD_ALL_ANNOTATIONS', +} as const; + export const USELESS_ELEMENTS = [ 'pageNavOverlay', 'menuButton', diff --git a/apps/red-ui/src/app/modules/pdf-viewer/utils/types.ts b/apps/red-ui/src/app/modules/pdf-viewer/utils/types.ts index 71407bb8c..fa6515774 100644 --- a/apps/red-ui/src/app/modules/pdf-viewer/utils/types.ts +++ b/apps/red-ui/src/app/modules/pdf-viewer/utils/types.ts @@ -15,3 +15,7 @@ export interface DeleteAnnotationsOptions { } export type AnnotationPredicate = (value: Annotation) => boolean; + +export interface ViewerEvent { + readonly type: string; +} diff --git a/apps/red-ui/src/assets/config/config.json b/apps/red-ui/src/assets/config/config.json index 21d8480c2..ea95a38bd 100644 --- a/apps/red-ui/src/assets/config/config.json +++ b/apps/red-ui/src/assets/config/config.json @@ -15,5 +15,6 @@ "RECENT_PERIOD_IN_HOURS": 24, "SELECTION_MODE": "structural", "MANUAL_BASE_URL": "https://docs.redactmanager.com/preview", - "RSS_ENABLED": true + "RSS_ENABLED": true, + "ANNOTATIONS_THRESHOLD": 1000 } diff --git a/apps/red-ui/src/assets/i18n/de.json b/apps/red-ui/src/assets/i18n/de.json index 1bb931ce5..d5c31ea12 100644 --- a/apps/red-ui/src/assets/i18n/de.json +++ b/apps/red-ui/src/assets/i18n/de.json @@ -787,7 +787,6 @@ }, "quick-filters": { "member": "", - "my-dossiers": "Meine Dossiers", "owner": "" }, "reanalyse": { @@ -1314,6 +1313,7 @@ "no-data": { "title": "Auf dieser Seite gibt es keine Anmerkungen." }, + "open-rss-view": "", "quick-nav": { "jump-first": "Zur ersten Seite springen", "jump-last": "Zur letzten Seite springen" @@ -1615,6 +1615,7 @@ "usage-details": "Nutzungsdetails" }, "license-information": "Lizenzinformationen", + "load-all-annotations-threshold-exceeded": "", "loading": "", "manual-annotation": { "dialog": { @@ -1906,6 +1907,14 @@ "red-user-admin": "Benutzer-Admin", "regular": "Regulär" }, + "rss-dialog": { + "actions": { + "close": "", + "export-json": "", + "export-xml": "" + }, + "title": "" + }, "rules-screen": { "error": { "generic": "Es ist ein Fehler aufgetreten ... Die Regeln konnten nicht aktualisiert werden!" @@ -2094,6 +2103,9 @@ "view-as": "Ansicht als:", "workflow": "Arbeitsablauf" }, + "viewer-header": { + "load-all-annotations": "" + }, "watermark-screen": { "action": { "change-success": "Das Wasserzeichen wurde aktualisiert!", diff --git a/apps/red-ui/src/assets/i18n/en.json b/apps/red-ui/src/assets/i18n/en.json index 44c934957..cd3f3f4e3 100644 --- a/apps/red-ui/src/assets/i18n/en.json +++ b/apps/red-ui/src/assets/i18n/en.json @@ -787,7 +787,6 @@ }, "quick-filters": { "member": "Dossier Member", - "my-dossiers": "My Dossiers", "owner": "Dossier Owner" }, "reanalyse": { @@ -1295,14 +1294,6 @@ }, "upload-csv": "Upload File Attributes Configuration" }, - "rss-dialog": { - "title": "Structured Component Management", - "actions": { - "export-json": "Export JSON", - "export-xml": "Export XML", - "close": "Close" - } - }, "file-preview": { "assign-me": "Assign to me", "assign-reviewer": "Assign User", @@ -1311,7 +1302,6 @@ "delta-tooltip": "The Delta View shows the unseen changes since your last visit to the page. This view is only available if there is at least 1 change.", "document-info": "Document Info", "download-original-file": "Download Original File", - "open-rss-view": "Open Structured Component Management View", "exclude-pages": "Exclude pages from redaction", "excluded-from-redaction": "excluded", "fullscreen": "Full Screen (F)", @@ -1323,6 +1313,7 @@ "no-data": { "title": "There have been no changes to this page." }, + "open-rss-view": "Open Structured Component Management View", "quick-nav": { "jump-first": "Jump to first page", "jump-last": "Jump to last page" @@ -1624,6 +1615,7 @@ "usage-details": "Usage Details" }, "license-information": "License Information", + "load-all-annotations-threshold-exceeded": "Caution, document contains more than {threshold} annotations. Drawing all annotations will affect the performance of the app and could even block it.", "loading": "Loading", "manual-annotation": { "dialog": { @@ -1915,6 +1907,14 @@ "red-user-admin": "Users Admin", "regular": "Regular" }, + "rss-dialog": { + "actions": { + "close": "Close", + "export-json": "Export JSON", + "export-xml": "Export XML" + }, + "title": "Structured Component Management" + }, "rules-screen": { "error": { "generic": "Something went wrong... Rules update failed!" @@ -2103,6 +2103,9 @@ "view-as": "View as:", "workflow": "Workflow" }, + "viewer-header": { + "load-all-annotations": "Load all annotations" + }, "watermark-screen": { "action": { "change-success": "Watermark has been updated!", diff --git a/docker/red-ui/docker-entrypoint.sh b/docker/red-ui/docker-entrypoint.sh index 0b0be7c92..d8c8949e3 100755 --- a/docker/red-ui/docker-entrypoint.sh +++ b/docker/red-ui/docker-entrypoint.sh @@ -18,6 +18,7 @@ RECENT_PERIOD_IN_HOURS="${RECENT_PERIOD_IN_HOURS:-24}" SELECTION_MODE="${SELECTION_MODE:-structural}" MANUAL_BASE_URL="${MANUAL_BASE_URL:-https://docs.redactmanager.com/preview}" RSS_ENABLED="${RSS_ENABLED:-false}" +ANNOTATIONS_THRESHOLD="${ANNOTATIONS_THRESHOLD:-1000}" @@ -39,6 +40,7 @@ echo '{ "SELECTION_MODE":"'"$SELECTION_MODE"'", "MANUAL_BASE_URL":"'"$MANUAL_BASE_URL"'", "RSS_ENABLED":'"$RSS_ENABLED"' + "ANNOTATIONS_THRESHOLD":'"$ANNOTATIONS_THRESHOLD"' }' > /usr/share/nginx/html/ui/assets/config/config.json echo 'Env variables: ' diff --git a/libs/red-domain/src/lib/shared/app-config.ts b/libs/red-domain/src/lib/shared/app-config.ts index 5ccb10056..d6f1d798e 100644 --- a/libs/red-domain/src/lib/shared/app-config.ts +++ b/libs/red-domain/src/lib/shared/app-config.ts @@ -16,4 +16,5 @@ export interface AppConfig { SELECTION_MODE: string; MANUAL_BASE_URL: string; RSS_ENABLED: boolean; + ANNOTATIONS_THRESHOLD: number; }