Pull request #303: RED-2484

Merge in RED/ui from RED-2484 to master

* commit '0504086fefd96d5800607cb1f1fb6c0f9425ab22':
  show manual redactions and their pages when excluded
  little refactor for pdf viewer
  add operator & fix icon name
  add excluded page icon
  update view accordingly to excluded pages design
This commit is contained in:
Dan Percic 2021-11-06 09:19:36 +01:00
commit 1e14469941
14 changed files with 393 additions and 291 deletions

View File

@ -6,9 +6,9 @@ import { UserService } from '@services/user.service';
import { DossiersService } from '@services/entity-services/dossiers.service'; import { DossiersService } from '@services/entity-services/dossiers.service';
import { NotificationsService } from '@services/notifications.service'; import { NotificationsService } from '@services/notifications.service';
import { Notification } from '@red/domain'; import { Notification } from '@red/domain';
import { distinctUntilChanged, map, shareReplay } from 'rxjs/operators'; import { distinctUntilChanged, map } from 'rxjs/operators';
import { BehaviorSubject, Observable } from 'rxjs'; import { BehaviorSubject, Observable } from 'rxjs';
import { List } from '@iqser/common-ui'; import { List, shareLast } from '@iqser/common-ui';
interface NotificationsGroup { interface NotificationsGroup {
date: string; date: string;
@ -25,7 +25,7 @@ export class NotificationsComponent implements OnInit {
notifications$: Observable<Notification[]>; notifications$: Observable<Notification[]>;
hasUnreadNotifications$: Observable<boolean>; hasUnreadNotifications$: Observable<boolean>;
groupedNotifications$: Observable<NotificationsGroup[]>; groupedNotifications$: Observable<NotificationsGroup[]>;
private _notifications$ = new BehaviorSubject([]); private _notifications$ = new BehaviorSubject<Notification[]>([]);
constructor( constructor(
private readonly _translateService: TranslateService, private readonly _translateService: TranslateService,
@ -35,12 +35,12 @@ export class NotificationsComponent implements OnInit {
private readonly _dossiersService: DossiersService, private readonly _dossiersService: DossiersService,
private readonly _datePipe: DatePipe, private readonly _datePipe: DatePipe,
) { ) {
this.notifications$ = this._notifications$.asObservable(); this.notifications$ = this._notifications$.asObservable().pipe(shareLast());
this.groupedNotifications$ = this.notifications$.pipe(map(notifications => this._groupNotifications(notifications))); this.groupedNotifications$ = this.notifications$.pipe(map(notifications => this._groupNotifications(notifications)));
this.hasUnreadNotifications$ = this.notifications$.pipe( this.hasUnreadNotifications$ = this.notifications$.pipe(
map(notifications => notifications.filter(n => !n.readDate).length > 0), map(notifications => notifications.filter(n => !n.readDate).length > 0),
distinctUntilChanged(), distinctUntilChanged(),
shareReplay(1), shareLast(),
); );
} }

View File

@ -24,7 +24,7 @@ export class FileDataModel {
const entries: RedactionLogEntryWrapper[] = this._convertData(); const entries: RedactionLogEntryWrapper[] = this._convertData();
let allAnnotations = entries let allAnnotations = entries
.map(entry => AnnotationWrapper.fromData(entry)) .map(entry => AnnotationWrapper.fromData(entry))
.filter(ann => !this.file.excludedPages.includes(ann.pageNumber)); .filter(ann => ann.manual || !this.file.excludedPages.includes(ann.pageNumber));
if (!areDevFeaturesEnabled) { if (!areDevFeaturesEnabled) {
allAnnotations = allAnnotations.filter(annotation => !annotation.isFalsePositive); allAnnotations = allAnnotations.filter(annotation => !annotation.isFalsePositive);

View File

@ -1,10 +1,18 @@
import { IManualRedactionEntry } from '@red/domain'; import { IManualRedactionEntry } from '@red/domain';
export const ManualRedactionEntryTypes = {
DICTIONARY: 'DICTIONARY',
REDACTION: 'REDACTION',
FALSE_POSITIVE: 'FALSE_POSITIVE',
} as const;
export type ManualRedactionEntryType = keyof typeof ManualRedactionEntryTypes;
export class ManualRedactionEntryWrapper { export class ManualRedactionEntryWrapper {
constructor( constructor(
readonly quads: any, readonly quads: any,
readonly manualRedactionEntry: IManualRedactionEntry, readonly manualRedactionEntry: IManualRedactionEntry,
readonly type: 'DICTIONARY' | 'REDACTION' | 'FALSE_POSITIVE', readonly type: ManualRedactionEntryType,
readonly annotationType: 'TEXT' | 'RECTANGLE' = 'TEXT', readonly annotationType: 'TEXT' | 'RECTANGLE' = 'TEXT',
readonly rectId?: string, readonly rectId?: string,
) {} ) {}

View File

@ -88,6 +88,7 @@
[activeSelection]="pageHasSelection(pageNumber)" [activeSelection]="pageHasSelection(pageNumber)"
[active]="pageNumber === activeViewerPage" [active]="pageNumber === activeViewerPage"
[number]="pageNumber" [number]="pageNumber"
[showDottedIcon]="hasOnlyManualRedactionsAndNotExcluded(pageNumber)"
[viewedPages]="fileData?.viewedPages" [viewedPages]="fileData?.viewedPages"
></redaction-page-indicator> ></redaction-page-indicator>
</div> </div>
@ -104,11 +105,21 @@
<div style="overflow: hidden; width: 100%"> <div style="overflow: hidden; width: 100%">
<ng-container *ngIf="!excludePages"> <ng-container *ngIf="!excludePages">
<div [attr.anotation-page-header]="activeViewerPage" class="page-separator"> <div [attr.anotation-page-header]="activeViewerPage" [class.padding-left-0]="isExcluded" class="page-separator">
<span *ngIf="!!activeViewerPage" class="all-caps-label"> <span *ngIf="!!activeViewerPage" class="flex-align-items-center">
<span translate="page"></span> {{ activeViewerPage }} - <iqser-circle-button
{{ activeAnnotations?.length || 0 }} (action)="viewExcludePages()"
<span [translate]="activeAnnotations?.length === 1 ? 'annotation' : 'annotations'"></span> *ngIf="isExcluded"
[tooltip]="'file-preview.excluded-from-redaction' | translate | capitalize"
icon="red:exclude-pages"
tooltipPosition="above"
></iqser-circle-button>
<span class="all-caps-label">
<span translate="page"></span> {{ activeViewerPage }} -
{{ activeAnnotations?.length || 0 }}
<span [translate]="activeAnnotations?.length === 1 ? 'annotation' : 'annotations'"></span>
</span>
</span> </span>
<div *ngIf="multiSelectActive"> <div *ngIf="multiSelectActive">
@ -141,13 +152,9 @@
[verticalPadding]="40" [verticalPadding]="40"
icon="iqser:document" icon="iqser:document"
> >
<ng-container *ngIf="fileData?.file?.excludedPages?.includes(activeViewerPage)"> <ng-container *ngIf="isExcluded">
{{ 'file-preview.tabs.annotations.page-is' | translate }} {{ 'file-preview.tabs.annotations.page-is' | translate }}
<a <a (click)="viewExcludePages()" class="with-underline" translate="file-preview.excluded-from-redaction"></a
(click)="actionPerformed.emit('view-exclude-pages')"
class="with-underline"
translate="file-preview.excluded-from-redaction"
></a
>. >.
</ng-container> </ng-container>
</iqser-empty-state> </iqser-empty-state>
@ -173,11 +180,11 @@
<redaction-annotations-list <redaction-annotations-list
(deselectAnnotations)="deselectAnnotations.emit($event)" (deselectAnnotations)="deselectAnnotations.emit($event)"
(pagesPanelActive)="pagesPanelActive = $event" (pagesPanelActive)="pagesPanelActive = $event"
(selectAnnotations)="selectAnnotations.emit($event)"
[(multiSelectActive)]="multiSelectActive" [(multiSelectActive)]="multiSelectActive"
[activeViewerPage]="activeViewerPage" [activeViewerPage]="activeViewerPage"
[annotationActionsTemplate]="annotationActionsTemplate" [annotationActionsTemplate]="annotationActionsTemplate"
[annotations]="(displayedAnnotations$ | async)?.get(activeViewerPage)" [annotations]="(displayedAnnotations$ | async)?.get(activeViewerPage)"
(selectAnnotations)="selectAnnotations.emit($event)"
[canMultiSelect]="!isReadOnly" [canMultiSelect]="!isReadOnly"
[selectedAnnotations]="selectedAnnotations" [selectedAnnotations]="selectedAnnotations"
></redaction-annotations-list> ></redaction-annotations-list>

View File

@ -167,3 +167,11 @@
} }
} }
} }
.padding-left-0 {
padding-left: 0 !important;
}
::ng-deep .page-separator iqser-circle-button mat-icon {
color: var(--iqser-primary);
}

View File

@ -90,6 +90,10 @@ export class FileWorkloadComponent {
return !this._permissionsService.canPerformAnnotationActions(); return !this._permissionsService.canPerformAnnotationActions();
} }
get isExcluded(): boolean {
return this.fileData?.file?.excludedPages?.includes(this.activeViewerPage);
}
private get _firstSelectedAnnotation() { private get _firstSelectedAnnotation() {
return this.selectedAnnotations?.length ? this.selectedAnnotations[0] : null; return this.selectedAnnotations?.length ? this.selectedAnnotations[0] : null;
} }
@ -114,6 +118,11 @@ export class FileWorkloadComponent {
} }
} }
hasOnlyManualRedactionsAndNotExcluded(pageNumber: number): boolean {
const hasOnlyManualRedactions = this.displayedAnnotations.get(pageNumber).every(annotation => annotation.manual);
return hasOnlyManualRedactions && this.fileData.file.excludedPages.includes(pageNumber);
}
pageHasSelection(page: number) { pageHasSelection(page: number) {
return this.multiSelectActive && !!this.selectedAnnotations?.find(a => a.pageNumber === page); return this.multiSelectActive && !!this.selectedAnnotations?.find(a => a.pageNumber === page);
} }
@ -225,6 +234,10 @@ export class FileWorkloadComponent {
this.selectPage.emit(this._nextPageWithAnnotations()); this.selectPage.emit(this._nextPageWithAnnotations());
} }
viewExcludePages(): void {
this.actionPerformed.emit('view-exclude-pages');
}
private _filterAnnotations( private _filterAnnotations(
annotations: AnnotationWrapper[], annotations: AnnotationWrapper[],
primary: INestedFilter[], primary: INestedFilter[],

View File

@ -6,7 +6,7 @@
[id]="'quick-nav-page-' + number" [id]="'quick-nav-page-' + number"
class="page-wrapper" class="page-wrapper"
> >
<mat-icon svgIcon="red:page"></mat-icon> <mat-icon [svgIcon]="showDottedIcon ? 'red:excluded-page' : 'red:page'"></mat-icon>
<div class="text"> <div class="text">
{{ number }} {{ number }}
</div> </div>

View File

@ -2,18 +2,19 @@ import { Component, EventEmitter, Input, OnChanges, OnDestroy, OnInit, Output, S
import { AppStateService } from '@state/app-state.service'; import { AppStateService } from '@state/app-state.service';
import { PermissionsService } from '@services/permissions.service'; import { PermissionsService } from '@services/permissions.service';
import { ConfigService } from '@services/config.service'; import { ConfigService } from '@services/config.service';
import { Subscription } from 'rxjs';
import { DossiersService } from '@services/entity-services/dossiers.service'; import { DossiersService } from '@services/entity-services/dossiers.service';
import { ViewedPagesService } from '../../shared/services/viewed-pages.service'; import { ViewedPagesService } from '../../shared/services/viewed-pages.service';
import { IViewedPage } from '@red/domain'; import { IViewedPage } from '@red/domain';
import { AutoUnsubscribe } from '@iqser/common-ui';
@Component({ @Component({
selector: 'redaction-page-indicator', selector: 'redaction-page-indicator',
templateUrl: './page-indicator.component.html', templateUrl: './page-indicator.component.html',
styleUrls: ['./page-indicator.component.scss'], styleUrls: ['./page-indicator.component.scss'],
}) })
export class PageIndicatorComponent implements OnChanges, OnInit, OnDestroy { export class PageIndicatorComponent extends AutoUnsubscribe implements OnChanges, OnInit, OnDestroy {
@Input() active: boolean; @Input() active: boolean;
@Input() showDottedIcon = false;
@Input() number: number; @Input() number: number;
@Input() viewedPages: IViewedPage[]; @Input() viewedPages: IViewedPage[];
@Input() activeSelection = false; @Input() activeSelection = false;
@ -22,7 +23,6 @@ export class PageIndicatorComponent implements OnChanges, OnInit, OnDestroy {
pageReadTimeout: number = null; pageReadTimeout: number = null;
canMarkPagesAsViewed: boolean; canMarkPagesAsViewed: boolean;
private _subscription: Subscription;
constructor( constructor(
private readonly _viewedPagesService: ViewedPagesService, private readonly _viewedPagesService: ViewedPagesService,
@ -30,7 +30,9 @@ export class PageIndicatorComponent implements OnChanges, OnInit, OnDestroy {
private readonly _dossiersService: DossiersService, private readonly _dossiersService: DossiersService,
private readonly _configService: ConfigService, private readonly _configService: ConfigService,
private readonly _permissionService: PermissionsService, private readonly _permissionService: PermissionsService,
) {} ) {
super();
}
get activePage() { get activePage() {
return this.viewedPages?.find(p => p.page === this.number); return this.viewedPages?.find(p => p.page === this.number);
@ -46,7 +48,7 @@ export class PageIndicatorComponent implements OnChanges, OnInit, OnDestroy {
} }
ngOnInit(): void { ngOnInit(): void {
this._subscription = this._appStateService.fileChanged$.subscribe(() => { this.addSubscription = this._appStateService.fileChanged$.subscribe(() => {
if (this.canMarkPagesAsViewed !== this._permissionService.canMarkPagesAsViewed()) { if (this.canMarkPagesAsViewed !== this._permissionService.canMarkPagesAsViewed()) {
this.canMarkPagesAsViewed = this._permissionService.canMarkPagesAsViewed(); this.canMarkPagesAsViewed = this._permissionService.canMarkPagesAsViewed();
this._handlePageRead(); this._handlePageRead();
@ -70,24 +72,21 @@ export class PageIndicatorComponent implements OnChanges, OnInit, OnDestroy {
} }
} }
ngOnDestroy(): void {
if (this._subscription) {
this._subscription.unsubscribe();
}
}
private _handlePageRead() { private _handlePageRead() {
if (this.canMarkPagesAsViewed) { if (!this.canMarkPagesAsViewed) {
if (this.pageReadTimeout) { return;
clearTimeout(this.pageReadTimeout); }
}
if (this.active && !this.read) { if (this.pageReadTimeout) {
this.pageReadTimeout = window.setTimeout(() => { clearTimeout(this.pageReadTimeout);
if (this.active && !this.read) { }
this._markPageRead();
} if (this.active && !this.read) {
}, this._configService.values.AUTO_READ_TIME * 1000); this.pageReadTimeout = window.setTimeout(() => {
} if (this.active && !this.read) {
this._markPageRead();
}
}, this._configService.values.AUTO_READ_TIME * 1000);
} }
} }
@ -109,7 +108,7 @@ export class PageIndicatorComponent implements OnChanges, OnInit, OnDestroy {
// } // }
private _markPageRead() { private _markPageRead() {
this._viewedPagesService this.addSubscription = this._viewedPagesService
.addPage({ page: this.number }, this._dossiersService.activeDossierId, this._appStateService.activeFileId) .addPage({ page: this.number }, this._dossiersService.activeDossierId, this._appStateService.activeFileId)
.subscribe(() => { .subscribe(() => {
if (this.activePage) { if (this.activePage) {
@ -121,7 +120,7 @@ export class PageIndicatorComponent implements OnChanges, OnInit, OnDestroy {
} }
private _markPageUnread() { private _markPageUnread() {
this._viewedPagesService this.addSubscription = this._viewedPagesService
.removePage(this._dossiersService.activeDossierId, this._appStateService.activeFileId, this.number) .removePage(this._dossiersService.activeDossierId, this._appStateService.activeFileId, this.number)
.subscribe(() => { .subscribe(() => {
this.viewedPages?.splice( this.viewedPages?.splice(

View File

@ -14,7 +14,11 @@ import {
import { File, IManualRedactionEntry, ViewMode } from '@red/domain'; import { File, IManualRedactionEntry, ViewMode } from '@red/domain';
import WebViewer, { Core, WebViewerInstance } from '@pdftron/webviewer'; import WebViewer, { Core, WebViewerInstance } from '@pdftron/webviewer';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
import { ManualRedactionEntryWrapper } from '@models/file/manual-redaction-entry.wrapper'; import {
ManualRedactionEntryType,
ManualRedactionEntryTypes,
ManualRedactionEntryWrapper,
} from '@models/file/manual-redaction-entry.wrapper';
import { AnnotationWrapper } from '@models/file/annotation.wrapper'; import { AnnotationWrapper } from '@models/file/annotation.wrapper';
import { ManualAnnotationService } from '../../services/manual-annotation.service'; import { ManualAnnotationService } from '../../services/manual-annotation.service';
import { environment } from '@environments/environment'; import { environment } from '@environments/environment';
@ -33,6 +37,20 @@ import Tools = Core.Tools;
import TextTool = Tools.TextTool; import TextTool = Tools.TextTool;
import Annotation = Core.Annotations.Annotation; import Annotation = Core.Annotations.Annotation;
const ALLOWED_KEYBOARD_SHORTCUTS = ['+', '-', 'p', 'r', 'Escape'] as const;
const dataElements = {
ADD_REDACTION: 'add-redaction',
ADD_DICTIONARY: 'add-dictionary',
ADD_RECTANGLE: 'add-rectangle',
ADD_FALSE_POSITIVE: 'add-false-positive',
SHAPE_TOOL_GROUP_BUTTON: 'shapeToolGroupButton',
RECTANGLE_TOOL_DIVIDER: 'rectangleToolDivider',
ANNOTATION_POPUP: 'annotationPopup',
COMPARE_BUTTON: 'compareButton',
CLOSE_COMPARE_BUTTON: 'closeCompareButton',
COMPARE_TOOL_DIVIDER: 'compareToolDivider',
} as const;
@Component({ @Component({
selector: 'redaction-pdf-viewer', selector: 'redaction-pdf-viewer',
templateUrl: './pdf-viewer.component.html', templateUrl: './pdf-viewer.component.html',
@ -55,10 +73,11 @@ export class PdfViewerComponent implements OnInit, OnChanges {
@ViewChild('viewer', { static: true }) viewer: ElementRef; @ViewChild('viewer', { static: true }) viewer: ElementRef;
@ViewChild('compareFileInput', { static: true }) compareFileInput: ElementRef; @ViewChild('compareFileInput', { static: true }) compareFileInput: ElementRef;
instance: WebViewerInstance; instance: WebViewerInstance;
documentViewer: Core.DocumentViewer;
annotationManager: Core.AnnotationManager;
utils: PdfViewerUtils; utils: PdfViewerUtils;
private _selectedText = ''; private _selectedText = '';
private _firstPageChange = true; private _firstPageChange = true;
private readonly _allowedKeyboardShortcuts = ['+', '-', 'p', 'r', 'Escape'];
constructor( constructor(
@Inject(BASE_HREF) private readonly _baseHref: string, @Inject(BASE_HREF) private readonly _baseHref: string,
@ -86,96 +105,90 @@ export class PdfViewerComponent implements OnInit, OnChanges {
} }
async ngOnInit() { async ngOnInit() {
this._documentLoaded = this._documentLoaded.bind(this); this._setReadyAndInitialState = this._setReadyAndInitialState.bind(this);
await this._loadViewer(); await this._loadViewer();
} }
ngOnChanges(changes: SimpleChanges): void { ngOnChanges(changes: SimpleChanges): void {
if (this.instance) { if (!this.instance) {
if (changes.fileData) { return;
this._loadDocument();
}
if (changes.canPerformActions) {
this._handleCustomActions();
}
if (changes.multiSelectActive) {
this.utils.multiSelectActive = this.multiSelectActive;
}
} }
}
setInitialViewerState() { if (changes.fileData) {
// viewer init this._loadDocument();
this.instance.UI.setFitMode('FitPage'); }
const instanceDisplayMode = this.instance.Core.documentViewer.getDisplayModeManager().getDisplayMode(); if (changes.canPerformActions) {
instanceDisplayMode.mode = this.viewMode === 'STANDARD' ? 'Single' : 'Facing'; this._handleCustomActions();
this.instance.Core.documentViewer.getDisplayModeManager().setDisplayMode(instanceDisplayMode); }
if (changes.multiSelectActive) {
this.utils.multiSelectActive = this.multiSelectActive;
}
} }
uploadFile(files: any) { uploadFile(files: any) {
const fileToCompare = files[0]; const fileToCompare = files[0];
this.compareFileInput.nativeElement.value = null; this.compareFileInput.nativeElement.value = null;
const fileReader = new FileReader(); if (!fileToCompare) {
console.error('No file to compare!');
if (fileToCompare) { return;
fileReader.onload = async () => {
const pdfData = fileReader.result;
const pdfNet = this.instance.Core.PDFNet;
await pdfNet.initialize(environment.licenseKey ? atob(environment.licenseKey) : null);
const mergedDocument = await pdfNet.PDFDoc.create();
const compareDocument = await pdfNet.PDFDoc.createFromBuffer(<any>pdfData);
const currentDocument = await pdfNet.PDFDoc.createFromBuffer(await this.fileData.arrayBuffer());
const currentDocumentPageCount = await currentDocument.getPageCount();
const compareDocumentPageCount = await compareDocument.getPageCount();
const loadCompareDocument = async () => {
this._loadingService.start();
this.utils.ready = false;
await loadCompareDocumentWrapper(
currentDocumentPageCount,
compareDocumentPageCount,
currentDocument,
compareDocument,
mergedDocument,
this.instance,
this.file,
() => {
this.viewMode = 'COMPARE';
},
() => {
this.utils.navigateToPage(1);
},
this.instance.Core.PDFNet,
);
this._loadingService.stop();
};
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();
}
};
} }
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 currentDocument = await pdfNet.PDFDoc.createFromBuffer(await this.fileData.arrayBuffer());
const loadCompareDocument = async () => {
this._loadingService.start();
this.utils.ready = false;
const mergedDocument = await pdfNet.PDFDoc.create();
await loadCompareDocumentWrapper(
currentDocument,
compareDocument,
mergedDocument,
this.instance,
this.file,
() => {
this.viewMode = 'COMPARE';
},
() => {
this.utils.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); fileReader.readAsArrayBuffer(fileToCompare);
} }
@ -187,11 +200,18 @@ export class PdfViewerComponent implements OnInit, OnChanges {
this.instance.UI.loadDocument(currentDocument, { this.instance.UI.loadDocument(currentDocument, {
filename: this.file ? this.file.filename : 'document.pdf', filename: this.file ? this.file.filename : 'document.pdf',
}); });
this.instance.UI.disableElements(['closeCompareButton']); this.instance.UI.disableElements([dataElements.CLOSE_COMPARE_BUTTON]);
this.instance.UI.enableElements(['compareButton']); this.instance.UI.enableElements([dataElements.COMPARE_BUTTON]);
this.utils.navigateToPage(1); this.utils.navigateToPage(1);
} }
private _setInitialDisplayMode() {
this.instance.UI.setFitMode('FitPage');
const instanceDisplayMode = this.documentViewer.getDisplayModeManager().getDisplayMode();
instanceDisplayMode.mode = this.viewMode === 'STANDARD' ? 'Single' : 'Facing';
this.documentViewer.getDisplayModeManager().setDisplayMode(instanceDisplayMode);
}
private _convertPath(path: string): string { private _convertPath(path: string): string {
return this._baseHref + path; return this._baseHref + path;
} }
@ -208,6 +228,8 @@ export class PdfViewerComponent implements OnInit, OnChanges {
this.viewer.nativeElement, this.viewer.nativeElement,
); );
this.documentViewer = this.instance.Core.documentViewer;
this.annotationManager = this.instance.Core.annotationManager;
this.utils = new PdfViewerUtils(this.instance, this.viewMode, this.multiSelectActive); this.utils = new PdfViewerUtils(this.instance, this.viewMode, this.multiSelectActive);
this._setSelectionMode(); this._setSelectionMode();
@ -215,8 +237,8 @@ export class PdfViewerComponent implements OnInit, OnChanges {
this.utils.disableHotkeys(); this.utils.disableHotkeys();
this._configureTextPopup(); this._configureTextPopup();
this.instance.Core.annotationManager.on('annotationSelected', (annotations, action) => { this.annotationManager.on('annotationSelected', (annotations, action) => {
this.annotationSelected.emit(this.instance.Core.annotationManager.getSelectedAnnotations().map(ann => ann.Id)); this.annotationSelected.emit(this.annotationManager.getSelectedAnnotations().map(ann => ann.Id));
if (action === 'deselected') { if (action === 'deselected') {
this._toggleRectangleAnnotationAction(true); this._toggleRectangleAnnotationAction(true);
} else { } else {
@ -225,16 +247,16 @@ export class PdfViewerComponent implements OnInit, OnChanges {
} }
}); });
this.instance.Core.annotationManager.on('annotationChanged', annotations => { this.annotationManager.on('annotationChanged', annotations => {
// when a rectangle is drawn, // when a rectangle is drawn,
// it returns one annotation with tool name 'AnnotationCreateRectangle; // it returns one annotation with tool name 'AnnotationCreateRectangle;
// this will auto select rectangle after drawing // this will auto select rectangle after drawing
if (annotations.length === 1 && annotations[0].ToolName === 'AnnotationCreateRectangle') { if (annotations.length === 1 && annotations[0].ToolName === 'AnnotationCreateRectangle') {
this.instance.Core.annotationManager.selectAnnotations(annotations); this.annotationManager.selectAnnotations(annotations);
} }
}); });
this.instance.Core.documentViewer.on('pageNumberUpdated', pageNumber => { this.documentViewer.on('pageNumberUpdated', pageNumber => {
if (this.shouldDeselectAnnotationsOnPageChange) { if (this.shouldDeselectAnnotationsOnPageChange) {
this.utils.deselectAllAnnotations(); this.utils.deselectAllAnnotations();
} }
@ -250,9 +272,9 @@ export class PdfViewerComponent implements OnInit, OnChanges {
this._handleCustomActions(); this._handleCustomActions();
}); });
this.instance.Core.documentViewer.on('documentLoaded', this._documentLoaded); this.documentViewer.on('documentLoaded', this._setReadyAndInitialState);
this.instance.Core.documentViewer.on('keyUp', $event => { this.documentViewer.on('keyUp', $event => {
// arrows and full-screen // arrows and full-screen
if ($event.target?.tagName?.toLowerCase() !== 'input') { if ($event.target?.tagName?.toLowerCase() !== 'input') {
if ($event.key.startsWith('Arrow') || $event.key === 'f') { if ($event.key.startsWith('Arrow') || $event.key === 'f') {
@ -264,18 +286,20 @@ export class PdfViewerComponent implements OnInit, OnChanges {
} }
} }
if (this._allowedKeyboardShortcuts.indexOf($event.key) < 0) { if (ALLOWED_KEYBOARD_SHORTCUTS.indexOf($event.key) < 0) {
$event.preventDefault(); $event.preventDefault();
$event.stopPropagation(); $event.stopPropagation();
} }
}); });
this.instance.Core.documentViewer.on('textSelected', (quads, selectedText) => { this.documentViewer.on('textSelected', (quads, selectedText) => {
this._selectedText = selectedText; this._selectedText = selectedText;
if (selectedText.length > 2 && this.canPerformActions) { const textActions = [dataElements.ADD_DICTIONARY, dataElements.ADD_FALSE_POSITIVE];
this.instance.UI.enableElements(['add-dictionary', 'add-false-positive']);
if (selectedText.length > 2 && this.canPerformActions && !this.utils.isCurrentPageExcluded) {
this.instance.UI.enableElements(textActions);
} else { } else {
this.instance.UI.disableElements(['add-dictionary', 'add-false-positive']); this.instance.UI.disableElements(textActions);
} }
}); });
@ -286,7 +310,7 @@ export class PdfViewerComponent implements OnInit, OnChanges {
inputElement.value = ''; inputElement.value = '';
}, 0); }, 0);
if (!event.detail.isVisible) { if (!event.detail.isVisible) {
this.instance.Core.documentViewer.clearSearchResults(); this.documentViewer.clearSearchResults();
} }
} }
}); });
@ -295,15 +319,15 @@ export class PdfViewerComponent implements OnInit, OnChanges {
} }
private _setSelectionMode(): void { private _setSelectionMode(): void {
const textTool = (<unknown> this.instance.Core.Tools.TextTool) as TextTool; const textTool = this.instance.Core.Tools.TextTool as unknown as TextTool;
textTool.SELECTION_MODE = this._configService.values.SELECTION_MODE; textTool.SELECTION_MODE = this._configService.values.SELECTION_MODE;
} }
private _toggleRectangleAnnotationAction(readonly: boolean) { private _toggleRectangleAnnotationAction(readonly = false) {
if (!readonly) { if (!readonly) {
this.instance.UI.enableElements(['add-rectangle']); this.instance.UI.enableElements([dataElements.ADD_RECTANGLE]);
} else { } else {
this.instance.UI.disableElements(['add-rectangle']); this.instance.UI.disableElements([dataElements.ADD_RECTANGLE]);
} }
} }
@ -331,50 +355,59 @@ export class PdfViewerComponent implements OnInit, OnChanges {
'annotationGroupButton', 'annotationGroupButton',
]); ]);
this.instance.UI.setHeaderItems(header => { const headerItems = [
const originalHeaderItems = header.getItems(); {
originalHeaderItems.splice(8, 0, {
type: 'divider', type: 'divider',
dataElement: 'rectangleToolDivider', dataElement: dataElements.RECTANGLE_TOOL_DIVIDER,
}); },
originalHeaderItems.splice(9, 0, { {
type: 'toolGroupButton', type: 'toolGroupButton',
toolGroup: 'rectangleTools', toolGroup: 'rectangleTools',
dataElement: 'shapeToolGroupButton', dataElement: dataElements.SHAPE_TOOL_GROUP_BUTTON,
img: this._convertPath('/assets/icons/general/rectangle.svg'), img: this._convertPath('/assets/icons/general/rectangle.svg'),
title: 'annotation.rectangle', title: 'annotation.rectangle',
}); },
];
this.instance.UI.setHeaderItems(header => {
const originalHeaderItems = header.getItems();
originalHeaderItems.splice(8, 0, ...headerItems);
if (this._userPreferenceService.areDevFeaturesEnabled) { if (this._userPreferenceService.areDevFeaturesEnabled) {
originalHeaderItems.splice(11, 0, { const devHeaderItems = [
type: 'actionButton', {
element: 'compare', type: 'actionButton',
dataElement: 'compareButton', element: 'compare',
img: this._convertPath('/assets/icons/general/pdftron-action-compare.svg'), dataElement: dataElements.COMPARE_BUTTON,
title: 'Compare', img: this._convertPath('/assets/icons/general/pdftron-action-compare.svg'),
onClick: () => { title: 'Compare',
this.compareFileInput.nativeElement.click(); onClick: () => {
this.compareFileInput.nativeElement.click();
},
}, },
}); {
originalHeaderItems.splice(11, 0, { type: 'actionButton',
type: 'actionButton', element: 'closeCompare',
element: 'closeCompare', dataElement: dataElements.CLOSE_COMPARE_BUTTON,
dataElement: 'closeCompareButton', img: this._convertPath('/assets/icons/general/pdftron-action-close-compare.svg'),
img: this._convertPath('/assets/icons/general/pdftron-action-close-compare.svg'), title: 'Leave Compare Mode',
title: 'Leave Compare Mode', onClick: async () => {
onClick: () => { await this.closeCompareMode();
this.closeCompareMode(); },
}, },
}); {
originalHeaderItems.splice(13, 0, { type: 'divider',
type: 'divider', dataElement: dataElements.COMPARE_TOOL_DIVIDER,
dataElement: 'compareToolDivider', },
}); ];
originalHeaderItems.splice(11, 0, ...devHeaderItems);
} }
}); });
this.instance.UI.disableElements(['closeCompareButton']); this.instance.UI.disableElements([dataElements.CLOSE_COMPARE_BUTTON]);
this.instance.Core.documentViewer.getTool('AnnotationCreateRectangle').setStyles(() => ({ this.documentViewer.getTool('AnnotationCreateRectangle').setStyles(() => ({
StrokeThickness: 2, StrokeThickness: 2,
StrokeColor: this._annotationDrawService.getColor(this.instance, 'manual'), StrokeColor: this._annotationDrawService.getColor(this.instance, 'manual'),
FillColor: this._annotationDrawService.getColor(this.instance, 'manual'), FillColor: this._annotationDrawService.getColor(this.instance, 'manual'),
@ -410,11 +443,11 @@ export class PdfViewerComponent implements OnInit, OnChanges {
onClick: () => { onClick: () => {
this._ngZone.run(() => { this._ngZone.run(() => {
if (allAreVisible) { if (allAreVisible) {
this.instance.Core.annotationManager.hideAnnotations(viewerAnnotations); this.annotationManager.hideAnnotations(viewerAnnotations);
} else { } else {
this.instance.Core.annotationManager.showAnnotations(viewerAnnotations); this.annotationManager.showAnnotations(viewerAnnotations);
} }
this.instance.Core.annotationManager.deselectAllAnnotations(); this.annotationManager.deselectAllAnnotations();
this._annotationActionsService.updateHiddenAnnotation(this.annotations, viewerAnnotations, allAreVisible); this._annotationActionsService.updateHiddenAnnotation(this.annotations, viewerAnnotations, allAreVisible);
}); });
}, },
@ -428,38 +461,43 @@ export class PdfViewerComponent implements OnInit, OnChanges {
} }
private _configureRectangleAnnotationPopup() { private _configureRectangleAnnotationPopup() {
this.instance.UI.annotationPopup.add(<any>{ this.instance.UI.annotationPopup.add([
type: 'actionButton', {
dataElement: 'add-rectangle', type: 'actionButton',
img: this._convertPath('/assets/icons/general/pdftron-action-add-redaction.svg'), dataElement: dataElements.ADD_RECTANGLE,
title: this._translateService.instant(this._manualAnnotationService.getTitle('REDACTION')), img: this._convertPath('/assets/icons/general/pdftron-action-add-redaction.svg'),
onClick: () => { title: this._translateService.instant(this._manualAnnotationService.getTitle('REDACTION')),
const selectedAnnotations = this.instance.Core.annotationManager.getSelectedAnnotations(); onClick: () => this._addRectangleManualRedaction(),
const activeAnnotation = selectedAnnotations[0];
const activePage = selectedAnnotations[0].getPageNumber();
const quad = this._annotationDrawService.annotationToQuads(activeAnnotation, this.instance);
const quadsObject = {};
quadsObject[activePage] = [quad];
const mre = this._getManualRedactionEntry(quadsObject, 'Rectangle');
// cleanup selection and button state
this.utils.deselectAllAnnotations();
this.instance.UI.disableElements(['shapeToolGroupButton', 'rectangleToolDivider']);
this.instance.UI.enableElements(['shapeToolGroupButton', 'rectangleToolDivider']);
// dispatch event
this.manualAnnotationRequested.emit(
new ManualRedactionEntryWrapper([quad], mre, 'REDACTION', 'RECTANGLE', activeAnnotation.Id),
);
}, },
}); ]);
}
private _addRectangleManualRedaction() {
const activeAnnotation = this.annotationManager.getSelectedAnnotations()[0];
const activePage = activeAnnotation.getPageNumber();
const quads = [this._annotationDrawService.annotationToQuads(activeAnnotation, this.instance)];
const manualRedaction = this._getManualRedaction({ [activePage]: quads }, 'Rectangle');
this._cleanUpSelectionAndButtonState();
this.manualAnnotationRequested.emit(
new ManualRedactionEntryWrapper(quads, manualRedaction, 'REDACTION', 'RECTANGLE', activeAnnotation.Id),
);
}
private _cleanUpSelectionAndButtonState() {
const rectangleElements = [dataElements.SHAPE_TOOL_GROUP_BUTTON, dataElements.RECTANGLE_TOOL_DIVIDER];
this.utils.deselectAllAnnotations();
this.instance.UI.disableElements(rectangleElements);
this.instance.UI.enableElements(rectangleElements);
} }
private _configureTextPopup() { private _configureTextPopup() {
this.instance.UI.textPopup.add(<any>{ const searchButton = {
type: 'actionButton', type: 'actionButton',
img: this._convertPath('/assets/icons/general/pdftron-action-search.svg'), img: this._convertPath('/assets/icons/general/pdftron-action-search.svg'),
title: this._translateService.instant('pdf-viewer.text-popup.actions.search'), title: this._translateService.instant('pdf-viewer.text-popup.actions.search'),
onClick: () => { onClick: () => {
const text = this.instance.Core.documentViewer.getSelectedText(); const text = this.documentViewer.getSelectedText();
const searchOptions = { const searchOptions = {
caseSensitive: true, // match case caseSensitive: true, // match case
wholeWord: true, // match whole words only wholeWord: true, // match whole words only
@ -469,117 +507,121 @@ export class PdfViewerComponent implements OnInit, OnChanges {
ambientString: true, // return ambient string as part of the result ambientString: true, // return ambient string as part of the result
}; };
this.instance.UI.openElements(['searchPanel']); this.instance.UI.openElements(['searchPanel']);
setTimeout(() => { setTimeout(() => this.instance.UI.searchTextFull(text, searchOptions), 250);
this.instance.UI.searchTextFull(text, searchOptions);
}, 250);
}, },
}); };
this.instance.UI.textPopup.add([searchButton]);
// Adding directly to the false-positive dict is only available in dev-mode // Adding directly to the false-positive dict is only available in dev-mode
if (this._userPreferenceService.areDevFeaturesEnabled) { if (this._userPreferenceService.areDevFeaturesEnabled) {
this.instance.UI.textPopup.add(<any>{ this.instance.UI.textPopup.add([
type: 'actionButton', {
dataElement: 'add-false-positive', type: 'actionButton',
img: this._convertPath('/assets/icons/general/pdftron-action-false-positive.svg'), dataElement: 'add-false-positive',
title: this._translateService.instant(this._manualAnnotationService.getTitle('FALSE_POSITIVE')), img: this._convertPath('/assets/icons/general/pdftron-action-false-positive.svg'),
onClick: () => { title: this._translateService.instant(this._manualAnnotationService.getTitle(ManualRedactionEntryTypes.FALSE_POSITIVE)),
const selectedQuads = this.instance.Core.documentViewer.getSelectedTextQuads(); onClick: () => this._addManualRedactionOfType(ManualRedactionEntryTypes.FALSE_POSITIVE),
const text = this.instance.Core.documentViewer.getSelectedText();
const mre = this._getManualRedactionEntry(selectedQuads, text, true);
this.manualAnnotationRequested.emit(
new ManualRedactionEntryWrapper(this.instance.Core.documentViewer.getSelectedTextQuads(), mre, 'FALSE_POSITIVE'),
);
}, },
}); ]);
} }
this.instance.UI.textPopup.add(<any>{ this.instance.UI.textPopup.add([
type: 'actionButton', {
dataElement: 'add-dictionary', type: 'actionButton',
img: this._convertPath('/assets/icons/general/pdftron-action-add-dict.svg'), dataElement: dataElements.ADD_REDACTION,
title: this._translateService.instant(this._manualAnnotationService.getTitle('DICTIONARY')), img: this._convertPath('/assets/icons/general/pdftron-action-add-redaction.svg'),
onClick: () => { title: this._translateService.instant(this._manualAnnotationService.getTitle(ManualRedactionEntryTypes.REDACTION)),
const selectedQuads = this.instance.Core.documentViewer.getSelectedTextQuads(); onClick: () => this._addManualRedactionOfType(ManualRedactionEntryTypes.REDACTION),
const text = this.instance.Core.documentViewer.getSelectedText();
const mre = this._getManualRedactionEntry(selectedQuads, text, true);
this.manualAnnotationRequested.emit(
new ManualRedactionEntryWrapper(this.instance.Core.documentViewer.getSelectedTextQuads(), mre, 'DICTIONARY'),
);
}, },
}); {
type: 'actionButton',
dataElement: dataElements.ADD_DICTIONARY,
img: this._convertPath('/assets/icons/general/pdftron-action-add-dict.svg'),
title: this._translateService.instant(this._manualAnnotationService.getTitle(ManualRedactionEntryTypes.DICTIONARY)),
onClick: () => this._addManualRedactionOfType(ManualRedactionEntryTypes.DICTIONARY),
},
]);
this.instance.UI.textPopup.add(<any>{
type: 'actionButton',
dataElement: 'add-redaction',
img: this._convertPath('/assets/icons/general/pdftron-action-add-redaction.svg'),
title: this._translateService.instant(this._manualAnnotationService.getTitle('REDACTION')),
onClick: () => {
const selectedQuads = this.instance.Core.documentViewer.getSelectedTextQuads();
const text = this.instance.Core.documentViewer.getSelectedText();
const mre = this._getManualRedactionEntry(selectedQuads, text, true);
this.manualAnnotationRequested.emit(
new ManualRedactionEntryWrapper(this.instance.Core.documentViewer.getSelectedTextQuads(), mre, 'REDACTION'),
);
},
});
this._handleCustomActions(); this._handleCustomActions();
} }
private _addManualRedactionOfType(type: ManualRedactionEntryType) {
const selectedQuads = this.documentViewer.getSelectedTextQuads();
const text = this.documentViewer.getSelectedText();
const manualRedaction = this._getManualRedaction(selectedQuads, text, true);
this.manualAnnotationRequested.emit(new ManualRedactionEntryWrapper(selectedQuads, manualRedaction, type));
}
private _handleCustomActions() { private _handleCustomActions() {
this.instance.UI.setToolMode('AnnotationEdit'); this.instance.UI.setToolMode('AnnotationEdit');
const { ANNOTATION_POPUP, ADD_RECTANGLE, ADD_REDACTION, SHAPE_TOOL_GROUP_BUTTON } = dataElements;
const elements = [
ADD_REDACTION,
ADD_RECTANGLE,
'add-false-positive',
SHAPE_TOOL_GROUP_BUTTON,
'rectangleToolDivider',
ANNOTATION_POPUP,
];
if (this.canPerformActions && !this.utils.isCurrentPageExcluded) { if (this.canPerformActions && !this.utils.isCurrentPageExcluded) {
this.instance.UI.enableTools(['AnnotationCreateRectangle']); this.instance.UI.enableTools(['AnnotationCreateRectangle']);
this.instance.UI.enableElements([ this.instance.UI.enableElements(elements);
'add-redaction',
'add-rectangle',
'add-false-positive',
'shapeToolGroupButton',
'rectangleToolDivider',
'annotationPopup',
]);
if (this._selectedText.length > 2) { if (this._selectedText.length > 2) {
this.instance.UI.enableElements(['add-dictionary', 'add-false-positive']); this.instance.UI.enableElements([dataElements.ADD_DICTIONARY, dataElements.ADD_FALSE_POSITIVE]);
} }
return;
}
let elementsToDisable = [...elements, ADD_RECTANGLE];
if (this.utils.isCurrentPageExcluded) {
const allowedActionsWhenPageExcluded: string[] = [ANNOTATION_POPUP, ADD_RECTANGLE, ADD_REDACTION, SHAPE_TOOL_GROUP_BUTTON];
elementsToDisable = elementsToDisable.filter(element => !allowedActionsWhenPageExcluded.includes(element));
} else { } else {
this.instance.UI.disableTools(['AnnotationCreateRectangle']); this.instance.UI.disableTools(['AnnotationCreateRectangle']);
this.instance.UI.disableElements([
'add-redaction',
'add-dictionary',
'add-false-positive',
'add-rectangle',
'shapeToolGroupButton',
'rectangleToolDivider',
'annotationPopup',
]);
} }
this.instance.UI.disableElements(elementsToDisable);
} }
private _getManualRedactionEntry(quads: any, text: string, convertQuads: boolean = false): IManualRedactionEntry { private _getManualRedaction(
quads: Readonly<Record<string, Core.Math.Quad[]>>,
text: string,
convertQuads = false,
): IManualRedactionEntry {
const entry: IManualRedactionEntry = { positions: [] }; const entry: IManualRedactionEntry = { positions: [] };
for (const key of Object.keys(quads)) { for (const key of Object.keys(quads)) {
for (const quad of quads[key]) { for (const quad of quads[key]) {
const page = parseInt(key, 10); const page = parseInt(key, 10);
entry.positions.push(this.utils.toPosition(page, convertQuads ? this.utils.translateQuads(page, quad) : quad)); entry.positions.push(this.utils.toPosition(page, convertQuads ? this.utils.translateQuads(page, quad) : quad));
} }
} }
entry.value = text; entry.value = text;
return entry; return entry;
} }
private _loadDocument() { private _loadDocument() {
if (this.fileData) { if (!this.fileData) {
this.instance.UI.loadDocument(this.fileData, { return;
filename: this.file ? this.file.filename : 'document.pdf',
});
} }
this.instance.UI.loadDocument(this.fileData, {
filename: this.file ? this.file.filename : 'document.pdf',
});
} }
private _documentLoaded(): void { private _setReadyAndInitialState(): void {
this._ngZone.run(() => { this._ngZone.run(() => {
this.utils.ready = true; this.utils.ready = true;
this._firstPageChange = true; this._firstPageChange = true;
this.viewerReady.emit(this.instance); this.viewerReady.emit(this.instance);
this.setInitialViewerState(); this._setInitialDisplayMode();
}); });
} }
} }

View File

@ -1,34 +1,42 @@
import { stampPDFPage } from '@utils/page-stamper'; import { stampPDFPage } from '@utils/page-stamper';
import { Core, WebViewerInstance } from '@pdftron/webviewer';
import { File } from '@red/domain';
const processPage = async (pageNumber, document1, document2, mergedDocument, pdfNet) => { const processPage = async (
pageNumber: number,
document1: Core.PDFNet.PDFDoc,
document2: Core.PDFNet.PDFDoc,
mergedDocument: Core.PDFNet.PDFDoc,
pdfNet: typeof Core.PDFNet,
) => {
const document1PageCount = await document1.getPageCount(); const document1PageCount = await document1.getPageCount();
if (document1PageCount >= pageNumber) { if (document1PageCount >= pageNumber) {
await mergedDocument.insertPages(pageNumber * 2, document1, pageNumber, pageNumber, pdfNet.PDFDoc.InsertFlag.e_none); await mergedDocument.insertPages(pageNumber * 2, document1, pageNumber, pageNumber, pdfNet.PDFDoc.InsertFlag.e_none);
} else { return;
const pageToCopy = await document2.getPage(pageNumber);
const blankPage = await mergedDocument.pageCreate(await pageToCopy.getCropBox());
await blankPage.setRotation(await pageToCopy.getRotation());
await mergedDocument.pagePushBack(blankPage);
await stampPDFPage(mergedDocument, pdfNet, '<< Compare Placeholder Page >>', 20, 'courier', 'DIAGONAL', 33, '#ffb83b', [
await mergedDocument.getPageCount(),
]);
} }
const pageToCopy = await document2.getPage(pageNumber);
const blankPage = await mergedDocument.pageCreate(await pageToCopy.getCropBox());
await blankPage.setRotation(await pageToCopy.getRotation());
await mergedDocument.pagePushBack(blankPage);
await stampPDFPage(mergedDocument, pdfNet, '<< Compare Placeholder Page >>', 20, 'courier', 'DIAGONAL', 33, '#ffb83b', [
await mergedDocument.getPageCount(),
]);
}; };
export const loadCompareDocumentWrapper = async ( export const loadCompareDocumentWrapper = async (
currentDocumentPageCount, currentDocument: Core.PDFNet.PDFDoc,
compareDocumentPageCount, compareDocument: Core.PDFNet.PDFDoc,
currentDocument, mergedDocument: Core.PDFNet.PDFDoc,
compareDocument, instance: WebViewerInstance,
mergedDocument, file: File,
instance,
file,
setCompareViewMode: () => void, setCompareViewMode: () => void,
navigateToPage: () => void, navigateToPage: () => void,
pdfNet: any, pdfNet: typeof Core.PDFNet,
) => { ) => {
try { try {
const maxPageCount = Math.max(currentDocumentPageCount, compareDocumentPageCount); const maxPageCount = Math.max(await currentDocument.getPageCount(), await compareDocument.getPageCount());
for (let idx = 1; idx <= maxPageCount; idx++) { for (let idx = 1; idx <= maxPageCount; idx++) {
await processPage(idx, currentDocument, compareDocument, mergedDocument, pdfNet); await processPage(idx, currentDocument, compareDocument, mergedDocument, pdfNet);
@ -43,11 +51,11 @@ export const loadCompareDocumentWrapper = async (
setCompareViewMode(); setCompareViewMode();
instance.loadDocument(mergedDocumentBuffer, { instance.UI.loadDocument(mergedDocumentBuffer, {
filename: file?.filename ?? 'document.pdf', filename: file?.filename ?? 'document.pdf',
}); });
instance.disableElements(['compareButton']); instance.UI.disableElements(['compareButton']);
instance.enableElements(['closeCompareButton']); instance.UI.enableElements(['closeCompareButton']);
navigateToPage(); navigateToPage();
} catch (e) { } catch (e) {

View File

@ -29,6 +29,7 @@ export class IconsModule {
'enter', 'enter',
'entries', 'entries',
'exclude-pages', 'exclude-pages',
'excluded-page',
'exit-fullscreen', 'exit-fullscreen',
'folder', 'folder',
'fullscreen', 'fullscreen',

View File

@ -1017,7 +1017,7 @@
"document-info": "Your Document Info lives here. This includes metadata required on each document.", "document-info": "Your Document Info lives here. This includes metadata required on each document.",
"download-original-file": "Download Original File", "download-original-file": "Download Original File",
"exclude-pages": "Exclude pages from redaction", "exclude-pages": "Exclude pages from redaction",
"excluded-from-redaction": "excluded from redaction", "excluded-from-redaction": "excluded from automatic redaction",
"fullscreen": "Full Screen (F)", "fullscreen": "Full Screen (F)",
"last-reviewer": "Last Reviewed by:", "last-reviewer": "Last Reviewed by:",
"no-data": { "no-data": {

View File

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg height="40px" version="1.1" viewBox="0 0 36 40" width="36px" xmlns="http://www.w3.org/2000/svg">
<g fill="none" fill-rule="evenodd" id="Exclude-page-from-redaction-NEW" stroke="none" stroke-width="1">
<g fill="currentColor" fill-rule="nonzero" id="05.Excluded-with-redactions" transform="translate(-1099.000000, -218.000000)">
<g id="Group-16" transform="translate(1087.000000, 173.000000)">
<g id="pages" transform="translate(0.000000, 33.000000)">
<g id="Group-8" transform="translate(12.000000, 12.000000)">
<path
d="M36,0 L36,40 L0,40 L1.81854531e-13,35.5 L1.333,35.5 L1.33333333,38.6666667 L4,38.666 L4,40 L8,40 L8,38.666 L12,38.666 L12,40 L16,40 L16,38.666 L20,38.666 L20,40 L24,40 L24,38.666 L28,38.666 L28,40 L32,40 L32,38.666 L34.6666667,38.6666667 L34.666,35.5 L36,35.5 L36,31 L34.666,31 L34.666,26.5 L36,26.5 L36,22 L34.666,22 L34.666,17.5 L36,17.5 L36,13 L34.666,13 L34.666,8.5 L36,8.5 L36,4 L34.666,4 L34.6666667,1.33333333 L32,1.333 L32,1.13242749e-14 L36,0 Z M1.333,26.5 L1.333,31 L1.81854531e-13,31 L1.81854531e-13,26.5 L1.333,26.5 Z M1.333,17.5 L1.333,22 L1.81854531e-13,22 L1.81854531e-13,17.5 L1.333,17.5 Z M1.333,8.5 L1.333,13 L1.81854531e-13,13 L0,8.5 L1.333,8.5 Z M4,1.13242749e-14 L4,1.333 L1.33333333,1.33333333 L1.333,4 L0,4 L0,0 L4,1.13242749e-14 Z M20,1.13242749e-14 L20,1.333 L16,1.333 L16,1.13242749e-14 L20,1.13242749e-14 Z M12,1.13242749e-14 L12,1.333 L8,1.333 L8,1.13242749e-14 L12,1.13242749e-14 Z M28,1.13242749e-14 L28,1.333 L24,1.333 L24,1.13242749e-14 L28,1.13242749e-14 Z"
id="Excluded-Page"></path>
</g>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

@ -1 +1 @@
Subproject commit 1df1b1ab899e21093eb07c444acf90def933cb02 Subproject commit e1ce89e38d3520ad11960074f74c381429c0251a