RED-3988: replace old pdf viewer
This commit is contained in:
parent
7ad5fe09ac
commit
edc53c7971
@ -7,11 +7,11 @@ import { UserService } from '@services/user.service';
|
||||
import { AnnotationReferencesService } from '../../services/annotation-references.service';
|
||||
import { MultiSelectService } from '../../services/multi-select.service';
|
||||
import { FilePreviewStateService } from '../../services/file-preview-state.service';
|
||||
import { HelpModeService, ScrollableParentView, ScrollableParentViews } from '@iqser/common-ui';
|
||||
import { PdfViewer } from '../../services/pdf-viewer.service';
|
||||
import { HelpModeService } from '@iqser/common-ui';
|
||||
import { FileDataService } from '../../services/file-data.service';
|
||||
import { DictionariesMapService } from '@services/entity-services/dictionaries-map.service';
|
||||
import { ViewModeService } from '../../services/view-mode.service';
|
||||
import { ReusablePdfViewer } from '../../../shared/components/reusable-pdf-viewer/reusable-pdf-viewer.service';
|
||||
|
||||
export const AnnotationButtonTypes = {
|
||||
dark: 'dark',
|
||||
@ -40,7 +40,7 @@ export class AnnotationActionsComponent implements OnChanges {
|
||||
readonly helpModeService: HelpModeService,
|
||||
private readonly _changeRef: ChangeDetectorRef,
|
||||
private readonly _userService: UserService,
|
||||
private readonly _pdf: PdfViewer,
|
||||
private readonly _reusablePdf: ReusablePdfViewer,
|
||||
private readonly _state: FilePreviewStateService,
|
||||
private readonly _permissionsService: PermissionsService,
|
||||
private readonly _fileDataService: FileDataService,
|
||||
@ -59,11 +59,7 @@ export class AnnotationActionsComponent implements OnChanges {
|
||||
}
|
||||
|
||||
get viewerAnnotations() {
|
||||
if (this._pdf.annotationManager) {
|
||||
return this._annotations.map(a => this._pdf.annotationManager.getAnnotationById(a.id));
|
||||
} else {
|
||||
return [];
|
||||
}
|
||||
return this._reusablePdf.getAnnotationsById(this._annotations.map(a => a.id));
|
||||
}
|
||||
|
||||
get isVisible() {
|
||||
@ -98,15 +94,15 @@ export class AnnotationActionsComponent implements OnChanges {
|
||||
|
||||
hideAnnotation($event: MouseEvent) {
|
||||
$event.stopPropagation();
|
||||
this._pdf.annotationManager.hideAnnotations(this.viewerAnnotations);
|
||||
this._pdf.annotationManager.deselectAllAnnotations();
|
||||
this._reusablePdf.hideAnnotations(this.viewerAnnotations);
|
||||
this._reusablePdf.deselectAnnotations();
|
||||
this._fileDataService.updateHiddenAnnotations(this.viewerAnnotations, true);
|
||||
}
|
||||
|
||||
showAnnotation($event: MouseEvent) {
|
||||
$event.stopPropagation();
|
||||
this._pdf.annotationManager.showAnnotations(this.viewerAnnotations);
|
||||
this._pdf.annotationManager.deselectAllAnnotations();
|
||||
this._reusablePdf.showAnnotations(this.viewerAnnotations);
|
||||
this._reusablePdf.deselectAnnotations();
|
||||
this._fileDataService.updateHiddenAnnotations(this.viewerAnnotations, false);
|
||||
}
|
||||
|
||||
|
||||
@ -18,6 +18,7 @@ import { ViewModeService } from '../../services/view-mode.service';
|
||||
import { BehaviorSubject } from 'rxjs';
|
||||
import { TextHighlightsGroup } from '@red/domain';
|
||||
import { PdfViewer } from '../../services/pdf-viewer.service';
|
||||
import { ReusablePdfViewer } from '../../../shared/components/reusable-pdf-viewer/reusable-pdf-viewer.service';
|
||||
|
||||
@Component({
|
||||
selector: 'redaction-annotations-list',
|
||||
@ -41,6 +42,7 @@ export class AnnotationsListComponent extends HasScrollbarDirective implements O
|
||||
private readonly _userPreferenceService: UserPreferenceService,
|
||||
private readonly _viewModeService: ViewModeService,
|
||||
private readonly _pdf: PdfViewer,
|
||||
private readonly _reusablePdf: ReusablePdfViewer,
|
||||
private readonly _listingService: ListingService<AnnotationWrapper>,
|
||||
readonly annotationReferencesService: AnnotationReferencesService,
|
||||
) {
|
||||
@ -69,7 +71,7 @@ export class AnnotationsListComponent extends HasScrollbarDirective implements O
|
||||
this.pagesPanelActive.emit(false);
|
||||
|
||||
if (this._listingService.isSelected(annotation)) {
|
||||
this._pdf.deselectAnnotations([annotation]);
|
||||
this._reusablePdf.deselectAnnotations([annotation]);
|
||||
} else {
|
||||
const canMultiSelect = this._multiSelectService.isEnabled;
|
||||
if (canMultiSelect && ($event?.ctrlKey || $event?.metaKey) && this._listingService.selected.length > 0) {
|
||||
|
||||
@ -48,7 +48,7 @@
|
||||
<div *ngIf="multiSelectService.active$ | async" class="multi-select">
|
||||
<div class="selected-wrapper">
|
||||
<iqser-round-checkbox
|
||||
(click)="pdf.deselectAllAnnotations()"
|
||||
(click)="reusablePdf.deselectAnnotations()"
|
||||
[indeterminate]="listingService.areSomeSelected$ | async"
|
||||
type="with-bg"
|
||||
></iqser-round-checkbox>
|
||||
|
||||
@ -37,6 +37,7 @@ import { ViewModeService } from '../../services/view-mode.service';
|
||||
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
||||
import { FileDataService } from '../../services/file-data.service';
|
||||
import { PdfViewer } from '../../services/pdf-viewer.service';
|
||||
import { ReusablePdfViewer } from '../../../shared/components/reusable-pdf-viewer/reusable-pdf-viewer.service';
|
||||
|
||||
const COMMAND_KEY_ARRAY = ['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown', 'Escape'];
|
||||
const ALL_HOTKEY_ARRAY = ['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown'];
|
||||
@ -78,10 +79,11 @@ export class FileWorkloadComponent {
|
||||
readonly viewModeService: ViewModeService,
|
||||
readonly listingService: ListingService<AnnotationWrapper>,
|
||||
readonly pdf: PdfViewer,
|
||||
readonly reusablePdf: ReusablePdfViewer,
|
||||
private readonly _changeDetectorRef: ChangeDetectorRef,
|
||||
private readonly _annotationProcessingService: AnnotationProcessingService,
|
||||
) {
|
||||
this.pdf.currentPage$.pipe(takeWhile(() => !!this)).subscribe(pageNumber => {
|
||||
this.reusablePdf.currentPage$.pipe(takeWhile(() => !!this)).subscribe(pageNumber => {
|
||||
this._scrollViews();
|
||||
this.scrollAnnotationsToPage(pageNumber, 'always');
|
||||
});
|
||||
@ -134,7 +136,7 @@ export class FileWorkloadComponent {
|
||||
return this.multiSelectService.inactive$.pipe(
|
||||
tap(value => {
|
||||
if (value) {
|
||||
this.pdf.deselectAllAnnotations();
|
||||
this.reusablePdf.deselectAnnotations();
|
||||
}
|
||||
}),
|
||||
shareDistinctLast(),
|
||||
@ -183,7 +185,7 @@ export class FileWorkloadComponent {
|
||||
}
|
||||
|
||||
deselectAllOnActivePage(): void {
|
||||
this.pdf.deselectAnnotations(this.activeAnnotations);
|
||||
this.reusablePdf.deselectAnnotations(this.activeAnnotations);
|
||||
}
|
||||
|
||||
@HostListener('window:keyup', ['$event'])
|
||||
|
||||
@ -1,29 +1,31 @@
|
||||
<div class="page">
|
||||
<div #viewer [id]="(stateService.file$ | async).fileId" class="viewer"></div>
|
||||
</div>
|
||||
<ng-container *ngIf="reusablePdf.show$ | async">
|
||||
<input #compareFileInput (change)="uploadFile($event.target['files'])" accept="application/pdf" class="file-upload-input" type="file" />
|
||||
|
||||
<input #compareFileInput (change)="uploadFile($event.target['files'])" accept="application/pdf" class="file-upload-input" type="file" />
|
||||
<div class="pagination noselect">
|
||||
<div (click)="pdfViewer.navigatePreviousPage()">
|
||||
<mat-icon class="chevron-icon" svgIcon="red:nav-prev"></mat-icon>
|
||||
</div>
|
||||
|
||||
<div *ngIf="pdfViewer?.totalPages && pdfViewer?.currentPage" class="pagination noselect">
|
||||
<div (click)="pdfViewer.navigatePreviousPage()">
|
||||
<mat-icon class="chevron-icon" svgIcon="red:nav-prev"></mat-icon>
|
||||
<div>
|
||||
<input
|
||||
#pageInput
|
||||
(change)="pdfViewer.navigateToPage(pageInput.value)"
|
||||
[max]="pdfViewer.totalPages"
|
||||
[value]="pdfViewer.currentPage"
|
||||
class="page-number-input"
|
||||
min="1"
|
||||
type="number"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="separator">/</div>
|
||||
|
||||
<div>
|
||||
{{ pdfViewer.totalPages }}
|
||||
</div>
|
||||
|
||||
<div (click)="pdfViewer.navigateNextPage()">
|
||||
<mat-icon class="chevron-icon" svgIcon="red:nav-next"></mat-icon>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<input
|
||||
#pageInput
|
||||
(change)="pdfViewer.navigateToPage(pageInput.value)"
|
||||
[max]="pdfViewer.totalPages"
|
||||
[value]="pdfViewer.currentPage"
|
||||
class="page-number-input"
|
||||
min="1"
|
||||
type="number"
|
||||
/>
|
||||
</div>
|
||||
<div class="separator">/</div>
|
||||
<div>
|
||||
{{ pdfViewer.totalPages }}
|
||||
</div>
|
||||
<div (click)="pdfViewer.navigateNextPage()">
|
||||
<mat-icon class="chevron-icon" svgIcon="red:nav-next"></mat-icon>
|
||||
</div>
|
||||
</div>
|
||||
</ng-container>
|
||||
|
||||
@ -1,14 +1,3 @@
|
||||
.page {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.viewer {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.searching {
|
||||
position: absolute;
|
||||
top: 9px;
|
||||
@ -22,6 +11,7 @@
|
||||
}
|
||||
|
||||
.pagination {
|
||||
z-index: 1;
|
||||
position: absolute;
|
||||
bottom: 20px;
|
||||
left: 50%;
|
||||
|
||||
@ -26,7 +26,7 @@ import { AnnotationActionsService } from '../../services/annotation-actions.serv
|
||||
import { UserPreferenceService } from '@services/user-preference.service';
|
||||
import { BASE_HREF } from '../../../../tokens';
|
||||
import { ConfigService } from '@services/config.service';
|
||||
import { AutoUnsubscribe, ConfirmationDialogInput, CustomError, ErrorService, LoadingService } from '@iqser/common-ui';
|
||||
import { AutoUnsubscribe, ConfirmationDialogInput, CustomError, ErrorService, LoadingService, log } from '@iqser/common-ui';
|
||||
import { PdfViewer } from '../../services/pdf-viewer.service';
|
||||
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
||||
import { toPosition } from '../../utils/pdf-calculation.utils';
|
||||
@ -44,8 +44,6 @@ import { ViewerHeaderConfigService } from '../../services/viewer-header-config.s
|
||||
import { TooltipsService } from '../../services/tooltips.service';
|
||||
import { ManualRedactionService } from '../../services/manual-redaction.service';
|
||||
import { ReusablePdfViewer } from '../../../shared/components/reusable-pdf-viewer/reusable-pdf-viewer.service';
|
||||
import Tools = Core.Tools;
|
||||
import TextTool = Tools.TextTool;
|
||||
import Annotation = Core.Annotations.Annotation;
|
||||
|
||||
const DocLoadingError = new CustomError(_('error.file-preview.label'), _('error.file-preview.action'), 'iqser:refresh');
|
||||
@ -62,7 +60,6 @@ export class PdfViewerComponent extends AutoUnsubscribe implements OnInit, OnCha
|
||||
@Output() readonly manualAnnotationRequested = new EventEmitter<ManualRedactionEntryWrapper>();
|
||||
@Output() readonly pageChanged = new EventEmitter<number>();
|
||||
@Output() readonly keyUp = new EventEmitter<KeyboardEvent>();
|
||||
@ViewChild('viewer', { static: true }) viewer: ElementRef;
|
||||
@ViewChild('compareFileInput', { static: true }) compareFileInput: ElementRef;
|
||||
instance: WebViewerInstance;
|
||||
documentViewer: Core.DocumentViewer;
|
||||
@ -85,7 +82,7 @@ export class PdfViewerComponent extends AutoUnsubscribe implements OnInit, OnCha
|
||||
private readonly _headerConfigService: ViewerHeaderConfigService,
|
||||
private readonly _tooltipsService: TooltipsService,
|
||||
private readonly _errorService: ErrorService,
|
||||
private readonly _reusablePdfViewer: ReusablePdfViewer,
|
||||
readonly reusablePdf: ReusablePdfViewer,
|
||||
readonly stateService: FilePreviewStateService,
|
||||
readonly viewModeService: ViewModeService,
|
||||
readonly multiSelectService: MultiSelectService,
|
||||
@ -94,27 +91,25 @@ export class PdfViewerComponent extends AutoUnsubscribe implements OnInit, OnCha
|
||||
super();
|
||||
}
|
||||
|
||||
async ngOnInit() {
|
||||
ngOnInit() {
|
||||
this._setReadyAndInitialState = this._setReadyAndInitialState.bind(this);
|
||||
await this._loadViewer();
|
||||
this._loadViewer();
|
||||
|
||||
console.log('pdf-viewer.component.ts: ngOnInit');
|
||||
this.addActiveScreenSubscription = this.stateService.blob$
|
||||
.pipe(
|
||||
switchMap(blob => from(this.pdfViewer.lockDocument()).pipe(map(() => blob))),
|
||||
log('Reload blob'),
|
||||
switchMap(blob => from(this.reusablePdf.lockDocument()).pipe(map(() => blob))),
|
||||
withLatestFrom(this.stateService.file$),
|
||||
tap(() => this._errorService.clear()),
|
||||
tap(([blob, file]) => this._loadDocument(blob, file)),
|
||||
tap(([blob, file]) => this._reusablePdfViewer.loadDocument(blob, file)),
|
||||
tap(([blob, file]) => this.reusablePdf.loadDocument(blob, file)),
|
||||
tap(() => this._pageRotationService.clearRotationsHideActions()),
|
||||
)
|
||||
.subscribe();
|
||||
}
|
||||
|
||||
ngOnChanges(changes: SimpleChanges): void {
|
||||
if (!this.instance) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (changes.canPerformActions) {
|
||||
if (changes.canPerformActions && !!this.instance) {
|
||||
this._handleCustomActions();
|
||||
}
|
||||
}
|
||||
@ -187,14 +182,12 @@ export class PdfViewerComponent extends AutoUnsubscribe implements OnInit, OnCha
|
||||
fileReader.readAsArrayBuffer(fileToCompare);
|
||||
}
|
||||
|
||||
private async _loadViewer() {
|
||||
this.instance = await this.pdfViewer.loadViewer(this.viewer.nativeElement as HTMLElement);
|
||||
this.documentViewer = this.pdfViewer.documentViewer;
|
||||
this.annotationManager = this.pdfViewer.annotationManager;
|
||||
private _loadViewer() {
|
||||
this.instance = this.reusablePdf.instance;
|
||||
this.documentViewer = this.reusablePdf.documentViewer;
|
||||
this.annotationManager = this.reusablePdf.annotationManager;
|
||||
|
||||
this._setSelectionMode();
|
||||
this._configureElements();
|
||||
this.pdfViewer.disableHotkeys();
|
||||
this._configureTextPopup();
|
||||
|
||||
this.annotationManager.addEventListener('annotationSelected', (annotations: Annotation[], action) => {
|
||||
@ -202,7 +195,7 @@ export class PdfViewerComponent extends AutoUnsubscribe implements OnInit, OnCha
|
||||
|
||||
if (action === 'deselected') {
|
||||
// Remove deselected annotations from selected list
|
||||
nextAnnotations = this.annotationManager.getSelectedAnnotations().filter(ann => !annotations.find(a => a.Id === ann.Id));
|
||||
nextAnnotations = this.annotationManager.getSelectedAnnotations().filter(ann => !annotations.some(a => a.Id === ann.Id));
|
||||
} else if (!this.multiSelectService.isEnabled) {
|
||||
// Only choose the last selected annotation, to bypass viewer multi select
|
||||
nextAnnotations = annotations;
|
||||
@ -217,8 +210,8 @@ export class PdfViewerComponent extends AutoUnsubscribe implements OnInit, OnCha
|
||||
}
|
||||
|
||||
if (!this.multiSelectService.isEnabled) {
|
||||
this.pdfViewer.deselectAnnotations(
|
||||
this._fileDataService.all.filter(wrapper => !nextAnnotations.find(ann => ann.Id === wrapper.id)),
|
||||
this.reusablePdf.deselectAnnotations(
|
||||
this._fileDataService.all.filter(wrapper => !nextAnnotations.some(ann => ann.Id === wrapper.id)),
|
||||
);
|
||||
}
|
||||
this.#configureAnnotationSpecificActions(annotations);
|
||||
@ -236,7 +229,7 @@ export class PdfViewerComponent extends AutoUnsubscribe implements OnInit, OnCha
|
||||
});
|
||||
|
||||
this.documentViewer.addEventListener('pageNumberUpdated', (pageNumber: number) => {
|
||||
this.pdfViewer.deselectAllAnnotations();
|
||||
this.reusablePdf.deselectAnnotations();
|
||||
this._ngZone.run(() => this.pageChanged.emit(pageNumber));
|
||||
return this._handleCustomActions();
|
||||
});
|
||||
@ -302,11 +295,6 @@ export class PdfViewerComponent extends AutoUnsubscribe implements OnInit, OnCha
|
||||
return this._baseHref + path;
|
||||
}
|
||||
|
||||
private _setSelectionMode(): void {
|
||||
const textTool = this.instance.Core.Tools.TextTool as unknown as TextTool;
|
||||
textTool.SELECTION_MODE = this._configService.values.SELECTION_MODE;
|
||||
}
|
||||
|
||||
private _toggleRectangleAnnotationAction(readonly = false) {
|
||||
if (!readonly) {
|
||||
this.instance.UI.enableElements([TextPopups.ADD_RECTANGLE]);
|
||||
@ -316,29 +304,6 @@ export class PdfViewerComponent extends AutoUnsubscribe implements OnInit, OnCha
|
||||
}
|
||||
|
||||
private _configureElements() {
|
||||
this.instance.UI.disableElements([
|
||||
'pageNavOverlay',
|
||||
'menuButton',
|
||||
'selectToolButton',
|
||||
'textHighlightToolButton',
|
||||
'textUnderlineToolButton',
|
||||
'textSquigglyToolButton',
|
||||
'textStrikeoutToolButton',
|
||||
'viewControlsButton',
|
||||
'contextMenuPopup',
|
||||
'linkButton',
|
||||
'toggleNotesButton',
|
||||
'notesPanel',
|
||||
'thumbnailControl',
|
||||
'documentControl',
|
||||
'ribbons',
|
||||
'toolsHeader',
|
||||
'rotateClockwiseButton',
|
||||
'rotateCounterClockwiseButton',
|
||||
'annotationStyleEditButton',
|
||||
'annotationGroupButton',
|
||||
]);
|
||||
|
||||
this._headerConfigService.initialize(this.compareFileInput);
|
||||
|
||||
const dossierTemplateId = this.dossier.dossierTemplateId;
|
||||
@ -568,7 +533,6 @@ export class PdfViewerComponent extends AutoUnsubscribe implements OnInit, OnCha
|
||||
const document = await pdfNet.PDFDoc.createFromBuffer(await blob.arrayBuffer());
|
||||
await document.flattenAnnotations(false);
|
||||
this.instance.UI.loadDocument(document, { filename: file?.filename + '.pdf' ?? 'document.pdf', onError });
|
||||
this._pageRotationService.clearRotationsHideActions();
|
||||
}
|
||||
|
||||
private _setReadyAndInitialState() {
|
||||
|
||||
@ -68,9 +68,7 @@
|
||||
(keyUp)="handleKeyEvent($event); workloadComponent.handleKeyEvent($event)"
|
||||
(manualAnnotationRequested)="openManualAnnotationDialog($event)"
|
||||
(pageChanged)="viewerPageChanged($event)"
|
||||
*ngIf="displayPdfViewer"
|
||||
[canPerformActions]="canPerformAnnotationActions$ | async"
|
||||
[class.hidden]="!ready"
|
||||
[dossier]="dossier"
|
||||
></redaction-pdf-viewer>
|
||||
</div>
|
||||
@ -88,7 +86,7 @@
|
||||
<redaction-file-workload
|
||||
(selectPage)="selectPage($event)"
|
||||
*ngIf="!file.excluded"
|
||||
[activeViewerPage]="pdf.currentPage$ | async"
|
||||
[activeViewerPage]="reusablePdf.currentPage$ | async"
|
||||
[annotationActionsTemplate]="annotationActionsTemplate"
|
||||
[dialogRef]="dialogRef"
|
||||
[file]="file"
|
||||
|
||||
@ -31,7 +31,7 @@ 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, map, startWith, switchMap, tap } from 'rxjs/operators';
|
||||
import { catchError, filter, map, startWith, switchMap, tap } from 'rxjs/operators';
|
||||
import { FilesMapService } from '@services/files/files-map.service';
|
||||
import { ExcludedPagesService } from './services/excluded-pages.service';
|
||||
import { ViewModeService } from './services/view-mode.service';
|
||||
@ -51,6 +51,7 @@ import { FileDataService } from './services/file-data.service';
|
||||
import { ActionsHelpModeKeys, ALL_HOTKEYS } from './utils/constants';
|
||||
import { NGXLogger } from 'ngx-logger';
|
||||
import { StampService } from './services/stamp.service';
|
||||
import { ReusablePdfViewer } from '../shared/components/reusable-pdf-viewer/reusable-pdf-viewer.service';
|
||||
import Annotation = Core.Annotations.Annotation;
|
||||
|
||||
@Component({
|
||||
@ -63,12 +64,10 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
|
||||
|
||||
dialogRef: MatDialogRef<unknown>;
|
||||
fullScreen = false;
|
||||
displayPdfViewer = false;
|
||||
readonly canPerformAnnotationActions$: Observable<boolean>;
|
||||
readonly fileId = this.state.fileId;
|
||||
readonly dossierId = this.state.dossierId;
|
||||
readonly file$ = this.state.file$.pipe(tap(file => this._fileDataService.loadAnnotations(file)));
|
||||
ready = false;
|
||||
@ViewChild(FileWorkloadComponent) readonly workloadComponent: FileWorkloadComponent;
|
||||
private _lastPage: string;
|
||||
@ViewChild('annotationFilterTemplate', {
|
||||
@ -79,6 +78,7 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
|
||||
|
||||
constructor(
|
||||
readonly pdf: PdfViewer,
|
||||
readonly reusablePdf: ReusablePdfViewer,
|
||||
readonly documentInfoService: DocumentInfoService,
|
||||
readonly state: FilePreviewStateService,
|
||||
readonly listingService: ListingService<AnnotationWrapper>,
|
||||
@ -143,7 +143,7 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
|
||||
async updateViewMode(): Promise<void> {
|
||||
this._logger.info(`[PDF] Update ${this._viewModeService.viewMode} view mode`);
|
||||
|
||||
const annotations = this.pdf.getAnnotations(a => a.getCustomData('redact-manager'));
|
||||
const annotations = this.reusablePdf.getAnnotations(a => Boolean(a.getCustomData('redact-manager')));
|
||||
const redactions = annotations.filter(a => a.getCustomData('redaction'));
|
||||
|
||||
switch (this._viewModeService.viewMode) {
|
||||
@ -156,8 +156,8 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
|
||||
.filter(a => !ocrAnnotationIds.includes(a.Id));
|
||||
const nonStandardEntries = annotations.filter(a => a.getCustomData('changeLogRemoved') === 'true');
|
||||
this._setAnnotationsOpacity(standardEntries, true);
|
||||
this.pdf.showAnnotations(standardEntries);
|
||||
this.pdf.hideAnnotations(nonStandardEntries);
|
||||
this.reusablePdf.showAnnotations(standardEntries);
|
||||
this.reusablePdf.hideAnnotations(nonStandardEntries);
|
||||
break;
|
||||
}
|
||||
case 'DELTA': {
|
||||
@ -165,21 +165,21 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
|
||||
const nonChangeLogEntries = annotations.filter(a => a.getCustomData('changeLog') === 'false');
|
||||
this._setAnnotationsColor(redactions, 'annotationColor');
|
||||
this._setAnnotationsOpacity(changeLogEntries, true);
|
||||
this.pdf.showAnnotations(changeLogEntries);
|
||||
this.pdf.hideAnnotations(nonChangeLogEntries);
|
||||
this.reusablePdf.showAnnotations(changeLogEntries);
|
||||
this.reusablePdf.hideAnnotations(nonChangeLogEntries);
|
||||
break;
|
||||
}
|
||||
case 'REDACTED': {
|
||||
const nonRedactionEntries = annotations.filter(a => a.getCustomData('redaction') === 'false');
|
||||
this._setAnnotationsOpacity(redactions);
|
||||
this._setAnnotationsColor(redactions, 'redactionColor');
|
||||
this.pdf.showAnnotations(redactions);
|
||||
this.pdf.hideAnnotations(nonRedactionEntries);
|
||||
this.reusablePdf.showAnnotations(redactions);
|
||||
this.reusablePdf.hideAnnotations(nonRedactionEntries);
|
||||
break;
|
||||
}
|
||||
case 'TEXT_HIGHLIGHTS': {
|
||||
this._loadingService.start();
|
||||
this.pdf.hideAnnotations(annotations);
|
||||
this.reusablePdf.hideAnnotations(annotations);
|
||||
const highlights = await this._fileDataService.loadTextHighlights();
|
||||
await this._annotationDrawService.draw(highlights);
|
||||
this._loadingService.stop();
|
||||
@ -192,7 +192,7 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
|
||||
|
||||
ngOnDetach(): void {
|
||||
this._pageRotationService.clearRotations();
|
||||
this.displayPdfViewer = false;
|
||||
this.reusablePdf.closeDocument();
|
||||
super.ngOnDetach();
|
||||
this._changeDetectorRef.markForCheck();
|
||||
}
|
||||
@ -204,6 +204,7 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
|
||||
|
||||
this._viewModeService.compareMode = false;
|
||||
this._viewModeService.switchToStandard();
|
||||
this.state.reloadBlob();
|
||||
|
||||
await this.ngOnInit();
|
||||
await this._fileDataService.loadRedactionLog();
|
||||
@ -218,7 +219,6 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
|
||||
return this._handleDeletedFile();
|
||||
}
|
||||
|
||||
this.ready = false;
|
||||
this._loadingService.start();
|
||||
await this.userPreferenceService.saveLastOpenedFileForDossier(this.dossierId, this.fileId);
|
||||
this._subscribeToFileUpdates();
|
||||
@ -228,8 +228,6 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
|
||||
const reanalyzeFiles = reanalysisService.reanalyzeFilesForDossier([file], this.dossierId, { force: true });
|
||||
await firstValueFrom(reanalyzeFiles);
|
||||
}
|
||||
|
||||
this.displayPdfViewer = true;
|
||||
}
|
||||
|
||||
handleAnnotationSelected(annotationIds: string[]) {
|
||||
@ -251,9 +249,9 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
|
||||
null,
|
||||
{ manualRedactionEntryWrapper, dossierId: this.dossierId, file },
|
||||
(wrappers: ManualRedactionEntryWrapper[]) => {
|
||||
const selectedAnnotations = this.pdf.annotationManager.getSelectedAnnotations();
|
||||
const selectedAnnotations = this.reusablePdf.annotationManager.getSelectedAnnotations();
|
||||
if (selectedAnnotations.length > 0) {
|
||||
this.pdf.deleteAnnotations([selectedAnnotations[0].Id]);
|
||||
this.reusablePdf.deleteAnnotations([selectedAnnotations[0].Id]);
|
||||
}
|
||||
const manualRedactionService = this._injector.get(ManualRedactionService);
|
||||
const add$ = manualRedactionService.addAnnotation(
|
||||
@ -311,13 +309,6 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
|
||||
|
||||
@Debounce(100)
|
||||
async viewerReady() {
|
||||
this.ready = true;
|
||||
this._setExcludedPageStyles();
|
||||
|
||||
this.pdf.documentViewer.addEventListener('pageComplete', () => {
|
||||
this._setExcludedPageStyles();
|
||||
});
|
||||
|
||||
// Go to initial page from query params
|
||||
const pageNumber: string = this._lastPage || this._activatedRoute.snapshot.queryParams.page;
|
||||
if (pageNumber) {
|
||||
@ -356,8 +347,11 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
|
||||
}
|
||||
|
||||
loadAnnotations() {
|
||||
const documentLoaded$ = this.pdf.documentLoaded$.pipe(tap(() => this.viewerReady()));
|
||||
const currentPageAnnotations$ = combineLatest([this.pdf.currentPage$, this._fileDataService.annotations$]).pipe(
|
||||
const documentLoaded$ = this.reusablePdf.show$.pipe(
|
||||
filter(s => s),
|
||||
tap(() => this.viewerReady()),
|
||||
);
|
||||
const currentPageAnnotations$ = combineLatest([this.reusablePdf.currentPage$, this._fileDataService.annotations$]).pipe(
|
||||
map(([page, annotations]) => annotations.filter(annotation => annotation.pageNumber === page)),
|
||||
);
|
||||
let start;
|
||||
@ -382,12 +376,12 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
|
||||
}
|
||||
|
||||
this._logger.info('[ANNOTATIONS] To delete: ', annotationsToDelete);
|
||||
this.pdf.deleteAnnotations(annotationsToDelete.map(annotation => annotation.id));
|
||||
this.reusablePdf.deleteAnnotations(annotationsToDelete.map(annotation => annotation.id));
|
||||
}
|
||||
|
||||
drawChangedAnnotations(oldAnnotations: AnnotationWrapper[], newAnnotations: AnnotationWrapper[]) {
|
||||
let annotationsToDraw: readonly AnnotationWrapper[];
|
||||
const annotationsList = this.pdf.annotationManager.getAnnotationsList();
|
||||
const annotationsList = this.reusablePdf.getAnnotations();
|
||||
const ann = annotationsList.map(a => oldAnnotations.find(oldAnnotation => oldAnnotation.id === a.Id));
|
||||
const hasAnnotations = ann.filter(a => !!a).length > 0;
|
||||
|
||||
@ -402,7 +396,7 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
|
||||
}
|
||||
|
||||
this._logger.info('[ANNOTATIONS] To draw: ', annotationsToDraw);
|
||||
this.pdf.deleteAnnotations(annotationsToDraw.map(a => a.annotationId));
|
||||
this.reusablePdf.deleteAnnotations(annotationsToDraw.map(a => a.annotationId));
|
||||
return this._cleanupAndRedrawAnnotations(annotationsToDraw);
|
||||
}
|
||||
|
||||
@ -478,14 +472,14 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
|
||||
|
||||
#deactivateMultiSelect() {
|
||||
this.multiSelectService.deactivate();
|
||||
this.pdf.deselectAllAnnotations();
|
||||
this.reusablePdf.deselectAnnotations();
|
||||
this.handleAnnotationSelected([]);
|
||||
}
|
||||
|
||||
private _setExcludedPageStyles() {
|
||||
const file = this._filesMapService.get(this.dossierId, this.fileId);
|
||||
setTimeout(() => {
|
||||
const iframeDoc = this.pdf.UI.iframeWindow.document;
|
||||
const iframeDoc = this.reusablePdf.instance.UI.iframeWindow.document;
|
||||
const pageContainer = iframeDoc.getElementById(`pageWidgetContainer${this.pdf.currentPage}`);
|
||||
if (pageContainer) {
|
||||
if (file.excludedPages.includes(this.pdf.currentPage)) {
|
||||
@ -519,6 +513,10 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
|
||||
}),
|
||||
)
|
||||
.subscribe();
|
||||
|
||||
this.addActiveScreenSubscription = this.reusablePdf.pageComplete$.subscribe(() => {
|
||||
this._setExcludedPageStyles();
|
||||
});
|
||||
}
|
||||
|
||||
private _handleDeletedDossier(): void {
|
||||
|
||||
@ -35,6 +35,7 @@ import { PdfViewer } from './pdf-viewer.service';
|
||||
import { FilePreviewDialogService } from './file-preview-dialog.service';
|
||||
import { DictionariesMapService } from '@services/entity-services/dictionaries-map.service';
|
||||
import { FileDataService } from './file-data.service';
|
||||
import { ReusablePdfViewer } from '../../shared/components/reusable-pdf-viewer/reusable-pdf-viewer.service';
|
||||
import Quad = Core.Math.Quad;
|
||||
|
||||
@Injectable()
|
||||
@ -49,6 +50,7 @@ export class AnnotationActionsService {
|
||||
private readonly _dialogService: FilePreviewDialogService,
|
||||
private readonly _dialog: MatDialog,
|
||||
private readonly _pdf: PdfViewer,
|
||||
private readonly _reusablePdf: ReusablePdfViewer,
|
||||
private readonly _annotationDrawService: AnnotationDrawService,
|
||||
private readonly _activeDossiersService: ActiveDossiersService,
|
||||
private readonly _dictionariesMapService: DictionariesMapService,
|
||||
@ -395,21 +397,21 @@ export class AnnotationActionsService {
|
||||
|
||||
annotationWrapper.resizing = true;
|
||||
|
||||
const viewerAnnotation = this._pdf.annotationManager.getAnnotationById(annotationWrapper.id);
|
||||
const viewerAnnotation = this._reusablePdf.annotationManager.getAnnotationById(annotationWrapper.id);
|
||||
if (annotationWrapper.rectangle || annotationWrapper.imported || annotationWrapper.isImage) {
|
||||
this._pdf.deleteAnnotations([annotationWrapper.id]);
|
||||
this._reusablePdf.deleteAnnotations([annotationWrapper.id]);
|
||||
const rectangleAnnotation = this.#generateRectangle(annotationWrapper);
|
||||
this._pdf.annotationManager.addAnnotation(rectangleAnnotation, { imported: true });
|
||||
await this._pdf.annotationManager.drawAnnotationsFromList([rectangleAnnotation]);
|
||||
this._pdf.annotationManager.selectAnnotation(rectangleAnnotation);
|
||||
this._reusablePdf.annotationManager.addAnnotation(rectangleAnnotation, { imported: true });
|
||||
await this._reusablePdf.annotationManager.drawAnnotationsFromList([rectangleAnnotation]);
|
||||
this._reusablePdf.annotationManager.selectAnnotation(rectangleAnnotation);
|
||||
return;
|
||||
}
|
||||
|
||||
viewerAnnotation.ReadOnly = false;
|
||||
viewerAnnotation.Hidden = false;
|
||||
viewerAnnotation.disableRotationControl();
|
||||
this._pdf.annotationManager.redrawAnnotation(viewerAnnotation);
|
||||
this._pdf.annotationManager.selectAnnotation(viewerAnnotation);
|
||||
this._reusablePdf.annotationManager.redrawAnnotation(viewerAnnotation);
|
||||
this._reusablePdf.annotationManager.selectAnnotation(viewerAnnotation);
|
||||
}
|
||||
|
||||
async acceptResize($event: MouseEvent, annotation: AnnotationWrapper): Promise<void> {
|
||||
@ -435,9 +437,9 @@ export class AnnotationActionsService {
|
||||
|
||||
annotationWrapper.resizing = false;
|
||||
|
||||
this._pdf.deleteAnnotations([annotationWrapper.id]);
|
||||
this._reusablePdf.deleteAnnotations([annotationWrapper.id]);
|
||||
await this._annotationDrawService.draw([annotationWrapper]);
|
||||
this._pdf.annotationManager.deselectAllAnnotations();
|
||||
this._reusablePdf.deselectAnnotations();
|
||||
await this._fileDataService.annotationsChanged();
|
||||
}
|
||||
|
||||
@ -461,8 +463,8 @@ export class AnnotationActionsService {
|
||||
}
|
||||
|
||||
#generateRectangle(annotationWrapper: AnnotationWrapper) {
|
||||
const annotation = new this._pdf.Annotations.RectangleAnnotation();
|
||||
const pageHeight = this._pdf.documentViewer.getPageHeight(annotationWrapper.pageNumber);
|
||||
const annotation = this._reusablePdf.rectangle();
|
||||
const pageHeight = this._reusablePdf.documentViewer.getPageHeight(annotationWrapper.pageNumber);
|
||||
const rectangle: IRectangle = annotationWrapper.positions[0];
|
||||
annotation.PageNumber = annotationWrapper.pageNumber;
|
||||
annotation.X = rectangle.topLeft.x;
|
||||
@ -527,17 +529,17 @@ export class AnnotationActionsService {
|
||||
}
|
||||
|
||||
private async _extractTextAndPositions(annotationId: string) {
|
||||
const viewerAnnotation = this._pdf.annotationManager.getAnnotationById(annotationId);
|
||||
const viewerAnnotation = this._reusablePdf.annotationManager.getAnnotationById(annotationId);
|
||||
|
||||
const document = await this._pdf.documentViewer.getDocument().getPDFDoc();
|
||||
const document = await this._reusablePdf.PDFDoc;
|
||||
const page = await document.getPage(viewerAnnotation.getPageNumber());
|
||||
if (viewerAnnotation instanceof this._pdf.Annotations.TextHighlightAnnotation) {
|
||||
if (this._reusablePdf.isTextHighlight(viewerAnnotation)) {
|
||||
const words = [];
|
||||
const rectangles: IRectangle[] = [];
|
||||
for (const quad of viewerAnnotation.Quads) {
|
||||
const rect = toPosition(
|
||||
viewerAnnotation.getPageNumber(),
|
||||
this._pdf.documentViewer.getPageHeight(viewerAnnotation.getPageNumber()),
|
||||
this._reusablePdf.getPageHeight(viewerAnnotation.getPageNumber()),
|
||||
this._translateQuads(viewerAnnotation.getPageNumber(), quad),
|
||||
);
|
||||
rectangles.push(rect);
|
||||
@ -548,7 +550,7 @@ export class AnnotationActionsService {
|
||||
*/
|
||||
const percentHeightOffset = rect.height / 10;
|
||||
|
||||
const pdfNetRect = new this._pdf.instance.Core.PDFNet.Rect(
|
||||
const pdfNetRect = new this._reusablePdf.PDFNet.Rect(
|
||||
rect.topLeft.x,
|
||||
rect.topLeft.y + percentHeightOffset,
|
||||
rect.topLeft.x + rect.width,
|
||||
@ -565,7 +567,7 @@ export class AnnotationActionsService {
|
||||
} else {
|
||||
const rect = toPosition(
|
||||
viewerAnnotation.getPageNumber(),
|
||||
this._pdf.documentViewer.getPageHeight(viewerAnnotation.getPageNumber()),
|
||||
this._reusablePdf.documentViewer.getPageHeight(viewerAnnotation.getPageNumber()),
|
||||
this._annotationDrawService.annotationToQuads(viewerAnnotation),
|
||||
);
|
||||
return {
|
||||
@ -576,13 +578,13 @@ export class AnnotationActionsService {
|
||||
}
|
||||
|
||||
private _translateQuads(page: number, quad: Quad): Quad {
|
||||
const rotation = this._pdf.documentViewer.getCompleteRotation(page);
|
||||
const rotation = this._reusablePdf.documentViewer.getCompleteRotation(page);
|
||||
return translateQuads(page, rotation, quad);
|
||||
}
|
||||
|
||||
private async _extractTextFromRect(page: Core.PDFNet.Page, rect: Core.PDFNet.Rect) {
|
||||
const txt = await this._pdf.PDFNet.TextExtractor.create();
|
||||
txt.begin(page, rect); // Read the page.
|
||||
const txt = await this._reusablePdf.PDFNet.TextExtractor.create();
|
||||
await txt.begin(page, rect); // Read the page.
|
||||
|
||||
const words: string[] = [];
|
||||
// Extract words one by one.
|
||||
|
||||
@ -15,6 +15,7 @@ import { FilePreviewStateService } from './file-preview-state.service';
|
||||
import { ViewModeService } from './view-mode.service';
|
||||
import { FileDataService } from './file-data.service';
|
||||
import { SuperTypes } from '@models/file/super-types';
|
||||
import { ReusablePdfViewer } from '../../shared/components/reusable-pdf-viewer/reusable-pdf-viewer.service';
|
||||
import Annotation = Core.Annotations.Annotation;
|
||||
import Quad = Core.Math.Quad;
|
||||
|
||||
@ -29,6 +30,7 @@ export class AnnotationDrawService {
|
||||
private readonly _userPreferenceService: UserPreferenceService,
|
||||
private readonly _skippedService: SkippedService,
|
||||
private readonly _pdf: PdfViewer,
|
||||
private readonly _reusablePdf: ReusablePdfViewer,
|
||||
private readonly _state: FilePreviewStateService,
|
||||
private readonly _viewModeService: ViewModeService,
|
||||
private readonly _fileDataService: FileDataService,
|
||||
@ -36,7 +38,7 @@ export class AnnotationDrawService {
|
||||
|
||||
draw(annotations: readonly AnnotationWrapper[]) {
|
||||
const licenseKey = environment.licenseKey ? atob(environment.licenseKey) : null;
|
||||
return this._pdf.PDFNet.runWithCleanup(() => this._draw(annotations), licenseKey);
|
||||
return this._reusablePdf.PDFNet.runWithCleanup(() => this._draw(annotations), licenseKey);
|
||||
}
|
||||
|
||||
getColor(superType: string, dictionary?: string) {
|
||||
@ -64,8 +66,7 @@ export class AnnotationDrawService {
|
||||
}
|
||||
|
||||
convertColor(hexColor: string) {
|
||||
const rgbColor = hexToRgb(hexColor);
|
||||
return new this._pdf.Annotations.Color(rgbColor.r, rgbColor.g, rgbColor.b);
|
||||
return this._reusablePdf.color(hexToRgb(hexColor));
|
||||
}
|
||||
|
||||
annotationToQuads(annotation: Annotation) {
|
||||
@ -81,13 +82,13 @@ export class AnnotationDrawService {
|
||||
const x4 = annotation.getRect().x1;
|
||||
const y4 = annotation.getRect().y1;
|
||||
|
||||
return new this._pdf.instance.Core.Math.Quad(x1, y1, x2, y2, x3, y3, x4, y4);
|
||||
return this._reusablePdf.quad(x1, y1, x2, y2, x3, y3, x4, y4);
|
||||
}
|
||||
|
||||
private async _draw(annotationWrappers: readonly AnnotationWrapper[]) {
|
||||
const annotations = annotationWrappers.map(annotation => this._computeAnnotation(annotation)).filter(a => !!a);
|
||||
this._pdf.annotationManager.addAnnotations(annotations, { imported: true });
|
||||
await this._pdf.annotationManager.drawAnnotationsFromList(annotations);
|
||||
this._reusablePdf.annotationManager.addAnnotations(annotations, { imported: true });
|
||||
await this._reusablePdf.annotationManager.drawAnnotationsFromList(annotations);
|
||||
|
||||
if (this._userPreferenceService.areDevFeaturesEnabled) {
|
||||
const { dossierId, fileId } = this._state;
|
||||
@ -108,14 +109,13 @@ export class AnnotationDrawService {
|
||||
// })
|
||||
});
|
||||
}
|
||||
const annotationManager = this._pdf.annotationManager;
|
||||
annotationManager.addAnnotations(sections, { imported: true });
|
||||
await annotationManager.drawAnnotationsFromList(sections);
|
||||
this._reusablePdf.annotationManager.addAnnotations(sections, { imported: true });
|
||||
await this._reusablePdf.annotationManager.drawAnnotationsFromList(sections);
|
||||
}
|
||||
|
||||
private _computeSection(pageNumber: number, sectionRectangle: ISectionRectangle) {
|
||||
const rectangleAnnot = new this._pdf.Annotations.RectangleAnnotation();
|
||||
const pageHeight = this._pdf.documentViewer.getPageHeight(pageNumber);
|
||||
const rectangleAnnot = this._reusablePdf.rectangle();
|
||||
const pageHeight = this._reusablePdf.getPageHeight(pageNumber);
|
||||
const rectangle: IRectangle = {
|
||||
topLeft: sectionRectangle.topLeft,
|
||||
page: pageNumber,
|
||||
@ -136,14 +136,14 @@ export class AnnotationDrawService {
|
||||
|
||||
private _computeAnnotation(annotationWrapper: AnnotationWrapper) {
|
||||
const pageNumber = this._viewModeService.isCompare ? annotationWrapper.pageNumber * 2 - 1 : annotationWrapper.pageNumber;
|
||||
if (pageNumber > this._pdf.pageCount) {
|
||||
if (pageNumber > this._reusablePdf.pageCount) {
|
||||
// skip imported annotations from files that have more pages than the current one
|
||||
return;
|
||||
}
|
||||
|
||||
if (annotationWrapper.superType === SuperTypes.TextHighlight) {
|
||||
const rectangleAnnot = new this._pdf.Annotations.RectangleAnnotation();
|
||||
const pageHeight = this._pdf.documentViewer.getPageHeight(pageNumber);
|
||||
const rectangleAnnot = this._reusablePdf.rectangle();
|
||||
const pageHeight = this._reusablePdf.getPageHeight(pageNumber);
|
||||
const rectangle: IRectangle = annotationWrapper.positions[0];
|
||||
rectangleAnnot.PageNumber = pageNumber;
|
||||
rectangleAnnot.X = rectangle.topLeft.x;
|
||||
@ -158,7 +158,7 @@ export class AnnotationDrawService {
|
||||
return rectangleAnnot;
|
||||
}
|
||||
|
||||
const annotation = new this._pdf.Annotations.TextHighlightAnnotation();
|
||||
const annotation = this._reusablePdf.textHighlight();
|
||||
annotation.Quads = this._rectanglesToQuads(annotationWrapper.positions, pageNumber);
|
||||
annotation.Opacity = annotationWrapper.isChangeLogRemoved ? DEFAULT_REMOVED_ANNOTATION_OPACITY : DEFAULT_TEXT_ANNOTATION_OPACITY;
|
||||
annotation.setContents(annotationWrapper.content);
|
||||
@ -185,7 +185,7 @@ export class AnnotationDrawService {
|
||||
}
|
||||
|
||||
private _rectanglesToQuads(positions: IRectangle[], pageNumber: number): Quad[] {
|
||||
const pageHeight = this._pdf.documentViewer.getPageHeight(pageNumber);
|
||||
const pageHeight = this._reusablePdf.getPageHeight(pageNumber);
|
||||
return positions.map(p => this._rectangleToQuad(p, pageHeight));
|
||||
}
|
||||
|
||||
@ -202,6 +202,6 @@ export class AnnotationDrawService {
|
||||
const x4 = rectangle.topLeft.x;
|
||||
const y4 = pageHeight - rectangle.topLeft.y;
|
||||
|
||||
return new this._pdf.instance.Core.Math.Quad(x1, y1, x2, y2, x3, y3, x4, y4);
|
||||
return this._reusablePdf.quad(x1, y1, x2, y2, x3, y3, x4, y4);
|
||||
}
|
||||
}
|
||||
|
||||
@ -71,14 +71,16 @@ export class FilePreviewStateService {
|
||||
}
|
||||
|
||||
get #blob$() {
|
||||
const reloadBlob$ = this.#reloadBlob$.pipe(
|
||||
withLatestFrom(this.file$),
|
||||
map(([, file]) => file),
|
||||
);
|
||||
return merge(this.file$, reloadBlob$).pipe(
|
||||
const file$ = this.file$.pipe(
|
||||
startWith(undefined),
|
||||
pairwise(),
|
||||
filter(([oldFile, newFile]) => oldFile?.cacheIdentifier !== newFile.cacheIdentifier),
|
||||
);
|
||||
const reloadBlob$ = this.#reloadBlob$.pipe(
|
||||
withLatestFrom(file$),
|
||||
map(([, file]) => file),
|
||||
);
|
||||
return merge(file$, reloadBlob$).pipe(
|
||||
switchMap(([oldFile, newFile]) => this.#downloadOriginalFile(newFile.cacheIdentifier, !!oldFile?.cacheIdentifier)),
|
||||
);
|
||||
}
|
||||
|
||||
@ -17,6 +17,7 @@ import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
||||
import { MatDialog } from '@angular/material/dialog';
|
||||
import { ViewerHeaderConfigService } from './viewer-header-config.service';
|
||||
import { FilesService } from '@services/files/files.service';
|
||||
import { ReusablePdfViewer } from '../../shared/components/reusable-pdf-viewer/reusable-pdf-viewer.service';
|
||||
|
||||
const ACTION_BUTTONS = [HeaderElements.APPLY_ROTATION, HeaderElements.DISCARD_ROTATION];
|
||||
const ONE_ROTATION_DEGREE = 90;
|
||||
@ -27,6 +28,7 @@ export class PageRotationService {
|
||||
|
||||
constructor(
|
||||
private readonly _pdf: PdfViewer,
|
||||
private readonly _reusablePdf: ReusablePdfViewer,
|
||||
private readonly _dialog: MatDialog,
|
||||
private readonly _loadingService: LoadingService,
|
||||
private readonly _screenState: FilePreviewStateService,
|
||||
@ -68,7 +70,7 @@ export class PageRotationService {
|
||||
for (const page of Object.keys(rotations)) {
|
||||
const times = rotations[page] / ONE_ROTATION_DEGREE;
|
||||
for (let i = 1; i <= times; i++) {
|
||||
this._pdf?.documentViewer?.rotateCounterClockwise(Number(page));
|
||||
this._reusablePdf.documentViewer.rotateCounterClockwise(Number(page));
|
||||
}
|
||||
}
|
||||
|
||||
@ -83,9 +85,9 @@ export class PageRotationService {
|
||||
this.#rotations$.next({ ...this.#rotations$.value, [pageNumber]: rotationValue });
|
||||
|
||||
if (rotation === RotationTypes.LEFT) {
|
||||
this._pdf.documentViewer.rotateCounterClockwise(pageNumber);
|
||||
this._reusablePdf.documentViewer.rotateCounterClockwise(pageNumber);
|
||||
} else {
|
||||
this._pdf.documentViewer.rotateClockwise(pageNumber);
|
||||
this._reusablePdf.documentViewer.rotateClockwise(pageNumber);
|
||||
}
|
||||
|
||||
if (this.hasRotations()) {
|
||||
|
||||
@ -1,69 +1,21 @@
|
||||
import { translateQuads } from '../../../utils';
|
||||
import { AnnotationWrapper } from '@models/file/annotation.wrapper';
|
||||
import WebViewer, { Core, WebViewerInstance } from '@pdftron/webviewer';
|
||||
import { Core } from '@pdftron/webviewer';
|
||||
import { ViewModeService } from './view-mode.service';
|
||||
import { File } from '@red/domain';
|
||||
import { Inject, Injectable } from '@angular/core';
|
||||
import { BASE_HREF } from '../../../tokens';
|
||||
import { environment } from '@environments/environment';
|
||||
import { DISABLED_HOTKEYS } from '../utils/constants';
|
||||
import { Observable, Subject } from 'rxjs';
|
||||
import { NGXLogger } from 'ngx-logger';
|
||||
import { map, tap } from 'rxjs/operators';
|
||||
import { ListingService, shareDistinctLast, shareLast } from '@iqser/common-ui';
|
||||
import { Injectable } from '@angular/core';
|
||||
import { ListingService } from '@iqser/common-ui';
|
||||
import { MultiSelectService } from './multi-select.service';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import Annotation = Core.Annotations.Annotation;
|
||||
import { ReusablePdfViewer } from '../../shared/components/reusable-pdf-viewer/reusable-pdf-viewer.service';
|
||||
|
||||
@Injectable()
|
||||
export class PdfViewer {
|
||||
instance?: WebViewerInstance;
|
||||
|
||||
readonly documentLoaded$: Observable<boolean>;
|
||||
readonly currentPage$ = this._activatedRoute.queryParamMap.pipe(
|
||||
map(params => Number(params.get('page') ?? '1')),
|
||||
shareDistinctLast(),
|
||||
);
|
||||
readonly #documentLoaded$ = new Subject<boolean>();
|
||||
|
||||
constructor(
|
||||
@Inject(BASE_HREF) private readonly _baseHref: string,
|
||||
private readonly _viewModeService: ViewModeService,
|
||||
private readonly _activatedRoute: ActivatedRoute,
|
||||
private readonly _multiSelectService: MultiSelectService,
|
||||
private readonly _reusablePdf: ReusablePdfViewer,
|
||||
private readonly _listingService: ListingService<AnnotationWrapper>,
|
||||
private readonly _logger: NGXLogger,
|
||||
) {
|
||||
this.documentLoaded$ = this.#documentLoaded$.asObservable().pipe(
|
||||
tap(() => this._logger.debug('[PDF] Loaded')),
|
||||
tap(() => this.#setCurrentPage()),
|
||||
shareLast(),
|
||||
);
|
||||
}
|
||||
|
||||
get documentViewer() {
|
||||
return this.instance?.Core.documentViewer;
|
||||
}
|
||||
|
||||
get annotationManager() {
|
||||
return this.instance?.Core.annotationManager;
|
||||
}
|
||||
|
||||
get UI() {
|
||||
return this.instance.UI;
|
||||
}
|
||||
|
||||
get Annotations() {
|
||||
return this.instance.Core.Annotations;
|
||||
}
|
||||
|
||||
get PDFNet(): typeof Core.PDFNet {
|
||||
return this.instance.Core.PDFNet;
|
||||
}
|
||||
|
||||
get hasAnnotations() {
|
||||
return this.annotationManager?.getAnnotationsList().length > 0 ?? false;
|
||||
}
|
||||
) {}
|
||||
|
||||
get paginationOffset() {
|
||||
return this._viewModeService.isCompare ? 2 : 1;
|
||||
@ -74,72 +26,16 @@ export class PdfViewer {
|
||||
}
|
||||
|
||||
get totalPages() {
|
||||
return this._viewModeService.isCompare ? Math.ceil(this.pageCount / 2) : this.pageCount;
|
||||
}
|
||||
|
||||
get pageCount() {
|
||||
try {
|
||||
return this.instance?.Core.documentViewer?.getPageCount() ?? 1;
|
||||
} catch {
|
||||
// might throw Error: getPageCount was called before the 'documentLoaded' event
|
||||
return 1;
|
||||
}
|
||||
const pageCount = this._reusablePdf.pageCount;
|
||||
return this._viewModeService.isCompare ? Math.ceil(pageCount / 2) : pageCount;
|
||||
}
|
||||
|
||||
private get _currentInternalPage() {
|
||||
return this.instance?.Core.documentViewer?.getCurrentPage() ?? 1;
|
||||
}
|
||||
|
||||
async lockDocument() {
|
||||
const document = await this.documentViewer.getDocument()?.getPDFDoc();
|
||||
if (!document) {
|
||||
return false;
|
||||
}
|
||||
|
||||
await document.lock();
|
||||
this._logger.debug('[PDF] Locked');
|
||||
return true;
|
||||
}
|
||||
|
||||
hideAnnotations(annotations: Annotation[]): void {
|
||||
this.annotationManager.hideAnnotations(annotations);
|
||||
}
|
||||
|
||||
showAnnotations(annotations: Annotation[]): void {
|
||||
this.annotationManager.showAnnotations(annotations);
|
||||
}
|
||||
|
||||
getAnnotations(predicate?: (value) => boolean) {
|
||||
const annotations = this.annotationManager?.getAnnotationsList() ?? [];
|
||||
return predicate ? annotations.filter(predicate) : annotations;
|
||||
}
|
||||
|
||||
getAnnotationsById(ids: readonly string[]) {
|
||||
if (this.annotationManager) {
|
||||
return ids.map(id => this.annotationManager.getAnnotationById(id)).filter(a => !!a);
|
||||
}
|
||||
|
||||
return [];
|
||||
return this._reusablePdf.documentViewer?.getCurrentPage() ?? 1;
|
||||
}
|
||||
|
||||
emitDocumentLoaded() {
|
||||
this.deleteAnnotations();
|
||||
this.#documentLoaded$.next(true);
|
||||
}
|
||||
|
||||
async loadViewer(htmlElement: HTMLElement) {
|
||||
this.instance = await WebViewer(
|
||||
{
|
||||
licenseKey: environment.licenseKey ? atob(environment.licenseKey) : null,
|
||||
fullAPI: true,
|
||||
path: this.#convertPath('/assets/wv-resources'),
|
||||
css: this.#convertPath('/assets/pdftron/stylesheet.css'),
|
||||
backendType: 'ems',
|
||||
},
|
||||
htmlElement,
|
||||
);
|
||||
|
||||
return this.instance;
|
||||
this._reusablePdf.deleteAnnotations();
|
||||
}
|
||||
|
||||
isCurrentPageExcluded(file: File) {
|
||||
@ -159,55 +55,26 @@ export class PdfViewer {
|
||||
}
|
||||
|
||||
navigateNextPage() {
|
||||
if (this._currentInternalPage < this.pageCount) {
|
||||
this._navigateToPage(Math.min(this._currentInternalPage + this.paginationOffset, this.pageCount));
|
||||
const pageCount = this._reusablePdf.pageCount;
|
||||
if (this._currentInternalPage < pageCount) {
|
||||
this._navigateToPage(Math.min(this._currentInternalPage + this.paginationOffset, pageCount));
|
||||
}
|
||||
}
|
||||
|
||||
disableHotkeys(): void {
|
||||
DISABLED_HOTKEYS.forEach(key => this.instance.UI.hotkeys.off(key));
|
||||
}
|
||||
|
||||
translateQuad(page: number, quad: Core.Math.Quad) {
|
||||
const rotation = this.documentViewer.getCompleteRotation(page);
|
||||
const rotation = this._reusablePdf.documentViewer.getCompleteRotation(page);
|
||||
return translateQuads(page, rotation, quad);
|
||||
}
|
||||
|
||||
deselectAllAnnotations() {
|
||||
this.annotationManager?.deselectAllAnnotations();
|
||||
}
|
||||
|
||||
selectAnnotations(annotations?: AnnotationWrapper[]) {
|
||||
if (!annotations) {
|
||||
return this.deselectAllAnnotations();
|
||||
return this._reusablePdf.deselectAnnotations();
|
||||
}
|
||||
|
||||
const annotationsToSelect = this._multiSelectService.isActive ? [...this._listingService.selected, ...annotations] : annotations;
|
||||
this.#selectAnnotations(annotationsToSelect);
|
||||
}
|
||||
|
||||
deleteAnnotations(annotationsIds?: readonly string[]) {
|
||||
let annotations: Annotation[];
|
||||
if (!annotationsIds) {
|
||||
annotations = this.getAnnotations();
|
||||
} else {
|
||||
annotations = this.getAnnotationsById(annotationsIds);
|
||||
}
|
||||
|
||||
try {
|
||||
this.annotationManager.deleteAnnotations(annotations, {
|
||||
force: true,
|
||||
});
|
||||
} catch (error) {
|
||||
console.log('Error while deleting annotations: ', error);
|
||||
}
|
||||
}
|
||||
|
||||
deselectAnnotations(annotations: AnnotationWrapper[]) {
|
||||
const ann = this.getAnnotationsById(annotations.map(a => a.id));
|
||||
this.annotationManager.deselectAnnotations(ann);
|
||||
}
|
||||
|
||||
#selectAnnotations(annotations: AnnotationWrapper[] = []) {
|
||||
const filteredAnnotationsIds = annotations.filter(a => !!a).map(a => a.id);
|
||||
|
||||
@ -216,7 +83,7 @@ export class PdfViewer {
|
||||
}
|
||||
|
||||
if (!this._multiSelectService.isActive) {
|
||||
this.deselectAllAnnotations();
|
||||
this._reusablePdf.deselectAnnotations();
|
||||
}
|
||||
|
||||
const pageNumber = annotations[0].pageNumber;
|
||||
@ -231,24 +98,15 @@ export class PdfViewer {
|
||||
}
|
||||
|
||||
#jumpAndSelectAnnotations(annotationIds: readonly string[]) {
|
||||
const annotationsFromViewer = this.getAnnotationsById(annotationIds);
|
||||
const annotationsFromViewer = this._reusablePdf.getAnnotationsById(annotationIds);
|
||||
|
||||
this.annotationManager.jumpToAnnotation(annotationsFromViewer[0]);
|
||||
this.annotationManager.selectAnnotations(annotationsFromViewer);
|
||||
this._reusablePdf.annotationManager.jumpToAnnotation(annotationsFromViewer[0]);
|
||||
this._reusablePdf.annotationManager.selectAnnotations(annotationsFromViewer);
|
||||
}
|
||||
|
||||
private _navigateToPage(pageNumber: number) {
|
||||
if (this._currentInternalPage !== pageNumber) {
|
||||
this.documentViewer.displayPageLocation(pageNumber, 0, 0);
|
||||
this._reusablePdf.documentViewer.displayPageLocation(pageNumber, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
#convertPath(path: string) {
|
||||
return `${this._baseHref}${path}`;
|
||||
}
|
||||
|
||||
#setCurrentPage() {
|
||||
const currentDocPage = this._activatedRoute.snapshot.queryParamMap.get('page');
|
||||
this.documentViewer.setCurrentPage(Number(currentDocPage ?? '1'));
|
||||
}
|
||||
}
|
||||
|
||||
@ -2,14 +2,14 @@ import { Injectable } from '@angular/core';
|
||||
import { BehaviorSubject, Observable } from 'rxjs';
|
||||
import { skip, tap } from 'rxjs/operators';
|
||||
import { shareDistinctLast } from '@iqser/common-ui';
|
||||
import { PdfViewer } from './pdf-viewer.service';
|
||||
import { ReusablePdfViewer } from '../../shared/components/reusable-pdf-viewer/reusable-pdf-viewer.service';
|
||||
|
||||
@Injectable()
|
||||
export class SkippedService {
|
||||
readonly hideSkipped$: Observable<boolean>;
|
||||
readonly #hideSkipped$ = new BehaviorSubject(false);
|
||||
|
||||
constructor(private readonly _pdf: PdfViewer) {
|
||||
constructor(private readonly _pdf: ReusablePdfViewer) {
|
||||
this.hideSkipped$ = this.#hideSkipped$.pipe(
|
||||
tap(hideSkipped => this._handleIgnoreAnnotationsDrawing(hideSkipped)),
|
||||
shareDistinctLast(),
|
||||
@ -28,7 +28,7 @@ export class SkippedService {
|
||||
}
|
||||
|
||||
private _handleIgnoreAnnotationsDrawing(hideSkipped: boolean): void {
|
||||
const ignored = this._pdf.getAnnotations(a => a.getCustomData('skipped'));
|
||||
const ignored = this._pdf.getAnnotations(a => Boolean(a.getCustomData('skipped')));
|
||||
if (hideSkipped) {
|
||||
this._pdf.hideAnnotations(ignored);
|
||||
} else {
|
||||
|
||||
@ -8,12 +8,14 @@ import { TranslateService } from '@ngx-translate/core';
|
||||
import { Core } from '@pdftron/webviewer';
|
||||
import { firstValueFrom } from 'rxjs';
|
||||
import { WatermarkService } from '@services/entity-services/watermark.service';
|
||||
import { ReusablePdfViewer } from '../../shared/components/reusable-pdf-viewer/reusable-pdf-viewer.service';
|
||||
import PDFNet = Core.PDFNet;
|
||||
|
||||
@Injectable()
|
||||
export class StampService {
|
||||
constructor(
|
||||
private readonly _pdf: PdfViewer,
|
||||
private readonly _reusablePdf: ReusablePdfViewer,
|
||||
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.documentViewer.getDocument()?.getPDFDoc();
|
||||
const pdfDoc = await this._reusablePdf.PDFDoc;
|
||||
if (!pdfDoc) {
|
||||
return;
|
||||
}
|
||||
@ -31,9 +33,9 @@ export class StampService {
|
||||
const allPages = [...Array(file.numberOfPages).keys()].map(page => page + 1);
|
||||
|
||||
try {
|
||||
await clearStamps(pdfDoc, this._pdf.PDFNet, allPages);
|
||||
await clearStamps(pdfDoc, this._reusablePdf.PDFNet, allPages);
|
||||
} catch (e) {
|
||||
this._logger.error('Error clearing stamps: ', e);
|
||||
console.error('Error clearing stamps: ', e);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -46,15 +48,15 @@ export class StampService {
|
||||
await this._stampExcludedPages(pdfDoc, file.excludedPages);
|
||||
}
|
||||
|
||||
this._pdf.documentViewer.refreshAll();
|
||||
this._pdf.documentViewer.updateView([this._pdf.currentPage], this._pdf.currentPage);
|
||||
this._reusablePdf.documentViewer.refreshAll();
|
||||
this._reusablePdf.documentViewer.updateView([this._pdf.currentPage], this._pdf.currentPage);
|
||||
}
|
||||
|
||||
private async _stampExcludedPages(document: PDFNet.PDFDoc, excludedPages: number[]): Promise<void> {
|
||||
if (excludedPages && excludedPages.length > 0) {
|
||||
await stampPDFPage(
|
||||
document,
|
||||
this._pdf.PDFNet,
|
||||
this._reusablePdf.PDFNet,
|
||||
this._translateService.instant('file-preview.excluded-from-redaction') as string,
|
||||
17,
|
||||
'courier',
|
||||
@ -70,7 +72,7 @@ export class StampService {
|
||||
const watermark = await firstValueFrom(this._watermarkService.getWatermark(dossierTemplateId));
|
||||
await stampPDFPage(
|
||||
document,
|
||||
this._pdf.PDFNet,
|
||||
this._reusablePdf.PDFNet,
|
||||
watermark.text,
|
||||
watermark.fontSize,
|
||||
watermark.fontType,
|
||||
|
||||
@ -1,16 +1,16 @@
|
||||
import { Inject, Injectable } from '@angular/core';
|
||||
import { PdfViewer } from './pdf-viewer.service';
|
||||
import { UserPreferenceService } from '@services/user-preference.service';
|
||||
import { HeaderElements } from '../utils/constants';
|
||||
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { BASE_HREF } from '../../../tokens';
|
||||
import { ReusablePdfViewer } from '../../shared/components/reusable-pdf-viewer/reusable-pdf-viewer.service';
|
||||
|
||||
@Injectable()
|
||||
export class TooltipsService {
|
||||
constructor(
|
||||
@Inject(BASE_HREF) private readonly _baseHref: string,
|
||||
private readonly _pdfViewer: PdfViewer,
|
||||
private readonly _pdfViewer: ReusablePdfViewer,
|
||||
private readonly _userPreferenceService: UserPreferenceService,
|
||||
private readonly _translateService: TranslateService,
|
||||
) {}
|
||||
|
||||
@ -9,6 +9,7 @@ import { environment } from '@environments/environment';
|
||||
import { ViewModeService } from './view-mode.service';
|
||||
import { FilePreviewStateService } from './file-preview-state.service';
|
||||
import { PageRotationService } from './page-rotation.service';
|
||||
import { ReusablePdfViewer } from '../../shared/components/reusable-pdf-viewer/reusable-pdf-viewer.service';
|
||||
|
||||
@Injectable()
|
||||
export class ViewerHeaderConfigService {
|
||||
@ -33,6 +34,7 @@ export class ViewerHeaderConfigService {
|
||||
private readonly _injector: Injector,
|
||||
private readonly _translateService: TranslateService,
|
||||
private readonly _pdfViewer: PdfViewer,
|
||||
private readonly _reusablePdf: ReusablePdfViewer,
|
||||
private readonly _tooltipsService: TooltipsService,
|
||||
private readonly _viewModeService: ViewModeService,
|
||||
private readonly _stateService: FilePreviewStateService,
|
||||
@ -163,13 +165,13 @@ export class ViewerHeaderConfigService {
|
||||
|
||||
private async _closeCompareMode() {
|
||||
this._viewModeService.compareMode = false;
|
||||
const pdfNet = this._pdfViewer.instance.Core.PDFNet;
|
||||
const pdfNet = this._reusablePdf.PDFNet;
|
||||
await pdfNet.initialize(environment.licenseKey ? atob(environment.licenseKey) : null);
|
||||
const blob = await this._stateService.blob;
|
||||
const currentDocument = await pdfNet.PDFDoc.createFromBuffer(await blob.arrayBuffer());
|
||||
|
||||
const filename = this._stateService.file.filename ?? 'document.pdf';
|
||||
this._pdfViewer.instance.UI.loadDocument(currentDocument, { filename });
|
||||
this._reusablePdf.instance.UI.loadDocument(currentDocument, { filename });
|
||||
|
||||
this.disable([HeaderElements.CLOSE_COMPARE_BUTTON]);
|
||||
this.enable([HeaderElements.COMPARE_BUTTON]);
|
||||
@ -197,7 +199,7 @@ export class ViewerHeaderConfigService {
|
||||
}
|
||||
|
||||
private _updateElements(): void {
|
||||
this._pdfViewer.instance.UI.setHeaderItems(header => {
|
||||
this._reusablePdf.instance.UI.setHeaderItems(header => {
|
||||
const enabledItems: IHeaderElement[] = [];
|
||||
const groups: HeaderElementType[][] = [
|
||||
[HeaderElements.COMPARE_BUTTON, HeaderElements.CLOSE_COMPARE_BUTTON],
|
||||
|
||||
@ -42,36 +42,3 @@ export const TextPopups = {
|
||||
ADD_RECTANGLE: 'add-rectangle',
|
||||
ADD_FALSE_POSITIVE: 'add-false-positive',
|
||||
} as const;
|
||||
|
||||
export const DISABLED_HOTKEYS = [
|
||||
'CTRL+SHIFT+EQUAL',
|
||||
'COMMAND+SHIFT+EQUAL',
|
||||
'CTRL+SHIFT+MINUS',
|
||||
'COMMAND+SHIFT+MINUS',
|
||||
'CTRL+V',
|
||||
'COMMAND+V',
|
||||
'CTRL+Y',
|
||||
'COMMAND+Y',
|
||||
'CTRL+O',
|
||||
'COMMAND+O',
|
||||
'CTRL+P',
|
||||
'COMMAND+P',
|
||||
'SPACE',
|
||||
'UP',
|
||||
'DOWN',
|
||||
'R',
|
||||
'P',
|
||||
'A',
|
||||
'C',
|
||||
'E',
|
||||
'I',
|
||||
'L',
|
||||
'N',
|
||||
'O',
|
||||
'T',
|
||||
'S',
|
||||
'G',
|
||||
'H',
|
||||
'K',
|
||||
'U',
|
||||
] as const;
|
||||
|
||||
@ -0,0 +1,60 @@
|
||||
import { CustomError } from '@iqser/common-ui';
|
||||
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
||||
|
||||
export const DOCUMENT_LOADING_ERROR = new CustomError(_('error.file-preview.label'), _('error.file-preview.action'), 'iqser:refresh');
|
||||
|
||||
export const USELESS_ELEMENTS = [
|
||||
'pageNavOverlay',
|
||||
'menuButton',
|
||||
'selectToolButton',
|
||||
'textHighlightToolButton',
|
||||
'textUnderlineToolButton',
|
||||
'textSquigglyToolButton',
|
||||
'textStrikeoutToolButton',
|
||||
'viewControlsButton',
|
||||
'contextMenuPopup',
|
||||
'linkButton',
|
||||
'toggleNotesButton',
|
||||
'notesPanel',
|
||||
'thumbnailControl',
|
||||
'documentControl',
|
||||
'ribbons',
|
||||
'toolsHeader',
|
||||
'rotateClockwiseButton',
|
||||
'rotateCounterClockwiseButton',
|
||||
'annotationStyleEditButton',
|
||||
'annotationGroupButton',
|
||||
];
|
||||
|
||||
export const DISABLED_HOTKEYS = [
|
||||
'CTRL+SHIFT+EQUAL',
|
||||
'COMMAND+SHIFT+EQUAL',
|
||||
'CTRL+SHIFT+MINUS',
|
||||
'COMMAND+SHIFT+MINUS',
|
||||
'CTRL+V',
|
||||
'COMMAND+V',
|
||||
'CTRL+Y',
|
||||
'COMMAND+Y',
|
||||
'CTRL+O',
|
||||
'COMMAND+O',
|
||||
'CTRL+P',
|
||||
'COMMAND+P',
|
||||
'SPACE',
|
||||
'UP',
|
||||
'DOWN',
|
||||
'R',
|
||||
'P',
|
||||
'A',
|
||||
'C',
|
||||
'E',
|
||||
'I',
|
||||
'L',
|
||||
'N',
|
||||
'O',
|
||||
'T',
|
||||
'S',
|
||||
'G',
|
||||
'H',
|
||||
'K',
|
||||
'U',
|
||||
] as const;
|
||||
@ -1,4 +1,4 @@
|
||||
import { Component, ElementRef, OnInit, ViewChild } from '@angular/core';
|
||||
import { Component, ElementRef, ViewChild } from '@angular/core';
|
||||
import { ReusablePdfViewer } from './reusable-pdf-viewer.service';
|
||||
|
||||
@Component({
|
||||
@ -16,12 +16,18 @@ import { ReusablePdfViewer } from './reusable-pdf-viewer.service';
|
||||
`,
|
||||
],
|
||||
})
|
||||
export class ReusablePdfViewerComponent implements OnInit {
|
||||
@ViewChild('viewer', { static: true }) readonly viewer: ElementRef;
|
||||
export class ReusablePdfViewerComponent {
|
||||
#viewer: ElementRef;
|
||||
|
||||
constructor(readonly reusablePdfViewer: ReusablePdfViewer) {}
|
||||
|
||||
ngOnInit() {
|
||||
return this.reusablePdfViewer.init(this.viewer.nativeElement);
|
||||
@ViewChild('viewer', { static: true })
|
||||
set viewer(value: ElementRef) {
|
||||
if (this.#viewer) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.#viewer = value;
|
||||
this.reusablePdfViewer.init(value.nativeElement as HTMLElement).then();
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,47 +1,28 @@
|
||||
import { Inject, Injectable, Injector } from '@angular/core';
|
||||
import WebViewer, { Core, WebViewerInstance, WebViewerOptions } from '@pdftron/webviewer';
|
||||
import { environment } from '../../../../../environments/environment';
|
||||
import { environment } from '@environments/environment';
|
||||
import { BASE_HREF_FN, BaseHrefFn } from '../../../../tokens';
|
||||
import { File } from '@red/domain';
|
||||
import { CustomError, ErrorService, LoadingService, log, shareDistinctLast, shareLast } from '@iqser/common-ui';
|
||||
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker/';
|
||||
import { ActivatedRoute, ActivationStart, Router } from '@angular/router';
|
||||
import { filter, map } from 'rxjs/operators';
|
||||
import { fromEvent, merge, Observable } from 'rxjs';
|
||||
import { ConfigService } from '../../../../services/config.service';
|
||||
import { ErrorService, List, shareDistinctLast, shareLast } from '@iqser/common-ui';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
import { debounceTime, map, tap } from 'rxjs/operators';
|
||||
import { fromEvent, merge, Observable, Subject } from 'rxjs';
|
||||
import { ConfigService } from '@services/config.service';
|
||||
import { NGXLogger } from 'ngx-logger';
|
||||
import { DISABLED_HOTKEYS, DOCUMENT_LOADING_ERROR, USELESS_ELEMENTS } from './constants';
|
||||
import { AnnotationWrapper } from '@models/file/annotation.wrapper';
|
||||
import { Rgb } from '@shared/components/reusable-pdf-viewer/types';
|
||||
import DocumentViewer = Core.DocumentViewer;
|
||||
import AnnotationManager = Core.AnnotationManager;
|
||||
import TextTool = Core.Tools.TextTool;
|
||||
|
||||
const DocLoadingError = new CustomError(_('error.file-preview.label'), _('error.file-preview.action'), 'iqser:refresh');
|
||||
const uselessElements = [
|
||||
'pageNavOverlay',
|
||||
'menuButton',
|
||||
'selectToolButton',
|
||||
'textHighlightToolButton',
|
||||
'textUnderlineToolButton',
|
||||
'textSquigglyToolButton',
|
||||
'textStrikeoutToolButton',
|
||||
'viewControlsButton',
|
||||
'contextMenuPopup',
|
||||
'linkButton',
|
||||
'toggleNotesButton',
|
||||
'notesPanel',
|
||||
'thumbnailControl',
|
||||
'documentControl',
|
||||
'ribbons',
|
||||
'toolsHeader',
|
||||
'rotateClockwiseButton',
|
||||
'rotateCounterClockwiseButton',
|
||||
'annotationStyleEditButton',
|
||||
'annotationGroupButton',
|
||||
];
|
||||
import Annotation = Core.Annotations.Annotation;
|
||||
import TextHighlightAnnotation = Core.Annotations.TextHighlightAnnotation;
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class ReusablePdfViewer {
|
||||
readonly currentPage$ = this._injector.get(ActivatedRoute).queryParamMap.pipe(
|
||||
readonly currentPage$ = this._activatedRoute.queryParamMap.pipe(
|
||||
map(params => Number(params.get('page') ?? '1')),
|
||||
shareDistinctLast(),
|
||||
);
|
||||
@ -50,51 +31,155 @@ export class ReusablePdfViewer {
|
||||
|
||||
show$: Observable<boolean>;
|
||||
documentLoaded$: Observable<boolean>;
|
||||
annotationSelected$: Observable<unknown>;
|
||||
pageComplete$: Observable<unknown>;
|
||||
|
||||
#instance: WebViewerInstance;
|
||||
#documentClosed$ = new Subject<boolean>();
|
||||
|
||||
constructor(
|
||||
@Inject(BASE_HREF_FN) private readonly _convertPath: BaseHrefFn,
|
||||
private readonly _loadingService: LoadingService,
|
||||
private readonly _errorService: ErrorService,
|
||||
private readonly _router: Router,
|
||||
private readonly _logger: NGXLogger,
|
||||
private readonly _configService: ConfigService,
|
||||
private readonly _activatedRoute: ActivatedRoute,
|
||||
private readonly _injector: Injector,
|
||||
) {}
|
||||
|
||||
get #documentLoaded$() {
|
||||
const docLoadedEvent = this.#instance.UI.Events.DOCUMENT_LOADED;
|
||||
const event$ = fromEvent(this.documentViewer, docLoadedEvent).pipe(map(() => true));
|
||||
|
||||
return event$.pipe(log('[PDF] Document loaded'), shareLast());
|
||||
get instance() {
|
||||
return this.#instance;
|
||||
}
|
||||
|
||||
get #show$() {
|
||||
const routeChanged = this._router.events.pipe(
|
||||
filter(event => event instanceof ActivationStart),
|
||||
map(() => false),
|
||||
);
|
||||
get PDFDoc() {
|
||||
return this.document?.getPDFDoc();
|
||||
}
|
||||
|
||||
return merge(routeChanged, this.documentLoaded$);
|
||||
get document() {
|
||||
return this.documentViewer.getDocument();
|
||||
}
|
||||
|
||||
get PDFNet(): typeof Core.PDFNet {
|
||||
return this.#instance.Core.PDFNet;
|
||||
}
|
||||
|
||||
get pageCount() {
|
||||
try {
|
||||
return this.#instance.Core.documentViewer.getPageCount();
|
||||
} catch {
|
||||
// might throw Error: getPageCount was called before the 'documentLoaded' event
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
get #pageComplete$() {
|
||||
return fromEvent(this.documentViewer, 'pageComplete').pipe(debounceTime(300));
|
||||
}
|
||||
|
||||
get #documentLoaded$() {
|
||||
const event$ = fromEvent(this.documentViewer, this.#instance.UI.Events.DOCUMENT_LOADED);
|
||||
const toBool$ = event$.pipe(map(() => true));
|
||||
const updateCurrentPage$ = toBool$.pipe(tap(() => this.#setCurrentPage()));
|
||||
|
||||
const log = tap<boolean>(() => this._logger.info('[PDF] Document loaded'));
|
||||
return updateCurrentPage$.pipe(log, shareLast());
|
||||
}
|
||||
|
||||
get #annotationSelected$() {
|
||||
const onSelect$ = fromEvent<[Annotation[], string]>(this.annotationManager, 'annotationSelected');
|
||||
return onSelect$.pipe(tap(value => console.log('Annotation selected: ', value)));
|
||||
}
|
||||
|
||||
async init(htmlElement: HTMLElement) {
|
||||
this.#instance = await this.#getInstance(htmlElement);
|
||||
console.log('[PDF] Initialized');
|
||||
this._logger.info('[PDF] Initialized');
|
||||
|
||||
this.documentViewer = this.#instance.Core.documentViewer;
|
||||
this.annotationManager = this.#instance.Core.annotationManager;
|
||||
|
||||
this.documentLoaded$ = this.#documentLoaded$;
|
||||
this.show$ = this.#show$;
|
||||
this.show$ = merge(this.#documentClosed$, this.documentLoaded$).pipe(shareLast());
|
||||
this.annotationSelected$ = this.#annotationSelected$;
|
||||
this.pageComplete$ = this.#pageComplete$;
|
||||
this.#setSelectionMode();
|
||||
this.#configureElements();
|
||||
this.#disableHotkeys();
|
||||
}
|
||||
|
||||
loadDocument(blob: Blob, file: File) {
|
||||
console.log('[PDF] Loading document', blob, file);
|
||||
deleteAnnotations(annotationsIds?: List) {
|
||||
let annotations: Annotation[];
|
||||
if (!annotationsIds) {
|
||||
annotations = this.getAnnotations();
|
||||
} else {
|
||||
annotations = this.getAnnotationsById(annotationsIds);
|
||||
}
|
||||
|
||||
try {
|
||||
this.annotationManager.deleteAnnotations(annotations, {
|
||||
force: true,
|
||||
});
|
||||
} catch (error) {
|
||||
console.log('Error while deleting annotations: ', error);
|
||||
}
|
||||
}
|
||||
|
||||
getAnnotations(predicate?: (value: Annotation) => boolean) {
|
||||
const annotations = this.annotationManager.getAnnotationsList();
|
||||
return predicate ? annotations.filter(predicate) : annotations;
|
||||
}
|
||||
|
||||
getAnnotationsById(ids: List) {
|
||||
return ids.map(id => this.annotationManager.getAnnotationById(id)).filter(a => !!a);
|
||||
}
|
||||
|
||||
deselectAnnotations(annotations?: List<AnnotationWrapper>) {
|
||||
if (!annotations) {
|
||||
return this.annotationManager.deselectAllAnnotations();
|
||||
}
|
||||
|
||||
const ann = this.getAnnotationsById(annotations.map(a => a.id));
|
||||
this.annotationManager.deselectAnnotations(ann);
|
||||
}
|
||||
|
||||
hideAnnotations(annotations: Annotation[]): void {
|
||||
this.annotationManager.hideAnnotations(annotations);
|
||||
}
|
||||
|
||||
showAnnotations(annotations: Annotation[]): void {
|
||||
this.annotationManager.showAnnotations(annotations);
|
||||
}
|
||||
|
||||
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.#documentClosed$.next(false);
|
||||
this.#instance.Core.documentViewer.closeDocument();
|
||||
}
|
||||
|
||||
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 = () => {
|
||||
this._loadingService.stop();
|
||||
this._errorService.set(DocLoadingError);
|
||||
this._errorService.set(DOCUMENT_LOADING_ERROR);
|
||||
this._logger.error('[PDF] Error while loading document');
|
||||
// this.stateService.reloadBlob();
|
||||
};
|
||||
// const pdfNet = this._instance.Core.PDFNet;
|
||||
@ -105,11 +190,40 @@ export class ReusablePdfViewer {
|
||||
// console.log('document initialized');
|
||||
// await document.flattenAnnotations(false);
|
||||
// console.log(document);
|
||||
this.#instance.UI.loadDocument(blob, { filename: file?.filename + '.pdf' ?? 'document.pdf', onError });
|
||||
const pdfNet = this.#instance.Core.PDFNet;
|
||||
|
||||
await pdfNet.initialize(environment.licenseKey ? atob(environment.licenseKey) : null);
|
||||
const document = await pdfNet.PDFDoc.createFromBuffer(await blob.arrayBuffer());
|
||||
await document.flattenAnnotations(false);
|
||||
this.#instance.UI.loadDocument(document, { filename: file?.filename + '.pdf' ?? 'document.pdf', onError });
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
color(rgb: Rgb) {
|
||||
return new this.#instance.Core.Annotations.Color(rgb.r, rgb.g, rgb.b);
|
||||
}
|
||||
|
||||
rectangle() {
|
||||
return new this.#instance.Core.Annotations.RectangleAnnotation();
|
||||
}
|
||||
|
||||
textHighlight() {
|
||||
return new this.#instance.Core.Annotations.TextHighlightAnnotation();
|
||||
}
|
||||
|
||||
isTextHighlight(annotation: Annotation): annotation is TextHighlightAnnotation {
|
||||
return annotation instanceof this.#instance.Core.Annotations.TextHighlightAnnotation;
|
||||
}
|
||||
|
||||
#disableHotkeys(): void {
|
||||
DISABLED_HOTKEYS.forEach(key => this.#instance.UI.hotkeys.off(key));
|
||||
}
|
||||
|
||||
#configureElements() {
|
||||
this.#instance.UI.disableElements(uselessElements);
|
||||
this.#instance.UI.disableElements(USELESS_ELEMENTS);
|
||||
}
|
||||
|
||||
#setSelectionMode(): void {
|
||||
@ -128,4 +242,9 @@ export class ReusablePdfViewer {
|
||||
|
||||
return WebViewer(options, htmlElement);
|
||||
}
|
||||
|
||||
#setCurrentPage() {
|
||||
const currentDocPage = this._activatedRoute.snapshot.queryParamMap.get('page');
|
||||
this.documentViewer.setCurrentPage(Number(currentDocPage ?? '1'));
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,5 @@
|
||||
export interface Rgb {
|
||||
readonly r: number;
|
||||
readonly g: number;
|
||||
readonly b: number;
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user