686 lines
28 KiB
TypeScript
686 lines
28 KiB
TypeScript
import { ChangeDetectorRef, Component, HostListener, NgZone, OnDestroy, OnInit, TemplateRef, ViewChild } from '@angular/core';
|
|
import { ActivatedRoute, ActivatedRouteSnapshot, NavigationExtras, Router } from '@angular/router';
|
|
import { Core } from '@pdftron/webviewer';
|
|
import {
|
|
AutoUnsubscribe,
|
|
CircleButtonTypes,
|
|
CustomError,
|
|
Debounce,
|
|
ErrorService,
|
|
FilterService,
|
|
LoadingService,
|
|
log,
|
|
NestedFilter,
|
|
OnAttach,
|
|
OnDetach,
|
|
processFilters,
|
|
shareDistinctLast,
|
|
} from '@iqser/common-ui';
|
|
import { MatDialogRef, MatDialogState } from '@angular/material/dialog';
|
|
import { ManualRedactionEntryWrapper } from '@models/file/manual-redaction-entry.wrapper';
|
|
import { AnnotationWrapper } from '@models/file/annotation.wrapper';
|
|
import { AnnotationDrawService } from './services/annotation-draw.service';
|
|
import { AnnotationProcessingService } from '../dossier/services/annotation-processing.service';
|
|
import { File, ViewMode } from '@red/domain';
|
|
import { PermissionsService } from '@services/permissions.service';
|
|
import { combineLatest, firstValueFrom, Observable, of, pairwise, timer } from 'rxjs';
|
|
import { UserPreferenceService } from '@services/user-preference.service';
|
|
import { clearStamps, download, handleFilterDelta, stampPDFPage } from '../../utils';
|
|
import { FileWorkloadComponent } from './components/file-workload/file-workload.component';
|
|
import { TranslateService } from '@ngx-translate/core';
|
|
import { FilesService } from '@services/entity-services/files.service';
|
|
import { FileManagementService } from '@services/entity-services/file-management.service';
|
|
import { catchError, debounceTime, map, startWith, switchMap, tap } from 'rxjs/operators';
|
|
import { FilesMapService } from '@services/entity-services/files-map.service';
|
|
import { WatermarkService } from '@shared/services/watermark.service';
|
|
import { ExcludedPagesService } from './services/excluded-pages.service';
|
|
import { ViewModeService } from './services/view-mode.service';
|
|
import { MultiSelectService } from './services/multi-select.service';
|
|
import { DocumentInfoService } from './services/document-info.service';
|
|
import { ReanalysisService } from '@services/reanalysis.service';
|
|
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
|
import { SkippedService } from './services/skipped.service';
|
|
import { FilePreviewStateService } from './services/file-preview-state.service';
|
|
import { filePreviewScreenProviders } from './file-preview-providers';
|
|
import { ManualAnnotationService } from '@services/manual-annotation.service';
|
|
import { DossiersService } from '@services/dossiers/dossiers.service';
|
|
import { PageRotationService } from './services/page-rotation.service';
|
|
import { ComponentCanDeactivate } from '../../guards/can-deactivate.guard';
|
|
import { PdfViewer } from './services/pdf-viewer.service';
|
|
import { FilePreviewDialogService } from './services/file-preview-dialog.service';
|
|
import { FileDataService } from './services/file-data.service';
|
|
import { ALL_HOTKEYS } from './shared/constants';
|
|
import { NGXLogger } from 'ngx-logger';
|
|
import Annotation = Core.Annotations.Annotation;
|
|
import PDFNet = Core.PDFNet;
|
|
|
|
@Component({
|
|
templateUrl: './file-preview-screen.component.html',
|
|
styleUrls: ['./file-preview-screen.component.scss'],
|
|
providers: filePreviewScreenProviders,
|
|
})
|
|
export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnInit, OnDestroy, OnAttach, OnDetach, ComponentCanDeactivate {
|
|
readonly circleButtonTypes = CircleButtonTypes;
|
|
|
|
dialogRef: MatDialogRef<unknown>;
|
|
fullScreen = false;
|
|
selectedAnnotations: AnnotationWrapper[] = [];
|
|
displayPdfViewer = false;
|
|
readonly canPerformAnnotationActions$: Observable<boolean>;
|
|
readonly fileId = this.stateService.fileId;
|
|
readonly dossierId = this.stateService.dossierId;
|
|
readonly file$ = this.stateService.file$.pipe(tap(() => this._fileDataService.loadAnnotations()));
|
|
ready = false;
|
|
private _lastPage: string;
|
|
|
|
@ViewChild('fileWorkloadComponent') private readonly _workloadComponent: FileWorkloadComponent;
|
|
@ViewChild('annotationFilterTemplate', {
|
|
read: TemplateRef,
|
|
static: false,
|
|
})
|
|
private readonly _filterTemplate: TemplateRef<unknown>;
|
|
|
|
constructor(
|
|
readonly pdf: PdfViewer,
|
|
private readonly _router: Router,
|
|
private readonly _ngZone: NgZone,
|
|
private readonly _logger: NGXLogger,
|
|
private readonly _filesService: FilesService,
|
|
private readonly _errorService: ErrorService,
|
|
readonly stateService: FilePreviewStateService,
|
|
private readonly _filterService: FilterService,
|
|
readonly permissionsService: PermissionsService,
|
|
readonly multiSelectService: MultiSelectService,
|
|
private readonly _activatedRoute: ActivatedRoute,
|
|
private readonly _loadingService: LoadingService,
|
|
private readonly _skippedService: SkippedService,
|
|
readonly documentInfoService: DocumentInfoService,
|
|
private readonly _filesMapService: FilesMapService,
|
|
private readonly _dossiersService: DossiersService,
|
|
private readonly _fileDataService: FileDataService,
|
|
private readonly _viewModeService: ViewModeService,
|
|
readonly excludedPagesService: ExcludedPagesService,
|
|
private readonly _watermarkService: WatermarkService,
|
|
private readonly _translateService: TranslateService,
|
|
readonly userPreferenceService: UserPreferenceService,
|
|
private readonly _changeDetectorRef: ChangeDetectorRef,
|
|
private readonly _reanalysisService: ReanalysisService,
|
|
private readonly _dialogService: FilePreviewDialogService,
|
|
private readonly _pageRotationService: PageRotationService,
|
|
private readonly _annotationDrawService: AnnotationDrawService,
|
|
private readonly _fileManagementService: FileManagementService,
|
|
private readonly _manualAnnotationService: ManualAnnotationService,
|
|
private readonly _annotationProcessingService: AnnotationProcessingService,
|
|
) {
|
|
super();
|
|
this.canPerformAnnotationActions$ = this._canPerformAnnotationActions$;
|
|
|
|
document.documentElement.addEventListener('fullscreenchange', () => {
|
|
if (!document.fullscreenElement) {
|
|
this.fullScreen = false;
|
|
}
|
|
});
|
|
}
|
|
|
|
get changed() {
|
|
return this._pageRotationService.hasRotations();
|
|
}
|
|
|
|
private get _canPerformAnnotationActions$() {
|
|
const viewMode$ = this._viewModeService.viewMode$.pipe(tap(() => this.#deactivateMultiSelect()));
|
|
|
|
return combineLatest([this.stateService.file$, this.stateService.dossier$, viewMode$, this._viewModeService.compareMode$]).pipe(
|
|
map(
|
|
([file, dossier, viewMode]) =>
|
|
this.permissionsService.canPerformAnnotationActions(file, dossier) && viewMode === 'STANDARD',
|
|
),
|
|
shareDistinctLast(),
|
|
);
|
|
}
|
|
|
|
async save() {
|
|
await this._pageRotationService.applyRotation();
|
|
}
|
|
|
|
async updateViewMode(): Promise<void> {
|
|
if (!this.pdf.ready) {
|
|
return;
|
|
}
|
|
|
|
this.pdf.deleteAnnotations(this._fileDataService.textHighlights.map(a => a.id));
|
|
|
|
const annotations = this.pdf.getAnnotations(a => a.getCustomData('redact-manager'));
|
|
const redactions = annotations.filter(a => a.getCustomData('redaction'));
|
|
|
|
switch (this._viewModeService.viewMode) {
|
|
case 'STANDARD': {
|
|
this._setAnnotationsColor(redactions, 'annotationColor');
|
|
const wrappers = await this._fileDataService.annotations;
|
|
const ocrAnnotationIds = wrappers.filter(a => a.isOCR).map(a => a.id);
|
|
|
|
const standardEntries = annotations
|
|
.filter(a => a.getCustomData('changeLogRemoved') === 'false')
|
|
.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);
|
|
break;
|
|
}
|
|
case 'DELTA': {
|
|
const changeLogEntries = annotations.filter(a => a.getCustomData('changeLog') === 'true');
|
|
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);
|
|
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);
|
|
break;
|
|
}
|
|
case 'TEXT_HIGHLIGHTS': {
|
|
this._loadingService.start();
|
|
this.pdf.hideAnnotations(annotations);
|
|
const highlights = await this._fileDataService.loadTextHighlights();
|
|
await this._annotationDrawService.drawAnnotations(highlights);
|
|
this._loadingService.stop();
|
|
}
|
|
}
|
|
|
|
await this._stampPDF();
|
|
await this.rebuildFilters();
|
|
}
|
|
|
|
ngOnDetach(): void {
|
|
this._pageRotationService.clearRotations();
|
|
this.displayPdfViewer = false;
|
|
super.ngOnDetach();
|
|
this._changeDetectorRef.markForCheck();
|
|
}
|
|
|
|
async ngOnAttach(previousRoute: ActivatedRouteSnapshot): Promise<boolean> {
|
|
const file = await this.stateService.file;
|
|
if (!file.canBeOpened) {
|
|
return this._router.navigate([this._dossiersService.find(this.dossierId)?.routerLink]);
|
|
}
|
|
this._viewModeService.compareMode = false;
|
|
this._viewModeService.switchToStandard();
|
|
|
|
await this.ngOnInit();
|
|
await this._fileDataService.loadRedactionLog();
|
|
this._lastPage = previousRoute.queryParams.page;
|
|
this._changeDetectorRef.markForCheck();
|
|
}
|
|
|
|
async ngOnInit(): Promise<void> {
|
|
this.ready = false;
|
|
this._loadingService.start();
|
|
await this.userPreferenceService.saveLastOpenedFileForDossier(this.dossierId, this.fileId);
|
|
this._subscribeToFileUpdates();
|
|
|
|
const file = await this.stateService.file;
|
|
if (file?.analysisRequired && !file.excludedFromAutomaticAnalysis) {
|
|
const reanalyzeFiles = this._reanalysisService.reanalyzeFilesForDossier([file], this.dossierId, { force: true });
|
|
await firstValueFrom(reanalyzeFiles);
|
|
}
|
|
|
|
this.displayPdfViewer = true;
|
|
}
|
|
|
|
async rebuildFilters(deletePreviousAnnotations = false) {
|
|
const startTime = new Date().getTime();
|
|
if (deletePreviousAnnotations) {
|
|
this.pdf.deleteAnnotations();
|
|
|
|
console.log(`[REDACTION] Delete previous annotations time: ${new Date().getTime() - startTime} ms`);
|
|
}
|
|
const processStartTime = new Date().getTime();
|
|
|
|
const visibleAnnotations = await this._fileDataService.visibleAnnotations;
|
|
const annotationFilters = this._annotationProcessingService.getAnnotationFilter(visibleAnnotations);
|
|
const primaryFilters = this._filterService.getGroup('primaryFilters')?.filters;
|
|
this._filterService.addFilterGroup({
|
|
slug: 'primaryFilters',
|
|
filterTemplate: this._filterTemplate,
|
|
filters: processFilters(primaryFilters, annotationFilters),
|
|
});
|
|
const secondaryFilters = this._filterService.getGroup('secondaryFilters')?.filters;
|
|
this._filterService.addFilterGroup({
|
|
slug: 'secondaryFilters',
|
|
filterTemplate: this._filterTemplate,
|
|
filters: processFilters(
|
|
secondaryFilters,
|
|
AnnotationProcessingService.secondaryAnnotationFilters(this._fileDataService.viewedPages),
|
|
),
|
|
});
|
|
|
|
this._logger.debug(`[REDACTION] Process time: ${new Date().getTime() - processStartTime} ms`);
|
|
this._logger.debug(`[REDACTION] Filter rebuild time: ${new Date().getTime() - startTime}`);
|
|
}
|
|
|
|
async handleAnnotationSelected(annotationIds: string[]) {
|
|
if (annotationIds.length > 0) {
|
|
this._workloadComponent.pagesPanelActive = false;
|
|
}
|
|
const visibleAnnotations = await this._fileDataService.visibleAnnotations;
|
|
this.selectedAnnotations = annotationIds
|
|
.map(id => visibleAnnotations.find(annotation => annotation.id === id))
|
|
.filter(ann => ann !== undefined);
|
|
if (this.selectedAnnotations.length > 1) {
|
|
this.multiSelectService.activate();
|
|
}
|
|
this._workloadComponent?.scrollToSelectedAnnotation();
|
|
this._changeDetectorRef.markForCheck();
|
|
}
|
|
|
|
@Debounce(10)
|
|
selectAnnotations(annotations: AnnotationWrapper[]) {
|
|
if (annotations) {
|
|
const annotationsToSelect = this.multiSelectService.isActive ? [...this.selectedAnnotations, ...annotations] : annotations;
|
|
this.pdf.selectAnnotations(annotationsToSelect, this.multiSelectService.isActive);
|
|
} else {
|
|
this.pdf.deselectAllAnnotations();
|
|
}
|
|
}
|
|
|
|
selectPage(pageNumber: number) {
|
|
this.pdf.navigateToPage(pageNumber);
|
|
this._workloadComponent?.scrollAnnotationsToPage(pageNumber, 'always');
|
|
this._lastPage = pageNumber.toString();
|
|
}
|
|
|
|
openManualAnnotationDialog(manualRedactionEntryWrapper: ManualRedactionEntryWrapper) {
|
|
this._ngZone.run(() => {
|
|
this.dialogRef = this._dialogService.openDialog(
|
|
'manualAnnotation',
|
|
null,
|
|
{ manualRedactionEntryWrapper, dossierId: this.dossierId },
|
|
async ({ manualRedactionEntry }: ManualRedactionEntryWrapper) => {
|
|
const addAnnotation$ = this._manualAnnotationService.addAnnotation(manualRedactionEntry, this.dossierId, this.fileId);
|
|
await firstValueFrom(addAnnotation$.pipe(catchError(() => of(undefined))));
|
|
await this._fileDataService.loadAnnotations();
|
|
},
|
|
);
|
|
});
|
|
}
|
|
|
|
toggleFullScreen() {
|
|
this.fullScreen = !this.fullScreen;
|
|
if (this.fullScreen) {
|
|
this._openFullScreen();
|
|
} else {
|
|
this.closeFullScreen();
|
|
}
|
|
}
|
|
|
|
handleArrowEvent($event: KeyboardEvent): void {
|
|
this._workloadComponent.handleKeyEvent($event);
|
|
}
|
|
|
|
@HostListener('window:keyup', ['$event'])
|
|
handleKeyEvent($event: KeyboardEvent) {
|
|
if (this._router.url.indexOf('/file/') < 0) {
|
|
return;
|
|
}
|
|
|
|
if (!ALL_HOTKEYS.includes($event.key) || this.dialogRef?.getState() === MatDialogState.OPEN) {
|
|
return;
|
|
}
|
|
|
|
if (['Escape'].includes($event.key)) {
|
|
this.fullScreen = false;
|
|
this.closeFullScreen();
|
|
this._changeDetectorRef.markForCheck();
|
|
}
|
|
|
|
if (['f', 'F'].includes($event.key)) {
|
|
// if you type in an input, don't toggle full-screen
|
|
if ($event.target instanceof HTMLInputElement || $event.target instanceof HTMLTextAreaElement) {
|
|
return;
|
|
}
|
|
this.toggleFullScreen();
|
|
return;
|
|
}
|
|
}
|
|
|
|
async viewerPageChanged($event: any) {
|
|
if (typeof $event !== 'number') {
|
|
return;
|
|
}
|
|
|
|
this._scrollViews();
|
|
this.multiSelectService.deactivate();
|
|
|
|
// Add current page in URL query params
|
|
const extras: NavigationExtras = {
|
|
queryParams: { page: $event },
|
|
queryParamsHandling: 'merge',
|
|
replaceUrl: true,
|
|
};
|
|
await this._router.navigate([], extras);
|
|
|
|
this._changeDetectorRef.markForCheck();
|
|
}
|
|
|
|
viewerReady() {
|
|
this.ready = true;
|
|
this.pdf.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) {
|
|
setTimeout(() => {
|
|
this.selectPage(parseInt(pageNumber, 10));
|
|
this._scrollViews();
|
|
this._changeDetectorRef.markForCheck();
|
|
this._loadingService.stop();
|
|
});
|
|
} else {
|
|
this._loadingService.stop();
|
|
}
|
|
this._changeDetectorRef.markForCheck();
|
|
}
|
|
|
|
async annotationsChangedByReviewAction() {
|
|
this.multiSelectService.deactivate();
|
|
const file = await this.stateService.file;
|
|
await firstValueFrom(this._filesService.reload(this.dossierId, file));
|
|
}
|
|
|
|
closeFullScreen() {
|
|
if (!!document.fullscreenElement && document.exitFullscreen) {
|
|
document.exitFullscreen().then();
|
|
}
|
|
}
|
|
|
|
async switchView(viewMode: ViewMode) {
|
|
this._viewModeService.viewMode = viewMode;
|
|
await this.updateViewMode();
|
|
this._scrollViews();
|
|
}
|
|
|
|
async downloadOriginalFile(file: File) {
|
|
const originalFile = this._fileManagementService.downloadOriginalFile(
|
|
this.dossierId,
|
|
this.fileId,
|
|
'response',
|
|
file.cacheIdentifier,
|
|
);
|
|
download(await firstValueFrom(originalFile), file.filename);
|
|
}
|
|
|
|
loadAnnotations() {
|
|
const documentLoaded$ = this.pdf.documentLoaded$.pipe(tap(() => this.viewerReady()));
|
|
let start;
|
|
return combineLatest([documentLoaded$, this._fileDataService.annotations$]).pipe(
|
|
debounceTime(300),
|
|
log(),
|
|
tap(() => (start = new Date().getTime())),
|
|
map(([, annotations]) => annotations),
|
|
startWith({} as Record<string, AnnotationWrapper>),
|
|
pairwise(),
|
|
tap(annotations => this.deleteAnnotations(...annotations)),
|
|
switchMap(annotations => this.drawChangedAnnotations(...annotations)),
|
|
tap(() => this._logger.debug(`[ANNOTATIONS] Processing time: ${new Date().getTime() - start}`)),
|
|
tap(() => this.updateViewMode()),
|
|
);
|
|
}
|
|
|
|
deleteAnnotations(oldAnnotations: Record<string, AnnotationWrapper>, newAnnotations: Record<string, AnnotationWrapper>) {
|
|
const annotationsToDelete = Object.values(oldAnnotations).filter(oldAnnotation => !newAnnotations[oldAnnotation.id]);
|
|
|
|
if (annotationsToDelete.length === 0) {
|
|
return;
|
|
}
|
|
|
|
this._logger.debug('[ANNOTATIONS] To delete: ', annotationsToDelete);
|
|
this.pdf.deleteAnnotations(annotationsToDelete.map(annotation => annotation.id));
|
|
}
|
|
|
|
drawChangedAnnotations(oldAnnotations: Record<string, AnnotationWrapper>, newAnnotations: Record<string, AnnotationWrapper>) {
|
|
let annotationsToDraw: readonly AnnotationWrapper[];
|
|
if (this.pdf.hasAnnotations) {
|
|
annotationsToDraw = this.#getAnnotationsToDraw(newAnnotations, oldAnnotations);
|
|
} else {
|
|
annotationsToDraw = Object.values(newAnnotations);
|
|
}
|
|
|
|
if (annotationsToDraw.length === 0) {
|
|
return firstValueFrom(of({}));
|
|
}
|
|
|
|
this._logger.debug('[ANNOTATIONS] To draw: ', annotationsToDraw);
|
|
const annotationsToDrawIds = annotationsToDraw.map(a => a.annotationId);
|
|
this.pdf.deleteAnnotations(annotationsToDrawIds);
|
|
return this._cleanupAndRedrawAnnotations(annotationsToDraw);
|
|
}
|
|
|
|
#getAnnotationsToDraw(newAnnotations: Record<string, AnnotationWrapper>, oldAnnotations: Record<string, AnnotationWrapper>) {
|
|
return Object.values(newAnnotations).filter(newAnnotation => {
|
|
const oldAnnotation = oldAnnotations[newAnnotation.id];
|
|
if (!oldAnnotation) {
|
|
return true;
|
|
}
|
|
|
|
const changed = JSON.stringify(oldAnnotation) !== JSON.stringify(newAnnotation);
|
|
if (changed && this.userPreferenceService.areDevFeaturesEnabled) {
|
|
import('@iqser/common-ui').then(commonUi => {
|
|
this._logger.debug('[ANNOTATIONS] Changed annotation: ', {
|
|
value: oldAnnotation.value,
|
|
before: commonUi.deepDiffObj(newAnnotation, oldAnnotation),
|
|
after: commonUi.deepDiffObj(oldAnnotation, newAnnotation),
|
|
});
|
|
});
|
|
}
|
|
|
|
return changed;
|
|
});
|
|
}
|
|
|
|
async #deactivateMultiSelect() {
|
|
this.multiSelectService.deactivate();
|
|
this.pdf.deselectAllAnnotations();
|
|
await this.handleAnnotationSelected([]);
|
|
}
|
|
|
|
private _setExcludedPageStyles() {
|
|
const file = this._filesMapService.get(this.dossierId, this.fileId);
|
|
setTimeout(() => {
|
|
const iframeDoc = this.pdf.UI.iframeWindow.document;
|
|
const pageContainer = iframeDoc.getElementById(`pageWidgetContainer${this.pdf.currentPage}`);
|
|
if (pageContainer) {
|
|
if (file.excludedPages.includes(this.pdf.currentPage)) {
|
|
pageContainer.classList.add('excluded-page');
|
|
} else {
|
|
pageContainer.classList.remove('excluded-page');
|
|
}
|
|
}
|
|
}, 100);
|
|
}
|
|
|
|
private async _stampPDF() {
|
|
const pdfDoc = await this.pdf.documentViewer.getDocument().getPDFDoc();
|
|
const file = await this.stateService.file;
|
|
const allPages = [...Array(file.numberOfPages).keys()].map(page => page + 1);
|
|
|
|
if (!pdfDoc || !this.pdf.ready) {
|
|
return;
|
|
}
|
|
|
|
try {
|
|
await clearStamps(pdfDoc, this.pdf.PDFNet, allPages);
|
|
} catch (e) {
|
|
this._logger.debug('Error clearing stamps: ', e);
|
|
return;
|
|
}
|
|
|
|
if (this._viewModeService.isRedacted) {
|
|
const dossier = await this.stateService.dossier;
|
|
if (dossier.watermarkPreviewEnabled) {
|
|
await this._stampPreview(pdfDoc, dossier.dossierTemplateId);
|
|
}
|
|
} else {
|
|
await this._stampExcludedPages(pdfDoc, file.excludedPages);
|
|
}
|
|
|
|
this.pdf.documentViewer.refreshAll();
|
|
this.pdf.documentViewer.updateView([this.pdf.currentPage], this.pdf.currentPage);
|
|
this._changeDetectorRef.markForCheck();
|
|
}
|
|
|
|
private async _stampPreview(document: PDFNet.PDFDoc, dossierTemplateId: string) {
|
|
const watermark = await firstValueFrom(this._watermarkService.getWatermark(dossierTemplateId));
|
|
await stampPDFPage(
|
|
document,
|
|
this.pdf.PDFNet,
|
|
watermark.text,
|
|
watermark.fontSize,
|
|
watermark.fontType,
|
|
watermark.orientation,
|
|
watermark.opacity,
|
|
watermark.hexColor,
|
|
Array.from({ length: await document.getPageCount() }, (x, i) => i + 1),
|
|
);
|
|
}
|
|
|
|
private async _stampExcludedPages(document: PDFNet.PDFDoc, excludedPages: number[]) {
|
|
if (excludedPages && excludedPages.length > 0) {
|
|
await stampPDFPage(
|
|
document,
|
|
this.pdf.PDFNet,
|
|
this._translateService.instant('file-preview.excluded-from-redaction') as string,
|
|
17,
|
|
'courier',
|
|
'TOP_LEFT',
|
|
50,
|
|
'#dd4d50',
|
|
excludedPages,
|
|
);
|
|
}
|
|
}
|
|
|
|
private _subscribeToFileUpdates(): void {
|
|
this.addActiveScreenSubscription = this.loadAnnotations().subscribe();
|
|
|
|
// With changes monitoring, this should not be necessary
|
|
// this.addActiveScreenSubscription = timer(0, 5000)
|
|
// .pipe(
|
|
// switchMap(() => this.stateService.file$),
|
|
// switchMap(file => this._filesService.reload(this.dossierId, file)),
|
|
// )
|
|
// .subscribe();
|
|
|
|
this.addActiveScreenSubscription = this._dossiersService
|
|
.getEntityDeleted$(this.dossierId)
|
|
.pipe(tap(() => this._handleDeletedDossier()))
|
|
.subscribe();
|
|
|
|
this.addActiveScreenSubscription = this._filesMapService
|
|
.watchDeleted$(this.fileId)
|
|
.pipe(tap(() => this._handleDeletedFile()))
|
|
.subscribe();
|
|
|
|
this.addActiveScreenSubscription = this._skippedService.hideSkipped$
|
|
.pipe(tap(hideSkipped => this._handleIgnoreAnnotationsDrawing(hideSkipped)))
|
|
.subscribe();
|
|
}
|
|
|
|
private _handleDeletedDossier(): void {
|
|
this._errorService.set(
|
|
new CustomError(_('error.deleted-entity.file-dossier.label'), _('error.deleted-entity.file-dossier.action'), 'iqser:expand'),
|
|
);
|
|
}
|
|
|
|
private _handleDeletedFile(): void {
|
|
this._errorService.set(
|
|
new CustomError(_('error.deleted-entity.file.label'), _('error.deleted-entity.file.action'), 'iqser:expand'),
|
|
);
|
|
}
|
|
|
|
@Debounce(0)
|
|
private _scrollViews() {
|
|
this._workloadComponent?.scrollQuickNavigation();
|
|
this._workloadComponent?.scrollAnnotations();
|
|
}
|
|
|
|
private async _cleanupAndRedrawAnnotations(newAnnotations: readonly AnnotationWrapper[]) {
|
|
if (!this.pdf.ready) {
|
|
return;
|
|
}
|
|
|
|
const currentFilters = this._filterService.getGroup('primaryFilters')?.filters || [];
|
|
await this.rebuildFilters();
|
|
|
|
const startTime = new Date().getTime();
|
|
|
|
if (currentFilters) {
|
|
const visibleAnnotations = await this._fileDataService.visibleAnnotations;
|
|
this._handleDeltaAnnotationFilters(currentFilters, visibleAnnotations);
|
|
}
|
|
|
|
await this._annotationDrawService.drawAnnotations(newAnnotations);
|
|
this._logger.debug(`[ANNOTATIONS] Redraw time: ${new Date().getTime() - startTime} ms for ${newAnnotations.length} annotations`);
|
|
}
|
|
|
|
private _handleDeltaAnnotationFilters(currentFilters: NestedFilter[], newAnnotations: AnnotationWrapper[]) {
|
|
const primaryFilterGroup = this._filterService.getGroup('primaryFilters');
|
|
const primaryFilters = primaryFilterGroup.filters;
|
|
const secondaryFilters = this._filterService.getGroup('secondaryFilters').filters;
|
|
const hasAnyFilterSet = [...primaryFilters, ...secondaryFilters].find(f => f.checked || f.indeterminate);
|
|
|
|
if (!hasAnyFilterSet) {
|
|
return;
|
|
}
|
|
|
|
const newPageSpecificFilters = this._annotationProcessingService.getAnnotationFilter(newAnnotations);
|
|
|
|
handleFilterDelta(currentFilters, newPageSpecificFilters, primaryFilters);
|
|
this._filterService.addFilterGroup({
|
|
...primaryFilterGroup,
|
|
filters: primaryFilters,
|
|
});
|
|
}
|
|
|
|
private _openFullScreen() {
|
|
const documentElement = document.documentElement;
|
|
if (documentElement.requestFullscreen) {
|
|
documentElement.requestFullscreen().then();
|
|
}
|
|
}
|
|
|
|
private _handleIgnoreAnnotationsDrawing(hideSkipped: boolean): void {
|
|
const ignored = this.pdf.getAnnotations(a => a.getCustomData('skipped'));
|
|
if (hideSkipped) {
|
|
this.pdf.hideAnnotations(ignored);
|
|
} else {
|
|
this.pdf.showAnnotations(ignored);
|
|
}
|
|
}
|
|
|
|
private _setAnnotationsOpacity(annotations: Annotation[], restoreToOriginal: boolean = false) {
|
|
annotations.forEach(annotation => {
|
|
annotation['Opacity'] = restoreToOriginal ? parseFloat(annotation.getCustomData('opacity')) : 1;
|
|
});
|
|
}
|
|
|
|
private _setAnnotationsColor(annotations: Annotation[], customData: string) {
|
|
annotations.forEach(annotation => {
|
|
const color = this._annotationDrawService.convertColor(annotation.getCustomData(customData));
|
|
annotation['StrokeColor'] = color;
|
|
annotation['FillColor'] = color;
|
|
});
|
|
}
|
|
}
|