RED-3988: add document viewer service

This commit is contained in:
Dan Percic 2022-05-23 11:24:26 +03:00
parent 94ab93cfaf
commit 669ec2ba1d
16 changed files with 208 additions and 147 deletions

View File

@ -1,6 +1,6 @@
<router-outlet></router-outlet>
<redaction-pdf-viewer [style.visibility]="(pdf.loaded$ | async) ? 'visible' : 'hidden'"></redaction-pdf-viewer>
<redaction-pdf-viewer [style.visibility]="(documentViewer.loaded$ | async) ? 'visible' : 'hidden'"></redaction-pdf-viewer>
<iqser-full-page-loading-indicator></iqser-full-page-loading-indicator>
<iqser-connection-status></iqser-connection-status>

View File

@ -1,7 +1,7 @@
import { Component, ViewContainerRef } from '@angular/core';
import { RouterHistoryService } from '@services/router-history.service';
import { UserService } from '@services/user.service';
import { PdfViewer } from './modules/pdf-viewer/services/pdf-viewer.service';
import { REDDocumentViewer } from './modules/pdf-viewer/services/document-viewer.service';
@Component({
selector: 'redaction-root',
@ -15,6 +15,6 @@ export class AppComponent {
public viewContainerRef: ViewContainerRef,
private readonly _routerHistoryService: RouterHistoryService,
private readonly _userService: UserService,
readonly pdf: PdfViewer,
readonly documentViewer: REDDocumentViewer,
) {}
}

View File

@ -6,6 +6,7 @@ import {
EventEmitter,
HostListener,
Input,
OnDestroy,
Output,
TemplateRef,
ViewChild,
@ -15,6 +16,7 @@ import { AnnotationProcessingService } from '../../services/annotation-processin
import { MatDialogRef, MatDialogState } from '@angular/material/dialog';
import scrollIntoView from 'scroll-into-view-if-needed';
import {
AutoUnsubscribe,
CircleButtonTypes,
Debounce,
FilterService,
@ -24,7 +26,7 @@ import {
shareDistinctLast,
shareLast,
} from '@iqser/common-ui';
import { combineLatest, firstValueFrom, Observable, takeWhile } from 'rxjs';
import { combineLatest, firstValueFrom, Observable } from 'rxjs';
import { map, tap } from 'rxjs/operators';
import { File } from '@red/domain';
import { ExcludedPagesService } from '../../services/excluded-pages.service';
@ -38,6 +40,7 @@ import { FileDataService } from '../../services/file-data.service';
import { PdfViewer } from '../../../pdf-viewer/services/pdf-viewer.service';
import { REDAnnotationManager } from '../../../pdf-viewer/services/annotation-manager.service';
import { AnnotationsListingService } from '../../services/annotations-listing.service';
import { REDDocumentViewer } from '../../../pdf-viewer/services/document-viewer.service';
const COMMAND_KEY_ARRAY = ['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown', 'Escape'];
const ALL_HOTKEY_ARRAY = ['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown'];
@ -48,7 +51,7 @@ const ALL_HOTKEY_ARRAY = ['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown'];
styleUrls: ['./file-workload.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class FileWorkloadComponent {
export class FileWorkloadComponent extends AutoUnsubscribe implements OnDestroy {
readonly iconButtonTypes = IconButtonTypes;
readonly circleButtonTypes = CircleButtonTypes;
@ -77,22 +80,31 @@ export class FileWorkloadComponent {
readonly viewModeService: ViewModeService,
readonly multiSelectService: MultiSelectService,
readonly annotationManager: REDAnnotationManager,
private readonly _documentViewer: REDDocumentViewer,
readonly documentInfoService: DocumentInfoService,
readonly listingService: AnnotationsListingService,
readonly excludedPagesService: ExcludedPagesService,
private readonly _changeDetectorRef: ChangeDetectorRef,
private readonly _annotationProcessingService: AnnotationProcessingService,
) {
this.pdf.currentPage$.pipe(takeWhile(() => !!this)).subscribe(pageNumber => {
super();
this.addActiveScreenSubscription = this.pdf.currentPage$.subscribe(pageNumber => {
this._scrollViews();
this.scrollAnnotationsToPage(pageNumber, 'always');
});
this.listingService.selected$.pipe(takeWhile(() => !!this)).subscribe(annotationIds => {
this.addActiveScreenSubscription = this.listingService.selected$.subscribe(annotationIds => {
if (annotationIds.length > 0) {
this.pagesPanelActive = false;
}
this.scrollToSelectedAnnotation();
});
this.addActiveScreenSubscription = this._documentViewer.keyUp$.subscribe($event => {
this.handleKeyEvent($event);
});
this.displayedAnnotations$ = this._displayedAnnotations$;
this.multiSelectInactive$ = this._multiSelectInactive$;
this.showExcludedPages$ = this._showExcludedPages$;

View File

@ -32,6 +32,7 @@ import {
ROTATION_ACTION_BUTTONS,
TEXT_POPUPS_TO_TOGGLE,
} from '../../../pdf-viewer/utils/constants';
import { REDDocumentViewer } from '../../../pdf-viewer/services/document-viewer.service';
import Annotation = Core.Annotations.Annotation;
@Component({
@ -45,7 +46,6 @@ export class PdfPaginatorComponent extends AutoUnsubscribe implements OnInit, On
@Output() readonly annotationSelected = this.#annotationSelected$;
@Output() readonly manualAnnotationRequested = new EventEmitter<ManualRedactionEntryWrapper>();
@Output() readonly pageChanged = this.pdf.pageChanged$.pipe(tap(() => this._handleCustomActions()));
@Output() readonly keyUp = this.pdf.keyUp$;
instance: WebViewerInstance;
private _selectedText = '';
readonly #visibilityOffIcon = this._convertPath('/assets/icons/general/visibility-off.svg');
@ -67,6 +67,7 @@ export class PdfPaginatorComponent extends AutoUnsubscribe implements OnInit, On
private readonly _fileDataService: FileDataService,
private readonly _viewerHeaderService: ViewerHeaderService,
private readonly _errorService: ErrorService,
private readonly _documentViewer: REDDocumentViewer,
private readonly _annotationManager: REDAnnotationManager,
readonly pdf: PdfViewer,
private readonly _state: FilePreviewStateService,
@ -85,7 +86,7 @@ export class PdfPaginatorComponent extends AutoUnsubscribe implements OnInit, On
this.addActiveScreenSubscription = this._state.blob$
.pipe(
log('Reload blob'),
switchMap(blob => from(this.pdf.lockDocument()).pipe(map(() => blob))),
switchMap(blob => from(this._documentViewer.lock()).pipe(map(() => blob))),
withLatestFrom(this._state.file$),
tap(() => this._errorService.clear()),
tap(([blob, file]) => this.pdf.loadDocument(blob, file)),
@ -109,24 +110,18 @@ export class PdfPaginatorComponent extends AutoUnsubscribe implements OnInit, On
if (action === 'deselected') {
// Remove deselected annotations from selected list
nextAnnotations = this._annotationManager.selected.filter(ann => !annotations.some(a => a.Id === ann.Id));
this.pdf.disable(TextPopups.ADD_RECTANGLE);
return nextAnnotations.map(ann => ann.Id);
} else if (!this._multiSelectService.isEnabled) {
// Only choose the last selected annotation, to bypass viewer multi select
nextAnnotations = annotations;
const notSelected = this._fileDataService.all.filter(wrapper => !nextAnnotations.some(ann => ann.Id === wrapper.id));
this._annotationManager.deselect(notSelected);
} else {
// Get selected annotations from the manager, no intervention needed
nextAnnotations = this._annotationManager.selected;
}
if (action === 'deselected') {
this.pdf.disable(TextPopups.ADD_RECTANGLE);
return nextAnnotations.map(ann => ann.Id);
}
if (!this._multiSelectService.isEnabled) {
const notSelected = this._fileDataService.all.filter(wrapper => !nextAnnotations.some(ann => ann.Id === wrapper.id));
this._annotationManager.deselect(notSelected);
}
this.#configureAnnotationSpecificActions(annotations);
this._toggleRectangleAnnotationAction(annotations.length === 1 && annotations[0].ReadOnly);
return nextAnnotations.map(ann => ann.Id);
@ -169,7 +164,7 @@ export class PdfPaginatorComponent extends AutoUnsubscribe implements OnInit, On
private _configureElements() {
const dossierTemplateId = this.dossier.dossierTemplateId;
const color = this._annotationDrawService.getAndConvertColor(dossierTemplateId, dossierTemplateId, 'manual');
this.pdf.setRectangleToolStyles(color);
this._documentViewer.setRectangleToolStyles(color);
}
#configureAnnotationSpecificActions(viewerAnnotations: Annotation[]) {
@ -293,6 +288,7 @@ export class PdfPaginatorComponent extends AutoUnsubscribe implements OnInit, On
title: this.#getTitle(ManualRedactionEntryTypes.DICTIONARY),
onClick: () => this._addManualRedactionOfType(ManualRedactionEntryTypes.DICTIONARY),
});
console.log(popups);
this.instance.UI.textPopup.add(popups);
@ -315,7 +311,7 @@ export class PdfPaginatorComponent extends AutoUnsubscribe implements OnInit, On
if (this.canPerformActions && !isCurrentPageExcluded) {
try {
this.instance.UI.enableTools(['AnnotationCreateRectangle']);
this.pdf.instance.UI.enableTools(['AnnotationCreateRectangle']);
} catch (e) {
// happens
}
@ -338,7 +334,7 @@ export class PdfPaginatorComponent extends AutoUnsubscribe implements OnInit, On
);
headerElementsToDisable = headerElementsToDisable.filter(element => !ALLOWED_ACTIONS_WHEN_PAGE_EXCLUDED.includes(element));
} else {
this.instance.UI.disableTools(['AnnotationCreateRectangle']);
this.pdf.instance.UI.disableTools(['AnnotationCreateRectangle']);
}
this.pdf.disable(textPopupElementsToDisable);

View File

@ -65,7 +65,6 @@
<div class="content-container">
<redaction-pdf-viewer
(annotationSelected)="handleAnnotationSelected($event)"
(keyUp)="handleKeyEvent($event); workloadComponent.handleKeyEvent($event)"
(manualAnnotationRequested)="openManualAnnotationDialog($event)"
(pageChanged)="viewerPageChanged($event)"
[canPerformActions]="canPerformAnnotationActions$ | async"

View File

@ -28,7 +28,6 @@ import { PermissionsService } from '@services/permissions.service';
import { combineLatest, firstValueFrom, Observable, of, pairwise } from 'rxjs';
import { UserPreferenceService } from '@services/user-preference.service';
import { download, handleFilterDelta } from '../../utils';
import { FileWorkloadComponent } from './components/file-workload/file-workload.component';
import { FilesService } from '@services/files/files.service';
import { FileManagementService } from '@services/files/file-management.service';
import { catchError, filter, map, startWith, switchMap, tap } from 'rxjs/operators';
@ -55,6 +54,7 @@ import { REDAnnotationManager } from '../pdf-viewer/services/annotation-manager.
import { ViewerHeaderService } from '../pdf-viewer/services/viewer-header.service';
import { ROTATION_ACTION_BUTTONS } from '../pdf-viewer/utils/constants';
import { SkippedService } from './services/skipped.service';
import { REDDocumentViewer } from '../pdf-viewer/services/document-viewer.service';
import Annotation = Core.Annotations.Annotation;
@Component({
@ -71,7 +71,6 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
readonly fileId = this.state.fileId;
readonly dossierId = this.state.dossierId;
readonly file$ = this.state.file$.pipe(tap(file => this._fileDataService.loadAnnotations(file)));
@ViewChild(FileWorkloadComponent) readonly workloadComponent: FileWorkloadComponent;
private _lastPage: string;
@ViewChild('annotationFilterTemplate', {
read: TemplateRef,
@ -102,6 +101,7 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
private readonly _skippedService: SkippedService,
private readonly _fileDataService: FileDataService,
private readonly _viewModeService: ViewModeService,
private readonly _documentViewer: REDDocumentViewer,
private readonly _changeDetectorRef: ChangeDetectorRef,
private readonly _dialogService: FilePreviewDialogService,
private readonly _pageRotationService: PageRotationService,
@ -198,7 +198,7 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
ngOnDetach(): void {
this._pageRotationService.clearRotations();
this.pdf.closeDocument();
this._documentViewer.close();
super.ngOnDetach();
this._changeDetectorRef.markForCheck();
}
@ -354,7 +354,7 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
}
loadAnnotations() {
const documentLoaded$ = this.pdf.loaded$.pipe(
const documentLoaded$ = this._documentViewer.loaded$.pipe(
filter(s => s),
tap(() => this.viewerReady()),
);
@ -525,9 +525,13 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
)
.subscribe();
this.addActiveScreenSubscription = this.pdf.pageComplete$.subscribe(() => {
this.addActiveScreenSubscription = this._documentViewer.pageComplete$.subscribe(() => {
this._setExcludedPageStyles();
});
this.addActiveScreenSubscription = this._documentViewer.keyUp$.subscribe($event => {
this.handleKeyEvent($event);
});
}
private _handleDeletedDossier(): void {

View File

@ -37,6 +37,7 @@ import { FileDataService } from './file-data.service';
import { PdfViewer } from '../../pdf-viewer/services/pdf-viewer.service';
import { REDAnnotationManager } from '../../pdf-viewer/services/annotation-manager.service';
import { SkippedService } from './skipped.service';
import { REDDocumentViewer } from '../../pdf-viewer/services/document-viewer.service';
import Quad = Core.Math.Quad;
@Injectable()
@ -51,6 +52,7 @@ export class AnnotationActionsService {
private readonly _dialogService: FilePreviewDialogService,
private readonly _dialog: MatDialog,
private readonly _pdf: PdfViewer,
private readonly _documentViewer: REDDocumentViewer,
private readonly _annotationManager: REDAnnotationManager,
private readonly _annotationDrawService: AnnotationDrawService,
private readonly _activeDossiersService: ActiveDossiersService,
@ -538,7 +540,7 @@ export class AnnotationActionsService {
private async _extractTextAndPositions(annotationId: string) {
const viewerAnnotation = this._annotationManager.get(annotationId);
const document = await this._pdf.PDFDoc;
const document = await this._documentViewer.PDFDoc;
const page = await document.getPage(viewerAnnotation.getPageNumber());
if (this._pdf.isTextHighlight(viewerAnnotation)) {
const words = [];
@ -546,7 +548,7 @@ export class AnnotationActionsService {
for (const quad of viewerAnnotation.Quads) {
const rect = toPosition(
viewerAnnotation.getPageNumber(),
this._pdf.getPageHeight(viewerAnnotation.getPageNumber()),
this._documentViewer.getHeight(viewerAnnotation.getPageNumber()),
this._translateQuads(viewerAnnotation.getPageNumber(), quad),
);
rectangles.push(rect);

View File

@ -8,12 +8,14 @@ import { Core } from '@pdftron/webviewer';
import { firstValueFrom } from 'rxjs';
import { WatermarkService } from '@services/entity-services/watermark.service';
import { PdfViewer } from '../../pdf-viewer/services/pdf-viewer.service';
import { REDDocumentViewer } from '../../pdf-viewer/services/document-viewer.service';
import PDFNet = Core.PDFNet;
@Injectable()
export class StampService {
constructor(
private readonly _pdf: PdfViewer,
private readonly _documentViewer: REDDocumentViewer,
private readonly _state: FilePreviewStateService,
private readonly _logger: NGXLogger,
private readonly _viewModeService: ViewModeService,
@ -22,7 +24,7 @@ export class StampService {
) {}
async stampPDF(): Promise<void> {
const pdfDoc = await this._pdf.PDFDoc;
const pdfDoc = await this._documentViewer.PDFDoc;
if (!pdfDoc) {
return;
}

View File

@ -1,4 +1,4 @@
<div [style.visibility]="(pdf.loaded$ | async) ? 'visible' : 'hidden'" class="pagination noselect">
<div [style.visibility]="(documentViewer.loaded$ | async) ? 'visible' : 'hidden'" class="pagination noselect">
<div (click)="pdf.navigatePreviousPage()">
<mat-icon class="chevron-icon" svgIcon="red:nav-prev"></mat-icon>
</div>

View File

@ -1,5 +1,6 @@
import { Component } from '@angular/core';
import { PdfViewer } from '../../services/pdf-viewer.service';
import { REDDocumentViewer } from '../../services/document-viewer.service';
@Component({
selector: 'redaction-paginator',
@ -7,5 +8,5 @@ import { PdfViewer } from '../../services/pdf-viewer.service';
styleUrls: ['./paginator.component.scss'],
})
export class PaginatorComponent {
constructor(readonly pdf: PdfViewer) {}
constructor(readonly pdf: PdfViewer, readonly documentViewer: REDDocumentViewer) {}
}

View File

@ -3,6 +3,7 @@ import { PdfViewer } from './services/pdf-viewer.service';
import { REDAnnotationManager } from './services/annotation-manager.service';
import { ViewerHeaderService } from './services/viewer-header.service';
import { CompareFileInputComponent } from './components/compare-file-input/compare-file-input.component';
import { REDDocumentViewer } from './services/document-viewer.service';
@Component({
selector: 'redaction-pdf-viewer',
@ -16,6 +17,7 @@ export class PdfViewerComponent implements OnInit {
constructor(
private readonly _pdf: PdfViewer,
private readonly _annotationManager: REDAnnotationManager,
private readonly _documentViewer: REDDocumentViewer,
private readonly _viewerHeaderService: ViewerHeaderService,
) {}
@ -23,6 +25,7 @@ export class PdfViewerComponent implements OnInit {
const pdfInit = this._pdf.init(this._viewer.nativeElement as HTMLElement);
return pdfInit.then(instance => {
this._annotationManager.init(instance.Core.annotationManager);
this._documentViewer.init(instance.Core.documentViewer);
this._viewerHeaderService.init(this._compare.input);
});
}

View File

@ -10,11 +10,20 @@ import { ViewerHeaderService } from './services/viewer-header.service';
import { PaginatorComponent } from './components/paginator/paginator.component';
import { MatIconModule } from '@angular/material/icon';
import { AnnotationDrawService } from './services/annotation-draw.service';
import { REDDocumentViewer } from './services/document-viewer.service';
@NgModule({
declarations: [PdfViewerComponent, CompareFileInputComponent, PaginatorComponent],
exports: [PdfViewerComponent],
imports: [CommonModule, MatIconModule],
providers: [PdfViewer, REDAnnotationManager, PageRotationService, TooltipsService, ViewerHeaderService, AnnotationDrawService],
providers: [
PdfViewer,
REDDocumentViewer,
REDAnnotationManager,
PageRotationService,
TooltipsService,
ViewerHeaderService,
AnnotationDrawService,
],
})
export class PdfViewerModule {}

View File

@ -14,6 +14,7 @@ import { PdfViewer } from './pdf-viewer.service';
import { ActivatedRoute } from '@angular/router';
import { REDAnnotationManager } from './annotation-manager.service';
import { List } from '@iqser/common-ui';
import { REDDocumentViewer } from './document-viewer.service';
import Annotation = Core.Annotations.Annotation;
import Quad = Core.Math.Quad;
@ -29,6 +30,7 @@ export class AnnotationDrawService {
private readonly _activatedRoute: ActivatedRoute,
private readonly _annotationManager: REDAnnotationManager,
private readonly _pdf: PdfViewer,
private readonly _documentViewer: REDDocumentViewer,
) {}
draw(annotations: List<AnnotationWrapper>, dossierTemplateId: string, hideSkipped: boolean) {
@ -113,7 +115,7 @@ export class AnnotationDrawService {
private _computeSection(dossierTemplateId: string, pageNumber: number, sectionRectangle: ISectionRectangle) {
const rectangleAnnot = this._pdf.rectangle();
const pageHeight = this._pdf.getPageHeight(pageNumber);
const pageHeight = this._documentViewer.getHeight(pageNumber);
const rectangle: IRectangle = {
topLeft: sectionRectangle.topLeft,
page: pageNumber,
@ -141,7 +143,7 @@ export class AnnotationDrawService {
if (annotationWrapper.superType === SuperTypes.TextHighlight) {
const rectangleAnnot = this._pdf.rectangle();
const pageHeight = this._pdf.getPageHeight(pageNumber);
const pageHeight = this._documentViewer.getHeight(pageNumber);
const rectangle: IRectangle = annotationWrapper.positions[0];
rectangleAnnot.PageNumber = pageNumber;
rectangleAnnot.X = rectangle.topLeft.x;
@ -186,7 +188,7 @@ export class AnnotationDrawService {
}
private _rectanglesToQuads(positions: IRectangle[], pageNumber: number): Quad[] {
const pageHeight = this._pdf.getPageHeight(pageNumber);
const pageHeight = this._documentViewer.getHeight(pageNumber);
return positions.map(p => this._rectangleToQuad(p, pageHeight));
}

View File

@ -0,0 +1,129 @@
import { Injectable } from '@angular/core';
import { Core } from '@pdftron/webviewer';
import { NGXLogger } from 'ngx-logger';
import { fromEvent, merge, Observable } from 'rxjs';
import { debounceTime, filter, map, tap } from 'rxjs/operators';
import { ActivatedRoute } from '@angular/router';
import { PdfViewer } from './pdf-viewer.service';
import { UserPreferenceService } from '@services/user-preference.service';
import { log, shareLast } from '@iqser/common-ui';
import { stopAndPrevent, stopAndPreventIfNotAllowed } from '../utils/functions';
import DocumentViewer = Core.DocumentViewer;
import Color = Core.Annotations.Color;
@Injectable()
export class REDDocumentViewer {
loaded$: Observable<boolean>;
pageComplete$: Observable<boolean>;
keyUp$: Observable<KeyboardEvent>;
#document: DocumentViewer;
constructor(
private readonly _logger: NGXLogger,
private readonly _userPreferenceService: UserPreferenceService,
private readonly _pdf: PdfViewer,
private readonly _activatedRoute: ActivatedRoute,
) {}
get PDFDoc() {
return this.document?.getPDFDoc();
}
get document() {
return this.#document.getDocument();
}
get #documentUnloaded$() {
const event$ = fromEvent(this.#document, 'documentUnloaded');
const toBool$ = event$.pipe(map(() => false));
return toBool$.pipe(tap(() => this._logger.info('[PDF] Document unloaded')));
}
get #documentLoaded$() {
const event$ = fromEvent(this.#document, 'documentLoaded');
const toBool$ = event$.pipe(map(() => true));
return toBool$.pipe(
tap(() => this.#setCurrentPage()),
tap(() => this.#setInitialDisplayMode()),
tap(() => this.updateTooltipsVisibility()),
tap(() => this._logger.info('[PDF] Document loaded')),
);
}
get #keyUp$() {
return fromEvent<KeyboardEvent>(this.#document, 'keyUp').pipe(
tap(stopAndPreventIfNotAllowed),
filter($event => ($event.target as HTMLElement)?.tagName?.toLowerCase() !== 'input'),
filter($event => $event.key.startsWith('Arrow') || $event.key === 'f'),
tap(stopAndPrevent),
log('[PDF] Keyboard shortcut'),
);
}
get #pageComplete$() {
return fromEvent(this.#document, 'pageComplete').pipe(debounceTime(300));
}
close() {
this._logger.info('[PDF] Closing document');
this.#document.closeDocument();
this._pdf.closeCompareMode();
}
updateTooltipsVisibility(): void {
const current = this._userPreferenceService.getFilePreviewTooltipsPreference();
this._pdf.instance.UI.setAnnotationContentOverlayHandler(() => (current ? undefined : false));
}
init(document: DocumentViewer) {
this.#document = document;
this.loaded$ = merge(this.#documentUnloaded$, this.#documentLoaded$).pipe(shareLast());
this.pageComplete$ = this.#pageComplete$.pipe(shareLast());
this.keyUp$ = this.#keyUp$;
}
async lock() {
const document = await this.PDFDoc;
if (!document) {
return false;
}
await document.lock();
this._logger.info('[PDF] Locked');
return true;
}
setRectangleToolStyles(color: Color) {
this.#document.getTool('AnnotationCreateRectangle').setStyles({
StrokeThickness: 2,
StrokeColor: color,
FillColor: color,
Opacity: 0.6,
});
}
getHeight(page: number) {
try {
return this.#document.getPageHeight(page);
} catch {
// might throw Error: getPageHeight was called before the 'documentLoaded' event
return 0;
}
}
#setCurrentPage() {
const currentDocPage = this._activatedRoute.snapshot.queryParamMap.get('page');
this.#document.setCurrentPage(Number(currentDocPage ?? '1'));
}
#setInitialDisplayMode() {
this._pdf.instance.UI.setFitMode('FitPage');
const displayModeManager = this.#document.getDisplayModeManager();
const instanceDisplayMode = displayModeManager.getDisplayMode();
instanceDisplayMode.mode = this._pdf.isCompare ? 'Facing' : 'Single';
displayModeManager.setDisplayMode(instanceDisplayMode);
}
}

View File

@ -3,23 +3,21 @@ import WebViewer, { Core, WebViewerInstance, WebViewerOptions } from '@pdftron/w
import { environment } from '../../../../environments/environment';
import { BASE_HREF_FN } from '../../../tokens';
import { File } from '@red/domain';
import { ErrorService, log, shareDistinctLast, shareLast } from '@iqser/common-ui';
import { ErrorService, shareDistinctLast, shareLast } from '@iqser/common-ui';
import { ActivatedRoute } from '@angular/router';
import { debounceTime, distinctUntilChanged, filter, map, startWith, tap } from 'rxjs/operators';
import { BehaviorSubject, combineLatest, fromEvent, merge, Observable } from 'rxjs';
import { distinctUntilChanged, map, startWith, tap } from 'rxjs/operators';
import { BehaviorSubject, combineLatest, fromEvent, Observable } from 'rxjs';
import { ConfigService } from '../../../services/config.service';
import { NGXLogger } from 'ngx-logger';
import { DISABLED_HOTKEYS, DOCUMENT_LOADING_ERROR, USELESS_ELEMENTS } from '../utils/constants';
import { Rgb } from '../utils/types';
import { UserPreferenceService } from '../../../services/user-preference.service';
import { asList, stopAndPrevent, stopAndPreventIfNotAllowed } from '../utils/functions';
import { asList } from '../utils/functions';
import { REDAnnotationManager } from './annotation-manager.service';
import AnnotationManager = Core.AnnotationManager;
import TextTool = Core.Tools.TextTool;
import Annotation = Core.Annotations.Annotation;
import TextHighlightAnnotation = Core.Annotations.TextHighlightAnnotation;
import DocumentViewer = Core.DocumentViewer;
import Color = Core.Annotations.Color;
@Injectable()
export class PdfViewer {
@ -27,28 +25,28 @@ export class PdfViewer {
map(params => Number(params.get('page') ?? '1')),
shareDistinctLast(),
);
/**
* @deprecated Use REDDocumentViewer service instead
*/
documentViewer: DocumentViewer;
/**
* @deprecated Use REDAnnotationManager service instead
*/
annotationManager: AnnotationManager;
loaded$: Observable<boolean>;
pageComplete$: Observable<unknown>;
pageChanged$: Observable<number>;
compareMode$: Observable<boolean>;
totalPages$: Observable<number>;
keyUp$: Observable<KeyboardEvent>;
#instance: WebViewerInstance;
readonly #compareMode$ = new BehaviorSubject(false);
#currentBlob: Blob;
readonly #compareMode$ = new BehaviorSubject(false);
constructor(
private readonly _logger: NGXLogger,
private readonly _activatedRoute: ActivatedRoute,
private readonly _annotationManager: REDAnnotationManager,
private readonly _userPreferenceService: UserPreferenceService,
private readonly _injector: Injector,
) {}
@ -56,14 +54,6 @@ export class PdfViewer {
return this.#instance;
}
get PDFDoc() {
return this.document?.getPDFDoc();
}
get document() {
return this.documentViewer.getDocument();
}
get blob() {
return this.#currentBlob;
}
@ -90,16 +80,6 @@ export class PdfViewer {
return this.isCompare ? Math.ceil(currentInternalPage / 2) : currentInternalPage;
}
get #keyUp$() {
return fromEvent<KeyboardEvent>(this.documentViewer, 'keyUp').pipe(
tap(stopAndPreventIfNotAllowed),
filter($event => ($event.target as HTMLElement)?.tagName?.toLowerCase() !== 'input'),
filter($event => $event.key.startsWith('Arrow') || $event.key === 'f'),
tap(stopAndPrevent),
log('[PDF] Keyboard shortcut'),
);
}
get #totalPages$() {
const layoutChanged$ = fromEvent(this.documentViewer, 'layoutChanged').pipe(startWith(''));
const pageCount$ = layoutChanged$.pipe(
@ -111,33 +91,10 @@ export class PdfViewer {
return docChanged$.pipe(map(([pageCount, isCompare]) => (isCompare ? Math.ceil(pageCount / 2) : pageCount)));
}
get #pageComplete$() {
return fromEvent(this.documentViewer, 'pageComplete').pipe(debounceTime(300));
}
get #paginationOffset() {
return this.isCompare ? 2 : 1;
}
get #documentLoaded$() {
const event$ = fromEvent(this.documentViewer, 'documentLoaded');
const toBool$ = event$.pipe(map(() => true));
return toBool$.pipe(
tap(() => this.#setCurrentPage()),
tap(() => this.#setInitialDisplayMode()),
tap(() => this.updateTooltipsVisibility()),
tap(() => this._logger.info('[PDF] Document loaded')),
);
}
get #documentUnloaded$() {
const event$ = fromEvent(this.documentViewer, 'documentUnloaded');
const toBool$ = event$.pipe(map(() => false));
return toBool$.pipe(tap(() => this._logger.info('[PDF] Document unloaded')));
}
get #currentInternalPage() {
return this.documentViewer.getCurrentPage();
}
@ -173,12 +130,9 @@ export class PdfViewer {
this.documentViewer = this.#instance.Core.documentViewer;
this.annotationManager = this.#instance.Core.annotationManager;
this.loaded$ = merge(this.#documentUnloaded$, this.#documentLoaded$).pipe(shareLast());
this.compareMode$ = this.#compareMode$.asObservable();
this.pageComplete$ = this.#pageComplete$.pipe(shareLast());
this.pageChanged$ = this.#pageChanged$.pipe(shareLast());
this.totalPages$ = this.#totalPages$.pipe(shareDistinctLast());
this.keyUp$ = this.#keyUp$;
this.#setSelectionMode();
this.#configureElements();
this.#disableHotkeys();
@ -187,15 +141,6 @@ export class PdfViewer {
return this.#instance;
}
setRectangleToolStyles(color: Color) {
this.documentViewer.getTool('AnnotationCreateRectangle').setStyles({
StrokeThickness: 2,
StrokeColor: color,
FillColor: color,
Opacity: 0.6,
});
}
enable(dataElements: string[] | string) {
this.#instance.UI.enableElements(asList(dataElements));
}
@ -204,21 +149,6 @@ export class PdfViewer {
this.#instance.UI.disableElements(asList(dataElements));
}
getPageHeight(page: number) {
try {
return this.documentViewer.getPageHeight(page);
} catch {
// might throw Error: getPageHeight was called before the 'documentLoaded' event
return 0;
}
}
closeDocument() {
this._logger.info('[PDF] Closing document');
this.closeCompareMode();
this.documentViewer.closeDocument();
}
openCompareMode() {
this.#compareMode$.next(true);
}
@ -227,17 +157,6 @@ export class PdfViewer {
this.#compareMode$.next(false);
}
async lockDocument() {
const document = await this.PDFDoc;
if (!document) {
return false;
}
await document.lock();
this._logger.info('[PDF] Locked');
return true;
}
async loadDocument(blob: Blob, file: File) {
this._logger.info('[PDF] Loading document', blob, file);
const onError = () => {
@ -255,11 +174,6 @@ export class PdfViewer {
this.#instance.UI.loadDocument(document, { filename: file?.filename + '.pdf' ?? 'document.pdf', onError });
}
updateTooltipsVisibility(): void {
const current = this._userPreferenceService.getFilePreviewTooltipsPreference();
this.#instance.UI.setAnnotationContentOverlayHandler(() => (current ? undefined : false));
}
quad(x1: number, y1: number, x2: number, y2: number, x3: number, y3: number, x4: number, y4: number) {
return new this.#instance.Core.Math.Quad(x1, y1, x2, y2, x3, y3, x4, y4);
}
@ -297,14 +211,6 @@ export class PdfViewer {
});
}
#setInitialDisplayMode() {
this.#instance.UI.setFitMode('FitPage');
const displayModeManager = this.documentViewer.getDisplayModeManager();
const instanceDisplayMode = displayModeManager.getDisplayMode();
instanceDisplayMode.mode = this.isCompare ? 'Facing' : 'Single';
displayModeManager.setDisplayMode(instanceDisplayMode);
}
#navigateTo(pageNumber: number) {
if (this.#currentInternalPage !== pageNumber) {
this.documentViewer.displayPageLocation(pageNumber, 0, 0);
@ -337,10 +243,4 @@ export class PdfViewer {
return WebViewer(options, htmlElement);
}
#setCurrentPage() {
const currentDocPage = this._activatedRoute.snapshot.queryParamMap.get('page');
console.log(currentDocPage);
this.documentViewer.setCurrentPage(Number(currentDocPage ?? '1'));
}
}

View File

@ -5,6 +5,7 @@ import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
import { TranslateService } from '@ngx-translate/core';
import { BASE_HREF_FN, BaseHrefFn } from '../../../tokens';
import { PdfViewer } from './pdf-viewer.service';
import { REDDocumentViewer } from './document-viewer.service';
@Injectable()
export class TooltipsService {
@ -14,6 +15,7 @@ export class TooltipsService {
constructor(
@Inject(BASE_HREF_FN) private readonly _convertPath: BaseHrefFn,
private readonly _pdf: PdfViewer,
private readonly _documentViewer: REDDocumentViewer,
private readonly _userPreferenceService: UserPreferenceService,
private readonly _translateService: TranslateService,
) {}
@ -31,7 +33,7 @@ export class TooltipsService {
async toggleTooltips(): Promise<void> {
await this._userPreferenceService.toggleFilePreviewTooltipsPreference();
this._pdf.updateTooltipsVisibility();
this._documentViewer.updateTooltipsVisibility();
this._pdf.instance.UI.updateElement(HeaderElements.TOGGLE_TOOLTIPS, {
title: this.toggleTooltipsBtnTitle,
img: this.toggleTooltipsBtnIcon,