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 new file mode 100644 index 000000000..ae87c066b --- /dev/null +++ b/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/components/pdf-viewer/pdf-viewer.component.ts @@ -0,0 +1,704 @@ +import { + Component, + ElementRef, + EventEmitter, + Inject, + Input, + NgZone, + OnChanges, + OnInit, + Output, + SimpleChanges, + ViewChild, +} from '@angular/core'; +import { Dossier, File, IHeaderElement, IManualRedactionEntry, RotationTypes } from '@red/domain'; +import { Core, WebViewerInstance } from '@pdftron/webviewer'; +import { TranslateService } from '@ngx-translate/core'; +import { + ManualRedactionEntryType, + ManualRedactionEntryTypes, + ManualRedactionEntryWrapper, +} from '@models/file/manual-redaction-entry.wrapper'; +import { AnnotationWrapper } from '@models/file/annotation.wrapper'; +import { ManualRedactionService } from '../../services/manual-redaction.service'; +import { environment } from '@environments/environment'; +import { AnnotationDrawService } from '../../services/annotation-draw.service'; +import { AnnotationActionsService } from '../../services/annotation-actions.service'; +import { UserPreferenceService } from '@services/user-preference.service'; +import { BASE_HREF } from '../../../../../../tokens'; +import { ConfigService } from '@services/config.service'; +import { AutoUnsubscribe, ConfirmationDialogInput, LoadingService } from '@iqser/common-ui'; +import { DossiersDialogService } from '../../../../services/dossiers-dialog.service'; +import { loadCompareDocumentWrapper } from '../../../../utils/compare-mode.utils'; +import { PdfViewer } from '../../services/pdf-viewer.service'; +import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker'; +import { ActivatedRoute } from '@angular/router'; +import { toPosition } from '../../../../utils/pdf-calculation.utils'; +import { ViewModeService } from '../../services/view-mode.service'; +import { MultiSelectService } from '../../services/multi-select.service'; +import { FilePreviewStateService } from '../../services/file-preview-state.service'; +import { tap, withLatestFrom } from 'rxjs/operators'; +import { PageRotationService } from '../../services/page-rotation.service'; +import { ALLOWED_KEYBOARD_SHORTCUTS, HeaderElements, TextPopups } from '../../shared/constants'; +import Tools = Core.Tools; +import TextTool = Tools.TextTool; +import Annotation = Core.Annotations.Annotation; + +function getDivider(hiddenOn?: readonly ('desktop' | 'mobile' | 'tablet')[]) { + return { + type: 'divider', + hidden: hiddenOn, + }; +} + +@Component({ + selector: 'redaction-pdf-viewer', + templateUrl: './pdf-viewer.component.html', + styleUrls: ['./pdf-viewer.component.scss'], +}) +export class PdfViewerComponent extends AutoUnsubscribe implements OnInit, OnChanges { + @Input() dossier: Dossier; + @Input() canPerformActions = false; + @Input() annotations: AnnotationWrapper[]; + @Output() readonly fileReady = new EventEmitter(); + @Output() readonly annotationSelected = new EventEmitter(); + @Output() readonly manualAnnotationRequested = new EventEmitter(); + @Output() readonly pageChanged = new EventEmitter(); + @Output() readonly keyUp = new EventEmitter(); + @Output() readonly viewerReady = new EventEmitter(); + @Output() readonly annotationsChanged = new EventEmitter(); + @ViewChild('viewer', { static: true }) viewer: ElementRef; + @ViewChild('compareFileInput', { static: true }) compareFileInput: ElementRef; + instance: WebViewerInstance; + documentViewer: Core.DocumentViewer; + annotationManager: Core.AnnotationManager; + private _selectedText = ''; + + constructor( + @Inject(BASE_HREF) private readonly _baseHref: string, + private readonly _translateService: TranslateService, + private readonly _manualRedactionService: ManualRedactionService, + private readonly _dialogService: DossiersDialogService, + private readonly _ngZone: NgZone, + private readonly _activatedRoute: ActivatedRoute, + private readonly _userPreferenceService: UserPreferenceService, + private readonly _annotationDrawService: AnnotationDrawService, + private readonly _annotationActionsService: AnnotationActionsService, + private readonly _configService: ConfigService, + private readonly _loadingService: LoadingService, + private readonly _pageRotationService: PageRotationService, + readonly stateService: FilePreviewStateService, + readonly viewModeService: ViewModeService, + readonly multiSelectService: MultiSelectService, + readonly pdf: PdfViewer, + ) { + super(); + } + + private get _toggleTooltipsBtnTitle(): string { + return this._translateService.instant(_('pdf-viewer.toggle-tooltips'), { + active: this._userPreferenceService.getFilePreviewTooltipsPreference(), + }); + } + + private get _toggleTooltipsIcon(): string { + return this._convertPath( + this._userPreferenceService.getFilePreviewTooltipsPreference() + ? '/assets/icons/general/pdftron-action-enable-tooltips.svg' + : '/assets/icons/general/pdftron-action-disable-tooltips.svg', + ); + } + + async ngOnInit() { + this._setReadyAndInitialState = this._setReadyAndInitialState.bind(this); + await this._loadViewer(); + + this.addActiveScreenSubscription = this.stateService.blob$ + .pipe( + withLatestFrom(this.stateService.file$), + tap(() => (this.pdf.ready = false)), + tap(([blob, file]) => this._loadDocument(blob, file)), + ) + .subscribe(); + } + + async ngOnChanges(changes: SimpleChanges): Promise { + if (!this.instance) { + return; + } + + if (changes.canPerformActions) { + await this._handleCustomActions(); + } + } + + uploadFile(files: FileList) { + const fileToCompare = files[0]; + this.compareFileInput.nativeElement.value = null; + + if (!fileToCompare) { + console.error('No file to compare!'); + return; + } + + const fileReader = new FileReader(); + fileReader.onload = async () => { + const pdfNet = this.instance.Core.PDFNet; + + await pdfNet.initialize(environment.licenseKey ? atob(environment.licenseKey) : null); + + const compareDocument = await pdfNet.PDFDoc.createFromBuffer(fileReader.result as ArrayBuffer); + const blob = await this.stateService.blob; + const currentDocument = await pdfNet.PDFDoc.createFromBuffer(await blob.arrayBuffer()); + + const loadCompareDocument = async () => { + this._loadingService.start(); + this.pdf.ready = false; + const mergedDocument = await pdfNet.PDFDoc.create(); + const file = await this.stateService.file; + await loadCompareDocumentWrapper( + currentDocument, + compareDocument, + mergedDocument, + this.instance, + file, + () => { + this.viewModeService.compareMode = true; + }, + () => { + this.pdf.navigateToPage(1); + }, + this.instance.Core.PDFNet, + ); + this._loadingService.stop(); + }; + + const currentDocumentPageCount = await currentDocument.getPageCount(); + const compareDocumentPageCount = await compareDocument.getPageCount(); + + if (currentDocumentPageCount !== compareDocumentPageCount) { + this._dialogService.openDialog( + 'confirm', + null, + new ConfirmationDialogInput({ + title: _('confirmation-dialog.compare-file.title'), + question: _('confirmation-dialog.compare-file.question'), + translateParams: { + fileName: fileToCompare.name, + currentDocumentPageCount, + compareDocumentPageCount, + }, + }), + loadCompareDocument, + ); + } else { + await loadCompareDocument(); + } + }; + + fileReader.readAsArrayBuffer(fileToCompare); + } + + async closeCompareMode() { + this.viewModeService.compareMode = false; + const pdfNet = this.instance.Core.PDFNet; + await pdfNet.initialize(environment.licenseKey ? atob(environment.licenseKey) : null); + const blob = await this.stateService.blob; + const currentDocument = await pdfNet.PDFDoc.createFromBuffer(await blob.arrayBuffer()); + + const filename = (await this.stateService.file).filename ?? 'document.pdf'; + this.instance.UI.loadDocument(currentDocument, { filename }); + + this.instance.UI.disableElements([HeaderElements.CLOSE_COMPARE_BUTTON]); + this.instance.UI.enableElements([HeaderElements.COMPARE_BUTTON]); + this.pdf.navigateToPage(1); + } + + private async _loadViewer() { + this.instance = await this.pdf.loadViewer(this.viewer.nativeElement as HTMLElement); + this.documentViewer = this.pdf.documentViewer; + this.annotationManager = this.pdf.annotationManager; + + this._setSelectionMode(); + this._configureElements(); + this.pdf.disableHotkeys(); + await this._configureTextPopup(); + + this.annotationManager.addEventListener('annotationSelected', (annotations: Annotation[], action) => { + const nextAnnotations = this.multiSelectService.isEnabled ? this.annotationManager.getSelectedAnnotations() : annotations; + this.annotationSelected.emit(nextAnnotations.map(ann => ann.Id)); + if (action === 'deselected') { + this._toggleRectangleAnnotationAction(true); + } else { + if (!this.multiSelectService.isEnabled) { + this.pdf.deselectAnnotations(this.annotations.filter(wrapper => !nextAnnotations.find(ann => ann.Id === wrapper.id))); + } + this._configureAnnotationSpecificActions(annotations); + this._toggleRectangleAnnotationAction(annotations.length === 1 && annotations[0].ReadOnly); + } + }); + + this.annotationManager.addEventListener('annotationChanged', (annotations: Annotation[]) => { + // when a rectangle is drawn, + // it returns one annotation with tool name 'AnnotationCreateRectangle; + // this will auto select rectangle after drawing + if (annotations.length === 1 && annotations[0].ToolName === 'AnnotationCreateRectangle') { + this.annotationManager.selectAnnotations(annotations); + annotations[0].disableRotationControl(); + } + }); + + this.documentViewer.addEventListener('pageNumberUpdated', (pageNumber: number) => { + this.pdf.deselectAllAnnotations(); + this._ngZone.run(() => this.pageChanged.emit(pageNumber)); + return this._handleCustomActions(); + }); + + this.documentViewer.addEventListener('documentLoaded', this._setReadyAndInitialState); + + this.documentViewer.addEventListener('keyUp', ($event: KeyboardEvent) => { + // arrows and full-screen + if (($event.target as HTMLElement)?.tagName?.toLowerCase() !== 'input') { + if ($event.key.startsWith('Arrow') || $event.key === 'f') { + this._ngZone.run(() => this.keyUp.emit($event)); + $event.preventDefault(); + $event.stopPropagation(); + } + } + + if (!ALLOWED_KEYBOARD_SHORTCUTS.includes($event.key)) { + $event.preventDefault(); + $event.stopPropagation(); + } + }); + + this.documentViewer.addEventListener('textSelected', async (quads, selectedText, pageNumber: number) => { + this._selectedText = selectedText; + const textActions = [TextPopups.ADD_DICTIONARY, TextPopups.ADD_FALSE_POSITIVE]; + + const file = await this.stateService.file; + + if (this.viewModeService.isCompare && pageNumber % 2 === 0) { + this.instance.UI.disableElements(['textPopup']); + } else { + this.instance.UI.enableElements(['textPopup']); + } + + if (selectedText.length > 2 && this.canPerformActions && !this.pdf.isCurrentPageExcluded(file)) { + this.instance.UI.enableElements(textActions); + } else { + this.instance.UI.disableElements(textActions); + } + }); + + this.instance.UI.iframeWindow.addEventListener('visibilityChanged', (event: any) => { + if (event.detail.element === 'searchPanel') { + const inputElement = this.instance.UI.iframeWindow.document.getElementById('SearchPanel__input') as HTMLInputElement; + setTimeout(() => { + inputElement.value = ''; + }, 0); + if (!event.detail.isVisible) { + this.documentViewer.clearSearchResults(); + } + } + }); + } + + private _setInitialDisplayMode() { + this.instance.UI.setFitMode('FitPage'); + const instanceDisplayMode = this.documentViewer.getDisplayModeManager().getDisplayMode(); + instanceDisplayMode.mode = this.viewModeService.isCompare ? 'Facing' : 'Single'; + this.documentViewer.getDisplayModeManager().setDisplayMode(instanceDisplayMode); + } + + private _convertPath(path: string): string { + return this._baseHref + path; + } + + private _setSelectionMode(): void { + const textTool = this.instance.Core.Tools.TextTool as unknown as TextTool; + textTool.SELECTION_MODE = this._configService.values.SELECTION_MODE; + } + + private _toggleRectangleAnnotationAction(readonly = false) { + if (!readonly) { + this.instance.UI.enableElements([TextPopups.ADD_RECTANGLE]); + } else { + this.instance.UI.disableElements([TextPopups.ADD_RECTANGLE]); + } + } + + private _configureElements() { + this.instance.UI.disableElements([ + 'pageNavOverlay', + 'menuButton', + 'selectToolButton', + 'textHighlightToolButton', + 'textUnderlineToolButton', + 'textSquigglyToolButton', + 'textStrikeoutToolButton', + 'viewControlsButton', + 'contextMenuPopup', + 'linkButton', + 'toggleNotesButton', + 'notesPanel', + 'thumbnailControl', + 'documentControl', + 'ribbons', + 'toolsHeader', + 'rotateClockwiseButton', + 'rotateCounterClockwiseButton', + 'annotationStyleEditButton', + 'annotationGroupButton', + ]); + + const applyRotation: IHeaderElement = { + type: 'customElement', + dataElement: HeaderElements.APPLY_ROTATION, + render: () => { + const paragraph = document.createElement('p'); + paragraph.innerText = this._translateService.instant('page-rotation.apply'); + paragraph.style.cssText = ` + font-size: 11px; + font-weight: 600; + color: #DD4D50; + cursor: pointer; + margin: 0 12px; + `; + paragraph.addEventListener('click', async () => { + await this._pageRotationService.applyRotation(); + }); + return paragraph; + }, + }; + const discardRotation: IHeaderElement = { + type: 'customElement', + dataElement: HeaderElements.DISCARD_ROTATION, + render: () => { + const paragraph = document.createElement('p'); + paragraph.innerText = this._translateService.instant('page-rotation.discard'); + paragraph.style.cssText = ` + font-size: 11px; + font-weight: 600; + color: #283241; + cursor: pointer; + opacity: 0.7; + `; + paragraph.addEventListener('click', () => { + this._pageRotationService.discardRotation(); + }); + return paragraph; + }, + }; + + const divider = getDivider(); + const headerItems: IHeaderElement[] = [ + divider, + { + type: 'actionButton', + element: 'compare', + dataElement: HeaderElements.COMPARE_BUTTON, + img: this._convertPath('/assets/icons/general/pdftron-action-compare.svg'), + title: 'Compare', + onClick: () => { + this.compareFileInput.nativeElement.click(); + }, + }, + { + type: 'actionButton', + element: 'closeCompare', + dataElement: HeaderElements.CLOSE_COMPARE_BUTTON, + img: this._convertPath('/assets/icons/general/pdftron-action-close-compare.svg'), + title: 'Leave Compare Mode', + onClick: async () => { + await this.closeCompareMode(); + }, + }, + divider, + { + type: 'actionButton', + element: 'tooltips', + dataElement: HeaderElements.TOGGLE_TOOLTIPS, + img: this._toggleTooltipsIcon, + title: this._toggleTooltipsBtnTitle, + onClick: async () => { + await this._userPreferenceService.toggleFilePreviewTooltipsPreference(); + this._updateTooltipsVisibility(); + this.instance.UI.updateElement(HeaderElements.TOGGLE_TOOLTIPS, { + title: this._toggleTooltipsBtnTitle, + img: this._toggleTooltipsIcon, + }); + }, + }, + divider, + { + type: 'toolGroupButton', + toolGroup: 'rectangleTools', + dataElement: HeaderElements.SHAPE_TOOL_GROUP_BUTTON, + img: this._convertPath('/assets/icons/general/rectangle.svg'), + title: 'annotation.rectangle', + }, + divider, + { + type: 'actionButton', + element: 'tooltips', + dataElement: HeaderElements.ROTATE_LEFT_BUTTON, + img: this._convertPath('/assets/icons/general/rotate-left.svg'), + onClick: () => this._pageRotationService.addRotation(RotationTypes.LEFT), + }, + { + type: 'actionButton', + element: 'tooltips', + dataElement: HeaderElements.ROTATE_RIGHT_BUTTON, + img: this._convertPath('/assets/icons/general/rotate-right.svg'), + onClick: () => this._pageRotationService.addRotation(RotationTypes.RIGHT), + }, + applyRotation, + discardRotation, + divider, + ]; + + this.instance.UI.setHeaderItems(header => { + header.getItems().splice(8, 0, ...headerItems); + }); + + this.instance.UI.disableElements([ + HeaderElements.CLOSE_COMPARE_BUTTON, + HeaderElements.APPLY_ROTATION, + HeaderElements.DISCARD_ROTATION, + ]); + + const dossierTemplateId = this.dossier.dossierTemplateId; + + this.documentViewer.getTool('AnnotationCreateRectangle').setStyles({ + StrokeThickness: 2, + StrokeColor: this._annotationDrawService.getAndConvertColor(dossierTemplateId, 'manual'), + FillColor: this._annotationDrawService.getAndConvertColor(dossierTemplateId, 'manual'), + Opacity: 0.6, + }); + } + + private _configureAnnotationSpecificActions(viewerAnnotations: Annotation[]) { + if (!this.canPerformActions) { + return; + } + + const annotationWrappers = viewerAnnotations.map(va => this.annotations.find(a => a.id === va.Id)).filter(va => !!va); + this.instance.UI.annotationPopup.update([]); + + if (annotationWrappers.length === 0) { + this._configureRectangleAnnotationPopup(viewerAnnotations[0]); + return; + } + + // Add hide action as last item + const allAnnotationsHaveImageAction = annotationWrappers.reduce((acc, next) => acc && next.isImage, true); + if (allAnnotationsHaveImageAction) { + const allAreVisible = viewerAnnotations.reduce((acc, next) => next.isVisible() && acc, true); + + this.instance.UI.annotationPopup.add([ + { + type: 'actionButton', + img: allAreVisible + ? this._convertPath('/assets/icons/general/visibility-off.svg') + : this._convertPath('/assets/icons/general/visibility.svg'), + title: this._translateService.instant(`annotation-actions.${allAreVisible ? 'hide' : 'show'}`), + onClick: () => { + this._ngZone.run(() => { + if (allAreVisible) { + this.annotationManager.hideAnnotations(viewerAnnotations); + } else { + this.annotationManager.showAnnotations(viewerAnnotations); + } + this.annotationManager.deselectAllAnnotations(); + this._annotationActionsService.updateHiddenAnnotation(this.annotations, viewerAnnotations, allAreVisible); + }); + }, + }, + ]); + } + + const actions = this._annotationActionsService.getViewerAvailableActions(this.dossier, annotationWrappers, this.annotationsChanged); + this.instance.UI.annotationPopup.add(actions); + } + + private _configureRectangleAnnotationPopup(annotation: Annotation) { + if (!this.viewModeService.isCompare || annotation.getPageNumber() % 2 === 1) { + this.instance.UI.annotationPopup.add([ + { + type: 'actionButton', + dataElement: TextPopups.ADD_RECTANGLE, + img: this._convertPath('/assets/icons/general/pdftron-action-add-redaction.svg'), + title: this.#getTitle(ManualRedactionEntryTypes.REDACTION), + onClick: () => this._addRectangleManualRedaction(), + }, + ]); + } + } + + private _addRectangleManualRedaction() { + const activeAnnotation = this.annotationManager.getSelectedAnnotations()[0]; + const activePage = activeAnnotation.getPageNumber(); + const quads = [this._annotationDrawService.annotationToQuads(activeAnnotation)]; + const manualRedaction = this._getManualRedaction({ [activePage]: quads }); + this._cleanUpSelectionAndButtonState(); + + this.manualAnnotationRequested.emit(new ManualRedactionEntryWrapper(quads, manualRedaction, 'REDACTION', activeAnnotation.Id)); + } + + private _cleanUpSelectionAndButtonState() { + const rectangleElements = [HeaderElements.SHAPE_TOOL_GROUP_BUTTON]; + this.pdf.deselectAllAnnotations(); + this.instance.UI.disableElements(rectangleElements); + this.instance.UI.enableElements(rectangleElements); + } + + private _configureTextPopup() { + const searchButton = { + type: 'actionButton', + img: this._convertPath('/assets/icons/general/pdftron-action-search.svg'), + title: this._translateService.instant('pdf-viewer.text-popup.actions.search'), + onClick: () => { + const text = this.documentViewer.getSelectedText(); + const searchOptions = { + caseSensitive: true, // match case + wholeWord: true, // match whole words only + wildcard: false, // allow using '*' as a wildcard value + regex: false, // string is treated as a regular expression + searchUp: false, // search from the end of the document upwards + ambientString: true, // return ambient string as part of the result + }; + this.instance.UI.openElements(['searchPanel']); + setTimeout(() => this.instance.UI.searchTextFull(text, searchOptions), 250); + }, + }; + const popups: IHeaderElement[] = [searchButton]; + + // Adding directly to the false-positive dict is only available in dev-mode + if (this._userPreferenceService.areDevFeaturesEnabled) { + popups.push({ + type: 'actionButton', + dataElement: TextPopups.ADD_FALSE_POSITIVE, + img: this._convertPath('/assets/icons/general/pdftron-action-false-positive.svg'), + title: this.#getTitle(ManualRedactionEntryTypes.FALSE_POSITIVE), + onClick: () => this._addManualRedactionOfType(ManualRedactionEntryTypes.FALSE_POSITIVE), + }); + } + + popups.push({ + type: 'actionButton', + dataElement: TextPopups.ADD_REDACTION, + img: this._convertPath('/assets/icons/general/pdftron-action-add-redaction.svg'), + title: this.#getTitle(ManualRedactionEntryTypes.REDACTION), + onClick: () => this._addManualRedactionOfType(ManualRedactionEntryTypes.REDACTION), + }); + popups.push({ + type: 'actionButton', + dataElement: TextPopups.ADD_DICTIONARY, + img: this._convertPath('/assets/icons/general/pdftron-action-add-dict.svg'), + title: this.#getTitle(ManualRedactionEntryTypes.DICTIONARY), + onClick: () => this._addManualRedactionOfType(ManualRedactionEntryTypes.DICTIONARY), + }); + + this.instance.UI.textPopup.add(popups); + + return this._handleCustomActions(); + } + + #getTitle(type: ManualRedactionEntryType) { + return this._translateService.instant(this._manualRedactionService.getTitle(type, this.dossier)); + } + + private _addManualRedactionOfType(type: ManualRedactionEntryType) { + const selectedQuads: Readonly> = this.documentViewer.getSelectedTextQuads(); + const text = this.documentViewer.getSelectedText(); + const manualRedaction = this._getManualRedaction(selectedQuads, text, true); + this.manualAnnotationRequested.emit(new ManualRedactionEntryWrapper(selectedQuads, manualRedaction, type)); + } + + private async _handleCustomActions() { + this.instance.UI.setToolMode('AnnotationEdit'); + const elementsToToggle = [ + TextPopups.ADD_REDACTION, + TextPopups.ADD_RECTANGLE, + TextPopups.ADD_FALSE_POSITIVE, + HeaderElements.SHAPE_TOOL_GROUP_BUTTON, + HeaderElements.ANNOTATION_POPUP, + ]; + + const isCurrentPageExcluded = this.pdf.isCurrentPageExcluded(await this.stateService.file); + + if (this.canPerformActions && !isCurrentPageExcluded) { + try { + this.instance.UI.enableTools(['AnnotationCreateRectangle']); + } catch (e) { + // happens + } + this.instance.UI.enableElements(elementsToToggle); + + if (this._selectedText.length > 2) { + this.instance.UI.enableElements([TextPopups.ADD_DICTIONARY, TextPopups.ADD_FALSE_POSITIVE]); + } + + return; + } + + let elementsToDisable = [...elementsToToggle, TextPopups.ADD_RECTANGLE]; + + if (isCurrentPageExcluded) { + const allowedActionsWhenPageExcluded: string[] = [ + HeaderElements.ANNOTATION_POPUP, + TextPopups.ADD_RECTANGLE, + TextPopups.ADD_REDACTION, + HeaderElements.SHAPE_TOOL_GROUP_BUTTON, + ]; + elementsToDisable = elementsToDisable.filter(element => !allowedActionsWhenPageExcluded.includes(element)); + } else { + this.instance.UI.disableTools(['AnnotationCreateRectangle']); + } + + this.instance.UI.disableElements(elementsToDisable); + } + + private _getManualRedaction( + quads: Readonly>, + text?: string, + convertQuads = false, + ): IManualRedactionEntry { + const entry: IManualRedactionEntry = { positions: [] }; + + for (const key of Object.keys(quads)) { + for (const quad of quads[key]) { + const page = parseInt(key, 10); + const pageHeight = this.documentViewer.getPageHeight(page); + entry.positions.push(toPosition(page, pageHeight, convertQuads ? this.pdf.translateQuad(page, quad) : quad)); + } + } + + entry.value = text; + entry.rectangle = !text; + return entry; + } + + private async _loadDocument(blob: Blob, file: File) { + const document = await this.instance.Core.documentViewer.getDocument()?.getPDFDoc(); + await document?.lock(); + this.instance.UI.loadDocument(blob, { filename: file?.filename ?? 'document.pdf' }); + this._pageRotationService.clearRotationsHideActions(); + } + + private _setReadyAndInitialState(): void { + this._ngZone.run(() => { + this.viewerReady.emit(this.instance); + const routePageNumber: number = this._activatedRoute.snapshot.queryParams.page; + this.pageChanged.emit(routePageNumber || 1); + this._setInitialDisplayMode(); + this._updateTooltipsVisibility(); + }); + } + + private _updateTooltipsVisibility(): void { + const current = this._userPreferenceService.getFilePreviewTooltipsPreference(); + this.instance.UI.setAnnotationContentOverlayHandler(() => (current ? undefined : false)); + } +} 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 new file mode 100644 index 000000000..57e7f1772 --- /dev/null +++ b/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/file-preview.module.ts @@ -0,0 +1,73 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { IqserIconsModule } from '@iqser/common-ui'; +import { TranslateModule } from '@ngx-translate/core'; +import { RouterModule, Routes } from '@angular/router'; +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 { 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'; +import { PdfViewerComponent } from './components/pdf-viewer/pdf-viewer.component'; +import { AnnotationActionsComponent } from './components/annotation-actions/annotation-actions.component'; +import { CommentsComponent } from './components/comments/comments.component'; +import { DocumentInfoComponent } from './components/document-info/document-info.component'; +import { TypeAnnotationIconComponent } from './components/type-annotation-icon/type-annotation-icon.component'; +import { OverlayModule } from '@angular/cdk/overlay'; +import { ViewSwitchComponent } from './components/view-switch/view-switch.component'; +import { UserManagementComponent } from './components/user-management/user-management.component'; +import { AnnotationReferencesListComponent } from './components/annotation-references-list/annotation-references-list.component'; +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'; +import { PendingChangesGuard } from '../../../../guards/can-deactivate.guard'; +import { ManualRedactionService } from './services/manual-redaction.service'; + +const routes: Routes = [ + { + path: '', + component: FilePreviewScreenComponent, + pathMatch: 'full', + data: { reuse: true }, + canDeactivate: [PendingChangesGuard], + }, +]; + +const components = [ + FileWorkloadComponent, + AnnotationDetailsComponent, + AnnotationsListComponent, + PageIndicatorComponent, + PageExclusionComponent, + PdfViewerComponent, + AnnotationActionsComponent, + CommentsComponent, + DocumentInfoComponent, + TypeAnnotationIconComponent, + ViewSwitchComponent, + UserManagementComponent, + AcceptRecommendationDialogComponent, + AnnotationReferencesListComponent, + AnnotationCardComponent, + AnnotationReferencesPageIndicatorComponent, + HighlightsSeparatorComponent, +]; + +@NgModule({ + declarations: [FilePreviewScreenComponent, ...components], + imports: [ + RouterModule.forChild(routes), + CommonModule, + SharedModule, + SharedDossiersModule, + IqserIconsModule, + TranslateModule, + OverlayModule, + ], + providers: [ManualRedactionService], +}) +export class FilePreviewModule {} diff --git a/apps/red-ui/src/app/services/manual-annotation.service.ts b/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/services/manual-redaction.service.ts similarity index 83% rename from apps/red-ui/src/app/services/manual-annotation.service.ts rename to apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/services/manual-redaction.service.ts index f3294ed5c..c30e4066d 100644 --- a/apps/red-ui/src/app/services/manual-annotation.service.ts +++ b/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/services/manual-redaction.service.ts @@ -1,5 +1,5 @@ import { Injectable, Injector } from '@angular/core'; -import { +import type { AnnotationActionMode, Dossier, IAddRedactionRequest, @@ -10,20 +10,26 @@ import { IRemoveRedactionRequest, IResizeRequest, } from '@red/domain'; -import { AnnotationWrapper } from '@models/file/annotation.wrapper'; +import { type AnnotationWrapper } from '@models/file/annotation.wrapper'; import { GenericService, RequiredParam, Toaster, Validate } from '@iqser/common-ui'; import { map, tap } from 'rxjs/operators'; -import { PermissionsService } from './permissions.service'; -import { annotationActionsTranslations } from '../translations/annotation-actions-translations'; +import { PermissionsService } from '@services/permissions.service'; +import { annotationActionsTranslations } from '../../../../../translations/annotation-actions-translations'; import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker'; import { HttpErrorResponse, HttpStatusCode } from '@angular/common/http'; -import { ActiveDossiersService } from './dossiers/active-dossiers.service'; -import { Observable } from 'rxjs'; -import { DictionariesMapService } from './entity-services/dictionaries-map.service'; -import { ManualRedactionEntryType } from '@models/file/manual-redaction-entry.wrapper'; +import { ActiveDossiersService } from '@services/dossiers/active-dossiers.service'; +import { type Observable } from 'rxjs'; +import { DictionariesMapService } from '@services/entity-services/dictionaries-map.service'; +import { type ManualRedactionEntryType } from '@models/file/manual-redaction-entry.wrapper'; -@Injectable({ providedIn: 'root' }) -export class ManualAnnotationService extends GenericService { +function getMessage(mode: AnnotationActionMode, modifyDictionary?: boolean, error = false, isConflict = false) { + const type = modifyDictionary ? 'dictionary' : 'manual-redaction'; + const resultType = error ? (isConflict ? 'conflictError' : 'error') : 'success'; + return annotationActionsTranslations[type][mode][resultType]; +} + +@Injectable() +export class ManualRedactionService extends GenericService { CONFIG: { [key in AnnotationActionMode]: string; }; @@ -69,15 +75,15 @@ export class ManualAnnotationService extends GenericService return obs.pipe( tap({ - next: () => this._toaster.success(this._getMessage(mode, modifyDictionary), { positionClass: 'toast-file-preview' }), + next: () => this._toaster.success(getMessage(mode, modifyDictionary), { positionClass: 'toast-file-preview' }), error: (error: HttpErrorResponse) => { const isConflict = error.status === HttpStatusCode.Conflict; - this._toaster.error(this._getMessage(mode, modifyDictionary, true, isConflict), { + this._toaster.error(getMessage(mode, modifyDictionary, true, isConflict), { error, params: { dictionaryName: this._dictionariesMapService.getDictionary( body.type as string, - this._dossier(dossierId).dossierTemplateId, + this.#dossier(dossierId).dossierTemplateId, ).label, content: body.value, }, @@ -113,7 +119,6 @@ export class ManualAnnotationService extends GenericService return this.addAnnotation(manualRedactionEntry, dossierId, fileId); } - // /manualRedaction/request/legalBasis changeLegalBasis( annotationId: string, dossierId: string, @@ -123,47 +128,31 @@ export class ManualAnnotationService extends GenericService legalBasis: string, comment?: string, ) { - const mode: AnnotationActionMode = this._permissionsService.isApprover(this._dossier(dossierId)) + const mode: AnnotationActionMode = this._permissionsService.isApprover(this.#dossier(dossierId)) ? 'change-legal-basis' : 'request-change-legal-basis'; return this._makeRequest(mode, dossierId, fileId, { annotationId, legalBasis, comment, section, value }); } - // this wraps - // /manualRedaction/redaction/legalBasisChange - - // /manualRedaction/request/recategorize recategorizeImg(annotationId: string, dossierId: string, fileId: string, type: string, comment: string) { - const mode: AnnotationActionMode = this._permissionsService.isApprover(this._dossier(dossierId)) + const mode: AnnotationActionMode = this._permissionsService.isApprover(this.#dossier(dossierId)) ? 'recategorize-image' : 'request-image-recategorization'; return this._makeRequest(mode, dossierId, fileId, { annotationId, type, comment }); } - // this wraps - // /manualRedaction/redaction/recategorize - - // /manualRedaction/request/add addAnnotation(manualRedactionEntry: IAddRedactionRequest, dossierId: string, fileId: string) { - const mode: AnnotationActionMode = this._permissionsService.isApprover(this._dossier(dossierId)) ? 'add' : 'suggest'; + const mode: AnnotationActionMode = this._permissionsService.isApprover(this.#dossier(dossierId)) ? 'add' : 'suggest'; return this._makeRequest(mode, dossierId, fileId, manualRedactionEntry, null, manualRedactionEntry.addToDictionary); } - // this wraps - // /manualRedaction/redaction/add - - // /manualRedaction/request/force force(request: ILegalBasisChangeRequest, dossierId: string, fileId: string) { - const mode: AnnotationActionMode = this._permissionsService.isApprover(this._dossier(dossierId)) + const mode: AnnotationActionMode = this._permissionsService.isApprover(this.#dossier(dossierId)) ? 'force-redaction' : 'request-force-redaction'; return this._makeRequest(mode, dossierId, fileId, request); } - // this wraps - // /manualRedaction/redaction/force - - // /manualRedaction/approve approve(annotationId: string, dossierId: string, fileId: string, addToDictionary: boolean = false) { // for only here - approve the request return this._makeRequest( @@ -176,29 +165,20 @@ export class ManualAnnotationService extends GenericService ); } - // this wraps - // /manualRedaction/undo undoRequest(annotationWrapper: AnnotationWrapper, dossierId: string, fileId: string) { return this._makeRequest('undo', dossierId, fileId, annotationWrapper.id, null, annotationWrapper.isModifyDictionary); } - // this wraps - // /manualRedaction/decline/remove declineOrRemoveRequest(annotationWrapper: AnnotationWrapper, dossierId: string, fileId: string) { - const mode: AnnotationActionMode = this._permissionsService.isApprover(this._dossier(dossierId)) ? 'decline' : 'undo'; + const mode: AnnotationActionMode = this._permissionsService.isApprover(this.#dossier(dossierId)) ? 'decline' : 'undo'; return this._makeRequest(mode, dossierId, fileId, annotationWrapper.id, null, annotationWrapper.isModifyDictionary); } - // /manualRedaction/request/resize/ resizeOrSuggestToResize(annotationWrapper: AnnotationWrapper, dossierId: string, fileId: string, resizeRequest: IResizeRequest) { - const mode: AnnotationActionMode = this._permissionsService.isApprover(this._dossier(dossierId)) ? 'resize' : 'request-resize'; + const mode: AnnotationActionMode = this._permissionsService.isApprover(this.#dossier(dossierId)) ? 'resize' : 'request-resize'; return this._makeRequest(mode, dossierId, fileId, resizeRequest); } - // this wraps - // /manualRedaction/redaction/resize/ - - // /manualRedaction/request/remove/ removeOrSuggestRemoveAnnotation( annotationWrapper: AnnotationWrapper, dossierId: string, @@ -210,7 +190,7 @@ export class ManualAnnotationService extends GenericService body: any, removeDict = false; - if (this._permissionsService.isApprover(this._dossier(dossierId))) { + if (this._permissionsService.isApprover(this.#dossier(dossierId))) { // if it was something manual simply decline the existing request mode = 'remove'; body = { @@ -232,9 +212,6 @@ export class ManualAnnotationService extends GenericService return this._makeRequest(mode, dossierId, fileId, body, null, removeDict); } - // this wraps - // /manualRedaction/redaction/remove/ - getTitle(type: ManualRedactionEntryType, dossier: Dossier) { if (this._permissionsService.isApprover(dossier)) { switch (type) { @@ -372,13 +349,27 @@ export class ManualAnnotationService extends GenericService return this._post(body, url); } - private _dossier(dossierId: string): Dossier { + // #showToast(body, dossierId: string) { + // return tap({ + // next: () => this._toaster.success(getMessage(mode, modifyDictionary), { positionClass: 'toast-file-preview' }), + // error: (error: HttpErrorResponse) => { + // const isConflict = error.status === HttpStatusCode.Conflict; + // this._toaster.error(getMessage(mode, modifyDictionary, true, isConflict), { + // error, + // params: { + // dictionaryName: this._dictionariesMapService.getDictionary( + // body.type as string, + // this.#dossier(dossierId).dossierTemplateId, + // ).label, + // content: body.value, + // }, + // positionClass: 'toast-file-preview', + // }); + // }, + // }); + // } + + #dossier(dossierId: string): Dossier { return this._activeDossiersService.find(dossierId); } - - private _getMessage(mode: AnnotationActionMode, modifyDictionary?: boolean, error = false, isConflict = false) { - const type = modifyDictionary ? 'dictionary' : 'manual-redaction'; - const resultType = error ? (isConflict ? 'conflictError' : 'error') : 'success'; - return annotationActionsTranslations[type][mode][resultType]; - } } diff --git a/apps/red-ui/src/app/modules/file-preview/components/comments/comments.component.ts b/apps/red-ui/src/app/modules/file-preview/components/comments/comments.component.ts index da2574a61..78ff74a8e 100644 --- a/apps/red-ui/src/app/modules/file-preview/components/comments/comments.component.ts +++ b/apps/red-ui/src/app/modules/file-preview/components/comments/comments.component.ts @@ -27,7 +27,7 @@ export class CommentsComponent extends AutoUnsubscribe implements OnChanges { constructor( readonly permissionsService: PermissionsService, private readonly _userService: UserService, - private readonly _manualAnnotationService: ManualAnnotationService, + private readonly _manualRedactionService: ManualRedactionService, private readonly _commentingService: CommentingService, private readonly _loadingService: LoadingService, private readonly _changeRef: ChangeDetectorRef, @@ -55,7 +55,7 @@ export class CommentsComponent extends AutoUnsubscribe implements OnChanges { } this._loadingService.start(); const { dossierId, fileId } = this._stateService; - const commentId = await firstValueFrom(this._manualAnnotationService.addComment(value, this.annotation.id, dossierId, fileId)); + const commentId = await firstValueFrom(this._manualRedactionService.addComment(value, this.annotation.id, dossierId, fileId)); this.annotation.comments.push({ text: value, id: commentId, @@ -76,7 +76,7 @@ export class CommentsComponent extends AutoUnsubscribe implements OnChanges { $event.stopPropagation(); this._loadingService.start(); const { dossierId, fileId } = this._stateService; - await firstValueFrom(this._manualAnnotationService.deleteComment(comment.id, this.annotation.id, dossierId, fileId)); + await firstValueFrom(this._manualRedactionService.deleteComment(comment.id, this.annotation.id, dossierId, fileId)); this.annotation.comments.splice(this.annotation.comments.indexOf(comment), 1); this._changeRef.markForCheck(); this._loadingService.stop(); diff --git a/apps/red-ui/src/app/modules/file-preview/dialogs/accept-recommendation-dialog/accept-recommendation-dialog.component.ts b/apps/red-ui/src/app/modules/file-preview/dialogs/accept-recommendation-dialog/accept-recommendation-dialog.component.ts index 73323ca4c..a945a3430 100644 --- a/apps/red-ui/src/app/modules/file-preview/dialogs/accept-recommendation-dialog/accept-recommendation-dialog.component.ts +++ b/apps/red-ui/src/app/modules/file-preview/dialogs/accept-recommendation-dialog/accept-recommendation-dialog.component.ts @@ -6,7 +6,6 @@ import { Dictionary, Dossier } from '@red/domain'; import { ActiveDossiersService } from '@services/dossiers/active-dossiers.service'; import { BaseDialogComponent } from '@iqser/common-ui'; import { DictionaryService } from '@services/entity-services/dictionary.service'; -import { ManualAnnotationService } from '@services/manual-annotation.service'; import { AnnotationWrapper } from '@models/file/annotation.wrapper'; export interface AcceptRecommendationData { @@ -31,7 +30,6 @@ export class AcceptRecommendationDialogComponent extends BaseDialogComponent imp constructor( private readonly _formBuilder: FormBuilder, - private readonly _manualAnnotationService: ManualAnnotationService, private readonly _permissionsService: PermissionsService, private readonly _activeDossiersService: ActiveDossiersService, private readonly _dictionaryService: DictionaryService, diff --git a/apps/red-ui/src/app/modules/file-preview/dialogs/force-redaction-dialog/force-annotation-dialog.component.ts b/apps/red-ui/src/app/modules/file-preview/dialogs/force-redaction-dialog/force-annotation-dialog.component.ts index 0948c1627..b62932477 100644 --- a/apps/red-ui/src/app/modules/file-preview/dialogs/force-redaction-dialog/force-annotation-dialog.component.ts +++ b/apps/red-ui/src/app/modules/file-preview/dialogs/force-redaction-dialog/force-annotation-dialog.component.ts @@ -1,11 +1,7 @@ import { Component, Inject, Injector, OnInit } from '@angular/core'; import { FormBuilder, FormGroup, Validators } from '@angular/forms'; import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; -import { BaseDialogComponent, Toaster } from '@iqser/common-ui'; -import { TranslateService } from '@ngx-translate/core'; -import { UserService } from '@services/user.service'; -import { ManualAnnotationService } from '@services/manual-annotation.service'; -import { ActiveDossiersService } from '@services/dossiers/active-dossiers.service'; +import { BaseDialogComponent } from '@iqser/common-ui'; import { JustificationsService } from '@services/entity-services/justifications.service'; import { Dossier, ILegalBasisChangeRequest } from '@red/domain'; import { firstValueFrom } from 'rxjs'; @@ -26,13 +22,8 @@ export class ForceAnnotationDialogComponent extends BaseDialogComponent implemen legalOptions: LegalBasisOption[] = []; constructor( - private readonly _activeDossiersService: ActiveDossiersService, - private readonly _userService: UserService, private readonly _formBuilder: FormBuilder, - private readonly _notificationService: Toaster, - private readonly _translateService: TranslateService, private readonly _justificationsService: JustificationsService, - private readonly _manualAnnotationService: ManualAnnotationService, protected readonly _injector: Injector, protected readonly _dialogRef: MatDialogRef, @Inject(MAT_DIALOG_DATA) diff --git a/apps/red-ui/src/app/modules/file-preview/dialogs/manual-redaction-dialog/manual-annotation-dialog.component.ts b/apps/red-ui/src/app/modules/file-preview/dialogs/manual-redaction-dialog/manual-annotation-dialog.component.ts index fabc6793a..0adc208b1 100644 --- a/apps/red-ui/src/app/modules/file-preview/dialogs/manual-redaction-dialog/manual-annotation-dialog.component.ts +++ b/apps/red-ui/src/app/modules/file-preview/dialogs/manual-redaction-dialog/manual-annotation-dialog.component.ts @@ -2,8 +2,7 @@ import { Component, Inject, Injector, OnInit } from '@angular/core'; import { FormBuilder, FormGroup, Validators } from '@angular/forms'; import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { ManualRedactionEntryWrapper } from '@models/file/manual-redaction-entry.wrapper'; -import { ManualAnnotationService } from '@services/manual-annotation.service'; -import { PermissionsService } from '@services/permissions.service'; +import { ManualRedactionService } from '../../screens/file-preview-screen/services/manual-redaction.service'; import { JustificationsService } from '@services/entity-services/justifications.service'; import { Dictionary, Dossier, IAddRedactionRequest } from '@red/domain'; import { ActiveDossiersService } from '@services/dossiers/active-dossiers.service'; @@ -35,8 +34,7 @@ export class ManualAnnotationDialogComponent extends BaseDialogComponent impleme constructor( private readonly _formBuilder: FormBuilder, private readonly _justificationsService: JustificationsService, - private readonly _manualAnnotationService: ManualAnnotationService, - private readonly _permissionsService: PermissionsService, + private readonly _manualRedactionService: ManualRedactionService, private readonly _activeDossiersService: ActiveDossiersService, private readonly _dictionaryService: DictionaryService, protected readonly _injector: Injector, @@ -54,7 +52,7 @@ export class ManualAnnotationDialogComponent extends BaseDialogComponent impleme } get title() { - return this._manualAnnotationService.getTitle(this.data.manualRedactionEntryWrapper.type, this._dossier); + return this._manualRedactionService.getTitle(this.data.manualRedactionEntryWrapper.type, this._dossier); } get displayedDictionaryLabel() { 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 bfdbbec0d..c5c59063f 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 @@ -41,7 +41,7 @@ import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker'; import { SkippedService } from './services/skipped.service'; import { FilePreviewStateService } from './services/file-preview-state.service'; import { filePreviewScreenProviders } from './file-preview-providers'; -import { ManualAnnotationService } from '@services/manual-annotation.service'; +import { ManualRedactionService } from './services/manual-redaction.service'; import { DossiersService } from '@services/dossiers/dossiers.service'; import { PageRotationService } from './services/page-rotation.service'; import { ComponentCanDeactivate } from '@guards/can-deactivate.guard'; diff --git a/apps/red-ui/src/app/modules/file-preview/services/annotation-actions.service.ts b/apps/red-ui/src/app/modules/file-preview/services/annotation-actions.service.ts index 843e3ecf5..09b704baf 100644 --- a/apps/red-ui/src/app/modules/file-preview/services/annotation-actions.service.ts +++ b/apps/red-ui/src/app/modules/file-preview/services/annotation-actions.service.ts @@ -1,6 +1,6 @@ import { EventEmitter, Inject, Injectable, NgZone } from '@angular/core'; import { PermissionsService } from '@services/permissions.service'; -import { ManualAnnotationService } from '@services/manual-annotation.service'; +import { ManualRedactionService } from './manual-redaction.service'; import { AnnotationWrapper } from '@models/file/annotation.wrapper'; import { Observable } from 'rxjs'; import { TranslateService } from '@ngx-translate/core'; @@ -34,7 +34,7 @@ export class AnnotationActionsService { private readonly _ngZone: NgZone, private readonly _userService: UserService, private readonly _permissionsService: PermissionsService, - private readonly _manualAnnotationService: ManualAnnotationService, + private readonly _manualRedactionService: ManualRedactionService, private readonly _translateService: TranslateService, private readonly _dialogService: FilePreviewDialogService, private readonly _dialog: MatDialog, @@ -54,7 +54,7 @@ export class AnnotationActionsService { const { dossierId, fileId } = this._screenStateService; annotations.forEach(annotation => { this._processObsAndEmit( - this._manualAnnotationService.approve(annotation.id, dossierId, fileId, annotation.isModifyDictionary), + this._manualRedactionService.approve(annotation.id, dossierId, fileId, annotation.isModifyDictionary), annotation, annotationsChanged, ); @@ -66,7 +66,7 @@ export class AnnotationActionsService { const { dossierId, fileId } = this._screenStateService; annotations.forEach(annotation => { this._processObsAndEmit( - this._manualAnnotationService.declineOrRemoveRequest(annotation, dossierId, fileId), + this._manualRedactionService.declineOrRemoveRequest(annotation, dossierId, fileId), annotation, annotationsChanged, ); @@ -84,7 +84,7 @@ export class AnnotationActionsService { this._dialogService.openDialog('forceAnnotation', $event, data, (request: ILegalBasisChangeRequest) => { annotations.forEach(annotation => { this._processObsAndEmit( - this._manualAnnotationService.force( + this._manualRedactionService.force( { ...request, annotationId: annotation.id, @@ -108,7 +108,7 @@ export class AnnotationActionsService { (data: { comment: string; legalBasis: string; section: string; value: string }) => { annotations.forEach(annotation => { this._processObsAndEmit( - this._manualAnnotationService.changeLegalBasis( + this._manualRedactionService.changeLegalBasis( annotation.annotationId, dossierId, fileId, @@ -141,7 +141,7 @@ export class AnnotationActionsService { this._dialogService.openDialog('removeAnnotations', $event, data, (result: { comment: string }) => { annotations.forEach(annotation => { this._processObsAndEmit( - this._manualAnnotationService.removeOrSuggestRemoveAnnotation( + this._manualRedactionService.removeOrSuggestRemoveAnnotation( annotation, dossierId, fileId, @@ -167,7 +167,7 @@ export class AnnotationActionsService { this._dialogService.openDialog('recategorizeImage', $event, data, (res: { type: string; comment: string }) => { annotations.forEach(annotation => { this._processObsAndEmit( - this._manualAnnotationService.recategorizeImg(annotation.annotationId, dossierId, fileId, res.type, res.comment), + this._manualRedactionService.recategorizeImg(annotation.annotationId, dossierId, fileId, res.type, res.comment), annotation, annotationsChanged, ); @@ -181,7 +181,7 @@ export class AnnotationActionsService { const { dossierId, fileId } = this._screenStateService; annotations.forEach(annotation => { this._processObsAndEmit( - this._manualAnnotationService.undoRequest(annotation, dossierId, fileId), + this._manualRedactionService.undoRequest(annotation, dossierId, fileId), annotation, annotationsChanged, ); @@ -205,7 +205,7 @@ export class AnnotationActionsService { const comment = commentText ? { text: commentText } : undefined; annotations.forEach(annotation => { this._processObsAndEmit( - this._manualAnnotationService.addRecommendation(annotation, dossierId, fileId, comment), + this._manualRedactionService.addRecommendation(annotation, dossierId, fileId, comment), annotation, annotationsChanged, ); @@ -444,7 +444,7 @@ export class AnnotationActionsService { }; this._processObsAndEmit( - this._manualAnnotationService.resizeOrSuggestToResize(annotationWrapper, data.dossier.dossierId, fileId, resizeRequest), + this._manualRedactionService.resizeOrSuggestToResize(annotationWrapper, data.dossier.dossierId, fileId, resizeRequest), annotationWrapper, annotationsChanged, ); @@ -517,7 +517,7 @@ export class AnnotationActionsService { const { dossierId, fileId } = this._screenStateService; this._processObsAndEmit( - this._manualAnnotationService.addAnnotation(falsePositiveRequest, dossierId, fileId), + this._manualRedactionService.addAnnotation(falsePositiveRequest, dossierId, fileId), annotation, annotationsChanged, );