Multi-Select Annotations

This commit is contained in:
Timo 2021-02-23 09:24:44 +02:00
parent 06debdfc7a
commit 79e568abbf
11 changed files with 214 additions and 118 deletions

View File

@ -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,

View File

@ -21,43 +21,63 @@ export class AnnotationActionsService {
private readonly _dialogService: DialogService
) {}
public acceptSuggestion($event: MouseEvent, annotation: AnnotationWrapper, annotationsChanged: EventEmitter<AnnotationWrapper>) {
public acceptSuggestion($event: MouseEvent, annotations: AnnotationWrapper[], annotationsChanged: EventEmitter<AnnotationWrapper>) {
$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<AnnotationWrapper>) {
public rejectSuggestion($event: MouseEvent, annotations: AnnotationWrapper[], annotationsChanged: EventEmitter<AnnotationWrapper>) {
$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<AnnotationWrapper>
) {
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<AnnotationWrapper>) {
this._markAsFalsePositive($event, annotation, this._getFalsePositiveText(annotation), annotationsChanged);
public markAsFalsePositive($event: MouseEvent, annotations: AnnotationWrapper[], annotationsChanged: EventEmitter<AnnotationWrapper>) {
annotations.forEach((annotation) => {
this._markAsFalsePositive($event, annotation, this._getFalsePositiveText(annotation), annotationsChanged);
});
}
public undoDirectAction($event: MouseEvent, annotation: AnnotationWrapper, annotationsChanged: EventEmitter<AnnotationWrapper>) {
public undoDirectAction($event: MouseEvent, annotations: AnnotationWrapper[], annotationsChanged: EventEmitter<AnnotationWrapper>) {
$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<AnnotationWrapper>) {
public convertRecommendationToAnnotation($event: any, annotations: AnnotationWrapper[], annotationsChanged: EventEmitter<AnnotationWrapper>) {
$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<AnnotationWrapper>) {
annotations.forEach((annotation) => {
this._markAsFalsePositive($event, annotation, annotation.value, annotationsChanged);
});
}
private _processObsAndEmit(obs: Observable<any>, annotation: AnnotationWrapper, annotationsChanged: EventEmitter<AnnotationWrapper>) {
@ -71,110 +91,126 @@ export class AnnotationActionsService {
);
}
public getViewerAvailableActions(annotation: AnnotationWrapper, annotationsChanged: EventEmitter<AnnotationWrapper>): {}[] {
public getViewerAvailableActions(annotations: AnnotationWrapper[], annotationsChanged: EventEmitter<AnnotationWrapper>): {}[] {
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<AnnotationWrapper>) {
this._markAsFalsePositive($event, annotation, annotation.value, annotationsChanged);
}
private _getFalsePositiveText(annotation: AnnotationWrapper) {
if (annotation.canBeMarkedAsFalsePositive) {
let text;

View File

@ -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<ConfirmationDialogComponent> {
): MatDialogRef<RemoveAnnotationsDialogComponent> {
$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) {

View File

@ -0,0 +1,20 @@
<section class="dialog">
<div class="dialog-header heading-l">
<!-- {{ confirmationDialogInput.title | translate: confirmationDialogInput.translateParams }}-->
</div>
<div class="dialog-content">
<!-- <p [innerHTML]="confirmationDialogInput.question | translate: confirmationDialogInput.translateParams"></p>-->
</div>
<div class="dialog-actions">
<button (click)="confirm()" color="primary" mat-flat-button>
<!-- {{ confirmationDialogInput.confirmationText | translate: confirmationDialogInput.translateParams }}-->
</button>
<button (click)="deny()" color="primary" mat-flat-button>
<!-- {{ confirmationDialogInput.denyText | translate: confirmationDialogInput.translateParams }}-->
</button>
</div>
<redaction-circle-button icon="red:close" mat-dialog-close class="dialog-close"></redaction-circle-button>
</section>

View File

@ -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<RemoveAnnotationsDialogComponent>,
@Inject(MAT_DIALOG_DATA) public annotationsToRemove: AnnotationWrapper[]
) {}
ngOnInit(): void {}
deny() {
this.dialogRef.close();
}
confirm() {
this.dialogRef.close(true);
}
}

View File

@ -1,7 +1,7 @@
<redaction-hidden-action (action)="logAnnotation(annotation)">
<div [class.visible]="menuOpen" *ngIf="canPerformAnnotationActions" class="annotation-actions">
<redaction-circle-button
(action)="annotationActionsService.convertRecommendationToAnnotation($event, annotation, annotationsChanged)"
(action)="annotationActionsService.convertRecommendationToAnnotation($event, [annotation], annotationsChanged)"
type="dark-bg"
*ngIf="annotationPermissions.canAcceptRecommendation"
tooltipPosition="before"
@ -11,7 +11,7 @@
</redaction-circle-button>
<redaction-circle-button
(action)="annotationActionsService.markTextOnlyAsFalsePositive($event, annotation, annotationsChanged)"
(action)="annotationActionsService.markTextOnlyAsFalsePositive($event, [annotation], annotationsChanged)"
type="dark-bg"
*ngIf="annotationPermissions.canMarkTextOnlyAsFalsePositive"
tooltipPosition="before"
@ -21,7 +21,7 @@
</redaction-circle-button>
<redaction-circle-button
(action)="annotationActionsService.acceptSuggestion($event, annotation, annotationsChanged)"
(action)="annotationActionsService.acceptSuggestion($event, [annotation], annotationsChanged)"
type="dark-bg"
*ngIf="annotationPermissions.canAcceptSuggestion"
tooltipPosition="before"
@ -31,7 +31,7 @@
</redaction-circle-button>
<redaction-circle-button
(action)="annotationActionsService.undoDirectAction($event, annotation, annotationsChanged)"
(action)="annotationActionsService.undoDirectAction($event, [annotation], annotationsChanged)"
*ngIf="annotationPermissions.canUndo"
type="dark-bg"
icon="red:undo"
@ -61,7 +61,7 @@
</redaction-circle-button>
<redaction-circle-button
(action)="annotationActionsService.rejectSuggestion($event, annotation, annotationsChanged)"
(action)="annotationActionsService.rejectSuggestion($event, [annotation], annotationsChanged)"
type="dark-bg"
icon="red:close"
*ngIf="annotationPermissions.canRejectSuggestion"
@ -71,7 +71,7 @@
</redaction-circle-button>
<redaction-circle-button
(action)="annotationActionsService.suggestRemoveAnnotation($event, annotation, false, annotationsChanged)"
(action)="annotationActionsService.suggestRemoveAnnotation($event, [annotation], false, annotationsChanged)"
type="dark-bg"
icon="red:trash"
*ngIf="annotationPermissions.canRemoveOrSuggestToRemoveOnlyHere && !annotationPermissions.canPerformMultipleRemoveActions"
@ -94,7 +94,7 @@
<mat-menu #menu="matMenu" (closed)="onMenuClosed()" xPosition="before">
<div
(click)="annotationActionsService.suggestRemoveAnnotation($event, annotation, true, annotationsChanged)"
(click)="annotationActionsService.suggestRemoveAnnotation($event, [annotation], true, annotationsChanged)"
mat-menu-item
*ngIf="annotationPermissions.canRemoveOrSuggestToRemoveFromDictionary"
>
@ -102,7 +102,7 @@
<div [translate]="'annotation-actions.remove-annotation.remove-from-dict'"></div>
</div>
<div
(click)="annotationActionsService.suggestRemoveAnnotation($event, annotation, false, annotationsChanged)"
(click)="annotationActionsService.suggestRemoveAnnotation($event, [annotation], false, annotationsChanged)"
mat-menu-item
*ngIf="annotationPermissions.canRemoveOrSuggestToRemoveOnlyHere"
>
@ -111,7 +111,7 @@
</div>
<div
(click)="annotationActionsService.markAsFalsePositive($event, annotation, annotationsChanged)"
(click)="annotationActionsService.markAsFalsePositive($event, [annotation], annotationsChanged)"
mat-menu-item
*ngIf="annotationPermissions.canMarkAsFalsePositive"
>
@ -119,7 +119,7 @@
<div translate="annotation-actions.remove-annotation.false-positive"></div>
</div>
<div
(click)="annotationActionsService.markTextOnlyAsFalsePositive($event, annotation, annotationsChanged)"
(click)="annotationActionsService.markTextOnlyAsFalsePositive($event, [annotation], annotationsChanged)"
mat-menu-item
*ngIf="annotationPermissions.canMarkTextOnlyAsFalsePositive"
>

View File

@ -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 {

View File

@ -240,7 +240,7 @@
<div
(click)="annotationClicked(annotation)"
*ngFor="let annotation of displayedAnnotations[activeViewerPage]?.annotations"
[ngClass]="{ active: selectedAnnotation?.id === annotation.id }"
[class.active]="annotationIsSelected(annotation)"
attr.annotation-id="{{ annotation.id }}"
attr.annotation-page="{{ activeViewerPage }}"
class="annotation"

View File

@ -123,7 +123,7 @@ export class FilePreviewScreenComponent implements OnInit, OnDestroy {
annotationData: AnnotationData;
displayedAnnotations: { [key: number]: { annotations: AnnotationWrapper[] } } = {};
selectedAnnotation: AnnotationWrapper;
selectedAnnotations: AnnotationWrapper[];
pagesPanelActive = true;
viewReady = false;
annotationFilters: FilterModel[];
@ -267,8 +267,8 @@ export class FilePreviewScreenComponent implements OnInit, OnDestroy {
);
}
handleAnnotationSelected(annotationId: string) {
this.selectedAnnotation = this.annotations.find((a) => 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();
}
// <!-- Dev Mode Features-->
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;
}
// <!-- Dev Mode Features-->
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'));

View File

@ -46,7 +46,7 @@ export class PdfViewerComponent implements OnInit, AfterViewInit, OnChanges {
@Input() shouldDeselectAnnotationsOnPageChange = true;
@Output() fileReady = new EventEmitter();
@Output() annotationSelected = new EventEmitter<string>();
@Output() annotationSelected = new EventEmitter<string[]>();
@Output() manualAnnotationRequested = new EventEmitter<ManualRedactionEntryWrapper>();
@Output() pageChanged = new EventEmitter<number>();
@Output() keyUp = new EventEmitter<KeyboardEvent>();
@ -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));
}
}