From 79e568abbf1d51e969e3fa8ddcf8c299ce07bd94 Mon Sep 17 00:00:00 2001 From: Timo Date: Tue, 23 Feb 2021 09:24:44 +0200 Subject: [PATCH] Multi-Select Annotations --- apps/red-ui/src/app/app.module.ts | 4 +- .../service/annotation-actions.service.ts | 132 +++++++++++------- apps/red-ui/src/app/dialogs/dialog.service.ts | 34 ++--- .../remove-annotations-dialog.component.html | 20 +++ .../remove-annotations-dialog.component.scss | 0 .../remove-annotations-dialog.component.ts | 28 ++++ .../annotation-actions.component.html | 20 +-- .../annotation-actions.component.ts | 4 +- .../file-preview-screen.component.html | 2 +- .../file-preview-screen.component.ts | 65 +++++---- .../file/pdf-viewer/pdf-viewer.component.ts | 23 +-- 11 files changed, 214 insertions(+), 118 deletions(-) create mode 100644 apps/red-ui/src/app/dialogs/remove-annotations-dialog/remove-annotations-dialog.component.html create mode 100644 apps/red-ui/src/app/dialogs/remove-annotations-dialog/remove-annotations-dialog.component.scss create mode 100644 apps/red-ui/src/app/dialogs/remove-annotations-dialog/remove-annotations-dialog.component.ts diff --git a/apps/red-ui/src/app/app.module.ts b/apps/red-ui/src/app/app.module.ts index 2c38a9962..907a54f0d 100644 --- a/apps/red-ui/src/app/app.module.ts +++ b/apps/red-ui/src/app/app.module.ts @@ -111,6 +111,7 @@ import { EditColorDialogComponent } from './screens/admin/default-colors-screen/ import { DownloadsListScreenComponent } from './screens/downloads-list-screen/downloads-list-screen.component'; import { DigitalSignatureScreenComponent } from './screens/admin/digital-signature-screen/digital-signature-screen.component'; import { ScrollingModule } from '@angular/cdk/scrolling'; +import { RemoveAnnotationsDialogComponent } from './dialogs/remove-annotations-dialog/remove-annotations-dialog.component'; export function HttpLoaderFactory(httpClient: HttpClient) { return new TranslateHttpLoader(httpClient, '/assets/i18n/', '.json'); @@ -369,7 +370,8 @@ const matImports = [ DefaultColorsScreenComponent, EditColorDialogComponent, DownloadsListScreenComponent, - DigitalSignatureScreenComponent + DigitalSignatureScreenComponent, + RemoveAnnotationsDialogComponent ], imports: [ BrowserModule, diff --git a/apps/red-ui/src/app/common/service/annotation-actions.service.ts b/apps/red-ui/src/app/common/service/annotation-actions.service.ts index eccd463bd..af30b2c58 100644 --- a/apps/red-ui/src/app/common/service/annotation-actions.service.ts +++ b/apps/red-ui/src/app/common/service/annotation-actions.service.ts @@ -21,43 +21,63 @@ export class AnnotationActionsService { private readonly _dialogService: DialogService ) {} - public acceptSuggestion($event: MouseEvent, annotation: AnnotationWrapper, annotationsChanged: EventEmitter) { + public acceptSuggestion($event: MouseEvent, annotations: AnnotationWrapper[], annotationsChanged: EventEmitter) { $event?.stopPropagation(); - this._processObsAndEmit(this._manualAnnotationService.approveRequest(annotation.id, annotation.isModifyDictionary), annotation, annotationsChanged); + annotations.forEach((annotation) => { + this._processObsAndEmit(this._manualAnnotationService.approveRequest(annotation.id, annotation.isModifyDictionary), annotation, annotationsChanged); + }); } - public rejectSuggestion($event: MouseEvent, annotation: AnnotationWrapper, annotationsChanged: EventEmitter) { + public rejectSuggestion($event: MouseEvent, annotations: AnnotationWrapper[], annotationsChanged: EventEmitter) { $event?.stopPropagation(); - this._processObsAndEmit(this._manualAnnotationService.declineOrRemoveRequest(annotation), annotation, annotationsChanged); + annotations.forEach((annotation) => { + this._processObsAndEmit(this._manualAnnotationService.declineOrRemoveRequest(annotation), annotation, annotationsChanged); + }); } public suggestRemoveAnnotation( $event: MouseEvent, - annotation: AnnotationWrapper, + annotations: AnnotationWrapper[], removeFromDictionary: boolean, annotationsChanged: EventEmitter ) { - this._dialogService.openRemoveFromDictionaryDialog($event, annotation, removeFromDictionary, () => { - this._processObsAndEmit( - this._manualAnnotationService.removeOrSuggestRemoveAnnotation(annotation, removeFromDictionary), - annotation, - annotationsChanged - ); + this._dialogService.openRemoveFromDictionaryDialog($event, annotations, removeFromDictionary, () => { + annotations.forEach((annotation) => { + this._processObsAndEmit( + this._manualAnnotationService.removeOrSuggestRemoveAnnotation(annotation, removeFromDictionary), + annotation, + annotationsChanged + ); + }); }); } - public markAsFalsePositive($event: MouseEvent, annotation: AnnotationWrapper, annotationsChanged: EventEmitter) { - this._markAsFalsePositive($event, annotation, this._getFalsePositiveText(annotation), annotationsChanged); + public markAsFalsePositive($event: MouseEvent, annotations: AnnotationWrapper[], annotationsChanged: EventEmitter) { + annotations.forEach((annotation) => { + this._markAsFalsePositive($event, annotation, this._getFalsePositiveText(annotation), annotationsChanged); + }); } - public undoDirectAction($event: MouseEvent, annotation: AnnotationWrapper, annotationsChanged: EventEmitter) { + public undoDirectAction($event: MouseEvent, annotations: AnnotationWrapper[], annotationsChanged: EventEmitter) { $event?.stopPropagation(); - this._processObsAndEmit(this._manualAnnotationService.undoRequest(annotation), annotation, annotationsChanged); + + annotations.forEach((annotation) => { + this._processObsAndEmit(this._manualAnnotationService.undoRequest(annotation), annotation, annotationsChanged); + }); } - public convertRecommendationToAnnotation($event: any, annotation: AnnotationWrapper, annotationsChanged: EventEmitter) { + public convertRecommendationToAnnotation($event: any, annotations: AnnotationWrapper[], annotationsChanged: EventEmitter) { $event?.stopPropagation(); - this._processObsAndEmit(this._manualAnnotationService.addRecommendation(annotation), annotation, annotationsChanged); + + annotations.forEach((annotation) => { + this._processObsAndEmit(this._manualAnnotationService.addRecommendation(annotation), annotation, annotationsChanged); + }); + } + + public markTextOnlyAsFalsePositive($event: MouseEvent, annotations: AnnotationWrapper[], annotationsChanged: EventEmitter) { + annotations.forEach((annotation) => { + this._markAsFalsePositive($event, annotation, annotation.value, annotationsChanged); + }); } private _processObsAndEmit(obs: Observable, annotation: AnnotationWrapper, annotationsChanged: EventEmitter) { @@ -71,110 +91,126 @@ export class AnnotationActionsService { ); } - public getViewerAvailableActions(annotation: AnnotationWrapper, annotationsChanged: EventEmitter): {}[] { + public getViewerAvailableActions(annotations: AnnotationWrapper[], annotationsChanged: EventEmitter): {}[] { const availableActions = []; - const annotationPermissions = AnnotationPermissions.forUser(this._permissionsService.currentUser, annotation); + const annotationPermissions = annotations.map((a) => { + return { annotation: a, permissions: AnnotationPermissions.forUser(this._permissionsService.currentUser, a) }; + }); - if (annotationPermissions.canAcceptRecommendation) { + const canAcceptRecommendation = annotationPermissions.reduce((acc, next) => acc && next.permissions.canAcceptRecommendation, true); + if (canAcceptRecommendation) { availableActions.push({ type: 'actionButton', img: '/assets/icons/general/check-alt.svg', title: this._translateService.instant('annotation-actions.accept-recommendation.label'), onClick: () => { this._ngZone.run(() => { - this.convertRecommendationToAnnotation(null, annotation, annotationsChanged); + this.convertRecommendationToAnnotation(null, annotations, annotationsChanged); }); } }); } - if (annotationPermissions.canMarkTextOnlyAsFalsePositive) { - availableActions.push({ - type: 'actionButton', - img: '/assets/icons/general/thumb-down.svg', - title: this._translateService.instant('annotation-actions.remove-annotation.false-positive'), - onClick: () => { - this._ngZone.run(() => { - this.markTextOnlyAsFalsePositive(null, annotation, annotationsChanged); - }); - } - }); - } - - if (annotationPermissions.canAcceptSuggestion) { + const canAcceptSuggestion = annotationPermissions.reduce((acc, next) => acc && next.permissions.canAcceptSuggestion, true); + if (canAcceptSuggestion) { availableActions.push({ type: 'actionButton', img: '/assets/icons/general/check-alt.svg', title: this._translateService.instant('annotation-actions.accept-suggestion.label'), onClick: () => { this._ngZone.run(() => { - this.acceptSuggestion(null, annotation, annotationsChanged); + this.acceptSuggestion(null, annotations, annotationsChanged); }); } }); } - if (annotationPermissions.canUndo) { + const canUndo = annotationPermissions.reduce((acc, next) => acc && next.permissions.canUndo, true); + if (canUndo) { availableActions.push({ type: 'actionButton', img: '/assets/icons/general/undo.svg', title: this._translateService.instant('annotation-actions.undo'), onClick: () => { this._ngZone.run(() => { - this.undoDirectAction(null, annotation, annotationsChanged); + this.undoDirectAction(null, annotations, annotationsChanged); }); } }); } - if (annotationPermissions.canRejectSuggestion) { + const canRejectSuggestion = annotationPermissions.reduce((acc, next) => acc && next.permissions.canRejectSuggestion, true); + if (canRejectSuggestion) { availableActions.push({ type: 'actionButton', img: '/assets/icons/general/close.svg', title: this._translateService.instant('annotation-actions.reject-suggestion'), onClick: () => { this._ngZone.run(() => { - this.rejectSuggestion(null, annotation, annotationsChanged); + this.rejectSuggestion(null, annotations, annotationsChanged); }); } }); } - if (annotationPermissions.canRemoveOrSuggestToRemoveOnlyHere) { + const canRemoveOrSuggestToRemoveOnlyHere = annotationPermissions.reduce( + (acc, next) => acc && next.permissions.canRemoveOrSuggestToRemoveOnlyHere, + true + ); + if (canRemoveOrSuggestToRemoveOnlyHere) { availableActions.push({ type: 'actionButton', img: '/assets/icons/general/close.svg', title: this._translateService.instant('annotation-actions.suggest-remove-annotation'), onClick: () => { this._ngZone.run(() => { - this.suggestRemoveAnnotation(null, annotation, false, annotationsChanged); + this.suggestRemoveAnnotation(null, annotations, false, annotationsChanged); }); } }); } - if (annotationPermissions.canRemoveOrSuggestToRemoveFromDictionary) { + const canRemoveOrSuggestToRemoveFromDictionary = annotationPermissions.reduce( + (acc, next) => acc && next.permissions.canRemoveOrSuggestToRemoveFromDictionary, + true + ); + if (canRemoveOrSuggestToRemoveFromDictionary) { availableActions.push({ type: 'actionButton', img: '/assets/icons/general/trash.svg', title: this._translateService.instant('annotation-actions.remove-annotation.remove-from-dict'), onClick: () => { this._ngZone.run(() => { - this.suggestRemoveAnnotation(null, annotation, true, annotationsChanged); + this.suggestRemoveAnnotation(null, annotations, true, annotationsChanged); }); } }); } - if (annotationPermissions.canMarkAsFalsePositive) { + const canMarkTextOnlyAsFalsePositive = annotationPermissions.reduce((acc, next) => acc && next.permissions.canMarkTextOnlyAsFalsePositive, true); + if (canMarkTextOnlyAsFalsePositive) { availableActions.push({ type: 'actionButton', img: '/assets/icons/general/thumb-down.svg', title: this._translateService.instant('annotation-actions.remove-annotation.false-positive'), onClick: () => { this._ngZone.run(() => { - this.markAsFalsePositive(null, annotation, annotationsChanged); + this.markTextOnlyAsFalsePositive(null, annotations, annotationsChanged); + }); + } + }); + } + + const canMarkAsFalsePositive = annotationPermissions.reduce((acc, next) => acc && next.permissions.canMarkAsFalsePositive, true); + if (canMarkAsFalsePositive) { + availableActions.push({ + type: 'actionButton', + img: '/assets/icons/general/thumb-down.svg', + title: this._translateService.instant('annotation-actions.remove-annotation.false-positive'), + onClick: () => { + this._ngZone.run(() => { + this.markAsFalsePositive(null, annotations, annotationsChanged); }); } }); @@ -183,10 +219,6 @@ export class AnnotationActionsService { return availableActions; } - markTextOnlyAsFalsePositive($event: MouseEvent, annotation: AnnotationWrapper, annotationsChanged: EventEmitter) { - this._markAsFalsePositive($event, annotation, annotation.value, annotationsChanged); - } - private _getFalsePositiveText(annotation: AnnotationWrapper) { if (annotation.canBeMarkedAsFalsePositive) { let text; diff --git a/apps/red-ui/src/app/dialogs/dialog.service.ts b/apps/red-ui/src/app/dialogs/dialog.service.ts index b1db7ce03..77fb9a886 100644 --- a/apps/red-ui/src/app/dialogs/dialog.service.ts +++ b/apps/red-ui/src/app/dialogs/dialog.service.ts @@ -25,6 +25,7 @@ import { AddEditDictionaryDialogComponent } from '../screens/admin/dictionary-li import { AddEditRuleSetDialogComponent } from '../screens/admin/rule-sets-listing-screen/add-edit-rule-set-dialog/add-edit-rule-set-dialog.component'; import { OverwriteFilesDialogComponent } from './overwrite-files-dialog/overwrite-files-dialog.component'; import { EditColorDialogComponent } from '../screens/admin/default-colors-screen/edit-color-dialog/edit-color-dialog.component'; +import { RemoveAnnotationsDialogComponent } from './remove-annotations-dialog/remove-annotations-dialog.component'; const dialogConfig = { width: '662px', @@ -143,26 +144,27 @@ export class DialogService { public openRemoveFromDictionaryDialog( $event: MouseEvent, - annotation: AnnotationWrapper, + annotations: AnnotationWrapper[], removeFromDictionary: boolean, cb?: Function - ): MatDialogRef { + ): MatDialogRef { $event?.stopPropagation(); - const ref = this._dialog.open(ConfirmationDialogComponent, { + const ref = this._dialog.open(RemoveAnnotationsDialogComponent, { ...dialogConfig, - data: new ConfirmationDialogInput({ - title: annotation.isManualRedaction - ? 'confirmation-dialog.remove-manual-redaction.title' - : removeFromDictionary - ? 'confirmation-dialog.remove-from-dictionary.title' - : 'confirmation-dialog.remove-only-here.title', - question: annotation.isManualRedaction - ? 'confirmation-dialog.remove-manual-redaction.question' - : removeFromDictionary - ? 'confirmation-dialog.remove-from-dictionary.question' - : 'confirmation-dialog.remove-only-here.question', - translateParams: { entry: annotation.value, dictionary: annotation.dictionary } - }) + data: annotations + // new ConfirmationDialogInput({ + // title: annotation.isManualRedaction + // ? 'confirmation-dialog.remove-manual-redaction.title' + // : removeFromDictionary + // ? 'confirmation-dialog.remove-from-dictionary.title' + // : 'confirmation-dialog.remove-only-here.title', + // question: annotation.isManualRedaction + // ? 'confirmation-dialog.remove-manual-redaction.question' + // : removeFromDictionary + // ? 'confirmation-dialog.remove-from-dictionary.question' + // : 'confirmation-dialog.remove-only-here.question', + // translateParams: { entry: annotation.value, dictionary: annotation.dictionary } + // }) }); ref.afterClosed().subscribe(async (result) => { if (result) { diff --git a/apps/red-ui/src/app/dialogs/remove-annotations-dialog/remove-annotations-dialog.component.html b/apps/red-ui/src/app/dialogs/remove-annotations-dialog/remove-annotations-dialog.component.html new file mode 100644 index 000000000..ab92d4c55 --- /dev/null +++ b/apps/red-ui/src/app/dialogs/remove-annotations-dialog/remove-annotations-dialog.component.html @@ -0,0 +1,20 @@ +
+
+ +
+ +
+ +
+ +
+ + +
+ + +
diff --git a/apps/red-ui/src/app/dialogs/remove-annotations-dialog/remove-annotations-dialog.component.scss b/apps/red-ui/src/app/dialogs/remove-annotations-dialog/remove-annotations-dialog.component.scss new file mode 100644 index 000000000..e69de29bb diff --git a/apps/red-ui/src/app/dialogs/remove-annotations-dialog/remove-annotations-dialog.component.ts b/apps/red-ui/src/app/dialogs/remove-annotations-dialog/remove-annotations-dialog.component.ts new file mode 100644 index 000000000..77e8037ce --- /dev/null +++ b/apps/red-ui/src/app/dialogs/remove-annotations-dialog/remove-annotations-dialog.component.ts @@ -0,0 +1,28 @@ +import { Component, Inject, OnInit } from '@angular/core'; +import { AnnotationWrapper } from '../../screens/file/model/annotation.wrapper'; +import { TranslateService } from '@ngx-translate/core'; +import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; +import { ConfirmationDialogInput } from '../confirmation-dialog/confirmation-dialog.component'; + +@Component({ + selector: 'redaction-remove-annotations-dialog', + templateUrl: './remove-annotations-dialog.component.html', + styleUrls: ['./remove-annotations-dialog.component.scss'] +}) +export class RemoveAnnotationsDialogComponent implements OnInit { + constructor( + private readonly _translateService: TranslateService, + public dialogRef: MatDialogRef, + @Inject(MAT_DIALOG_DATA) public annotationsToRemove: AnnotationWrapper[] + ) {} + + ngOnInit(): void {} + + deny() { + this.dialogRef.close(); + } + + confirm() { + this.dialogRef.close(true); + } +} diff --git a/apps/red-ui/src/app/screens/file/annotation-actions/annotation-actions.component.html b/apps/red-ui/src/app/screens/file/annotation-actions/annotation-actions.component.html index 6c039d98a..3aec4a365 100644 --- a/apps/red-ui/src/app/screens/file/annotation-actions/annotation-actions.component.html +++ b/apps/red-ui/src/app/screens/file/annotation-actions/annotation-actions.component.html @@ -1,7 +1,7 @@
@@ -102,7 +102,7 @@
@@ -111,7 +111,7 @@
@@ -119,7 +119,7 @@
diff --git a/apps/red-ui/src/app/screens/file/annotation-actions/annotation-actions.component.ts b/apps/red-ui/src/app/screens/file/annotation-actions/annotation-actions.component.ts index 99fe8d9a0..3a11d9ca2 100644 --- a/apps/red-ui/src/app/screens/file/annotation-actions/annotation-actions.component.ts +++ b/apps/red-ui/src/app/screens/file/annotation-actions/annotation-actions.component.ts @@ -23,8 +23,8 @@ export class AnnotationActionsComponent implements OnInit { constructor( public appStateService: AppStateService, - private _permissionsService: PermissionsService, - public annotationActionsService: AnnotationActionsService + public annotationActionsService: AnnotationActionsService, + private _permissionsService: PermissionsService ) {} ngOnInit(): void { diff --git a/apps/red-ui/src/app/screens/file/file-preview-screen/file-preview-screen.component.html b/apps/red-ui/src/app/screens/file/file-preview-screen/file-preview-screen.component.html index 235a829c3..15302d42e 100644 --- a/apps/red-ui/src/app/screens/file/file-preview-screen/file-preview-screen.component.html +++ b/apps/red-ui/src/app/screens/file/file-preview-screen/file-preview-screen.component.html @@ -240,7 +240,7 @@
a.id === annotationId); + handleAnnotationSelected(annotationIds: string[]) { + this.selectedAnnotations = annotationIds.map((annotationId) => this.annotations.find((annotationWrapper) => annotationWrapper.id === annotationId)); this.scrollToSelectedAnnotation(); this._changeDetectorRef.detectChanges(); } @@ -282,15 +282,6 @@ export class FilePreviewScreenComponent implements OnInit, OnDestroy { this._viewerComponent.selectAnnotation(annotation); } - @debounce() - private scrollToSelectedAnnotation() { - if (!this.selectedAnnotation) { - return; - } - const elements: any[] = this._annotationsElement.nativeElement.querySelectorAll(`div[annotation-id="${this.selectedAnnotation.id}"].active`); - this._scrollToFirstElement(elements); - } - selectPage(pageNumber: number) { this._viewerComponent.navigateToPage(pageNumber); this._scrollAnnotationsToPage(pageNumber, 'always'); @@ -312,19 +303,40 @@ export class FilePreviewScreenComponent implements OnInit, OnDestroy { }); } + annotationIsSelected(annotation: AnnotationWrapper) { + return this.selectedAnnotations?.find((a) => a.id === annotation.id); + } + + private get firstSelectedAnnotation() { + return this.selectedAnnotations?.length ? this.selectedAnnotations[0] : null; + } + + private get lastSelectedAnnotation() { + return this.selectedAnnotations?.length ? this.selectedAnnotations[this.selectedAnnotations.length - 1] : null; + } + @debounce() private _scrollViews() { this._scrollQuickNavigation(); this._scrollAnnotations(); } + @debounce() + private scrollToSelectedAnnotation() { + if (!this.selectedAnnotations || this.selectedAnnotations.length === 0) { + return; + } + const elements: any[] = this._annotationsElement.nativeElement.querySelectorAll(`div[annotation-id="${this.firstSelectedAnnotation.id}"].active`); + this._scrollToFirstElement(elements); + } + private _scrollQuickNavigation() { const elements: any[] = this._quickNavigationElement.nativeElement.querySelectorAll(`#quick-nav-page-${this.activeViewerPage}`); this._scrollToFirstElement(elements); } private _scrollAnnotations() { - if (this.selectedAnnotation?.pageNumber === this.activeViewerPage) { + if (this.firstSelectedAnnotation?.pageNumber === this.activeViewerPage) { return; } this._scrollAnnotationsToPage(this.activeViewerPage, 'always'); @@ -402,7 +414,7 @@ export class FilePreviewScreenComponent implements OnInit, OnDestroy { private _selectFirstAnnotationOnCurrentPageIfNecessary() { if ( - (!this.selectedAnnotation || this.activeViewerPage !== this.selectedAnnotation.pageNumber) && + (!this.firstSelectedAnnotation || this.activeViewerPage !== this.firstSelectedAnnotation.pageNumber) && this.displayedPages.indexOf(this.activeViewerPage) >= 0 ) { this.selectAnnotation(this.displayedAnnotations[this.activeViewerPage].annotations[0]); @@ -410,7 +422,7 @@ export class FilePreviewScreenComponent implements OnInit, OnDestroy { } private _navigateAnnotations($event: KeyboardEvent) { - if (!this.selectedAnnotation || this.activeViewerPage !== this.selectedAnnotation.pageNumber) { + if (!this.firstSelectedAnnotation || this.activeViewerPage !== this.firstSelectedAnnotation.pageNumber) { const pageIdx = this.displayedPages.indexOf(this.activeViewerPage); if (pageIdx !== -1) { // Displayed page has annotations @@ -429,10 +441,10 @@ export class FilePreviewScreenComponent implements OnInit, OnDestroy { } } } else { - const page = this.selectedAnnotation.pageNumber; + const page = this.firstSelectedAnnotation.pageNumber; const pageIdx = this.displayedPages.indexOf(page); const annotationsOnPage = this.displayedAnnotations[page].annotations; - const idx = annotationsOnPage.findIndex((a) => a.id === this.selectedAnnotation.id); + const idx = annotationsOnPage.findIndex((a) => a.id === this.firstSelectedAnnotation.id); if ($event.key === 'ArrowDown') { if (idx + 1 !== annotationsOnPage.length) { @@ -669,16 +681,6 @@ export class FilePreviewScreenComponent implements OnInit, OnDestroy { this.viewMode = $event.value; this.updateViewMode(); } - - // - async openSSRFilePreview() { - window.open(`/pdf-preview/${this.projectId}/${this.fileId}`, '_blank'); - } - - async openHTMLDebug() { - window.open(`/html-debug/${this.projectId}/${this.fileId}`, '_blank'); - } - downloadOriginalFile() { this._fileManagementControllerService .downloadOriginalFile(this.projectId, this.fileId, true, this.fileData.fileStatus.lastUploaded, 'response') @@ -697,6 +699,15 @@ export class FilePreviewScreenComponent implements OnInit, OnDestroy { return false; } + // + async openSSRFilePreview() { + window.open(`/pdf-preview/${this.projectId}/${this.fileId}`, '_blank'); + } + + async openHTMLDebug() { + window.open(`/html-debug/${this.projectId}/${this.fileId}`, '_blank'); + } + private _handleIgnoreAnnotationsDrawing() { const allAnnotations = this._instance.annotManager.getAnnotationsList(); const ignoreAnnotations = allAnnotations.filter((a) => a.getCustomData('skipped')); diff --git a/apps/red-ui/src/app/screens/file/pdf-viewer/pdf-viewer.component.ts b/apps/red-ui/src/app/screens/file/pdf-viewer/pdf-viewer.component.ts index c61dee94d..1bcca9675 100644 --- a/apps/red-ui/src/app/screens/file/pdf-viewer/pdf-viewer.component.ts +++ b/apps/red-ui/src/app/screens/file/pdf-viewer/pdf-viewer.component.ts @@ -46,7 +46,7 @@ export class PdfViewerComponent implements OnInit, AfterViewInit, OnChanges { @Input() shouldDeselectAnnotationsOnPageChange = true; @Output() fileReady = new EventEmitter(); - @Output() annotationSelected = new EventEmitter(); + @Output() annotationSelected = new EventEmitter(); @Output() manualAnnotationRequested = new EventEmitter(); @Output() pageChanged = new EventEmitter(); @Output() keyUp = new EventEmitter(); @@ -107,12 +107,12 @@ export class PdfViewerComponent implements OnInit, AfterViewInit, OnChanges { instance.annotManager.on('annotationSelected', (annotationList, action) => { if (action === 'deselected') { - this.annotationSelected.emit(null); + this.annotationSelected.emit([]); this._toggleRectangleAnnotationAction(true); } else { - this._configureAnnotationSpecificActions(annotationList[0]); - this._toggleRectangleAnnotationAction(annotationList[0].ReadOnly); - this.annotationSelected.emit(annotationList[0].Id); + this._configureAnnotationSpecificActions(annotationList); + this._toggleRectangleAnnotationAction(annotationList.length === 1 && annotationList[0].ReadOnly); + this.annotationSelected.emit(annotationList.map((a) => a.Id)); } }); @@ -215,18 +215,19 @@ export class PdfViewerComponent implements OnInit, AfterViewInit, OnChanges { })); } - private _configureAnnotationSpecificActions(viewerAnnotation: Annotations.Annotation) { + private _configureAnnotationSpecificActions(viewerAnnotations: Annotations.Annotation[]) { if (this.canPerformActions) { - const annotation = this.annotations.find((a) => a.id === viewerAnnotation.Id); + const annotationWrappers = viewerAnnotations.map((va) => this.annotations.find((a) => a.id === va.Id)).filter((va) => !!va); this.instance.annotationPopup.update([]); - if (!annotation) { + if (annotationWrappers.length === 0) { this._configureRectangleAnnotationPopup(); return; } // Add hide action as last item - if (annotation.isImage) { + const allAnnotationsHaveImageAction = annotationWrappers.reduce((acc, next) => acc && next.isImage, true); + if (allAnnotationsHaveImageAction) { this.instance.annotationPopup.add([ { type: 'actionButton', @@ -234,14 +235,14 @@ export class PdfViewerComponent implements OnInit, AfterViewInit, OnChanges { title: this._translateService.instant('annotation-actions.hide'), onClick: () => { this._ngZone.run(() => { - this.instance.annotManager.hideAnnotation(viewerAnnotation); + this.instance.annotManager.hideAnnotations(viewerAnnotations); }); } } ]); } - this.instance.annotationPopup.add(this._annotationActionsService.getViewerAvailableActions(annotation, this.annotationsChanged)); + this.instance.annotationPopup.add(this._annotationActionsService.getViewerAvailableActions(annotationWrappers, this.annotationsChanged)); } }