State service

This commit is contained in:
Adina Țeudan 2022-01-18 20:53:17 +02:00
parent 7993092732
commit 5a020c6d9b
9 changed files with 95 additions and 62 deletions

View File

@ -96,7 +96,6 @@
[file]="file"
[number]="pageNumber"
[showDottedIcon]="hasOnlyManualRedactionsAndIsExcluded(pageNumber)"
[viewedPages]="viewedPages"
></redaction-page-indicator>
</div>

View File

@ -28,7 +28,7 @@ import { PermissionsService } from '@services/permissions.service';
import { WebViewerInstance } from '@pdftron/webviewer';
import { BehaviorSubject, combineLatest, Observable } from 'rxjs';
import { map, tap } from 'rxjs/operators';
import { File, IViewedPage } from '@red/domain';
import { File } from '@red/domain';
import { ExcludedPagesService } from '../../services/excluded-pages.service';
import { MultiSelectService } from '../../services/multi-select.service';
import { DocumentInfoService } from '../../services/document-info.service';
@ -52,7 +52,6 @@ export class FileWorkloadComponent {
@Input() activeViewerPage: number;
@Input() shouldDeselectAnnotationsOnPageChange: boolean;
@Input() dialogRef: MatDialogRef<unknown>;
@Input() viewedPages: IViewedPage[];
@Input() file!: File;
@Input() annotationActionsTemplate: TemplateRef<unknown>;
@Input() viewer: WebViewerInstance;
@ -225,7 +224,7 @@ export class FileWorkloadComponent {
scrollAnnotationsToPage(page: number, mode: 'always' | 'if-needed' = 'if-needed'): void {
if (this._annotationsElement) {
const elements = this._annotationsElement.nativeElement.querySelectorAll(`div[anotation-page-header="${page}"]`);
const elements: HTMLElement[] = this._annotationsElement.nativeElement.querySelectorAll(`div[anotation-page-header="${page}"]`);
FileWorkloadComponent._scrollToFirstElement(elements, mode);
}
}
@ -235,7 +234,7 @@ export class FileWorkloadComponent {
if (!this.selectedAnnotations || this.selectedAnnotations.length === 0 || !this._annotationsElement) {
return;
}
const elements = this._annotationsElement.nativeElement.querySelectorAll(
const elements: HTMLElement[] = this._annotationsElement.nativeElement.querySelectorAll(
`div[annotation-id="${this._firstSelectedAnnotation?.id}"].active`,
);
FileWorkloadComponent._scrollToFirstElement(elements);
@ -412,7 +411,7 @@ export class FileWorkloadComponent {
private _scrollQuickNavigationToPage(page: number) {
if (this._quickNavigationElement) {
const elements = this._quickNavigationElement.nativeElement.querySelectorAll(`#quick-nav-page-${page}`);
const elements: HTMLElement[] = this._quickNavigationElement.nativeElement.querySelectorAll(`#quick-nav-page-${page}`);
FileWorkloadComponent._scrollToFirstElement(elements);
}
}

View File

@ -6,6 +6,7 @@ import { ViewedPagesService } from '@services/entity-services/viewed-pages.servi
import { File, IViewedPage } from '@red/domain';
import { AutoUnsubscribe } from '@iqser/common-ui';
import { FilesMapService } from '@services/entity-services/files-map.service';
import { FilePreviewStateService } from '../../services/file-preview-state.service';
@Component({
selector: 'redaction-page-indicator',
@ -18,7 +19,6 @@ export class PageIndicatorComponent extends AutoUnsubscribe implements OnDestroy
@Input() active = false;
@Input() showDottedIcon = false;
@Input() number: number;
@Input() viewedPages: IViewedPage[];
@Input() activeSelection = false;
@Output() readonly pageSelected = new EventEmitter<number>();
@ -33,25 +33,17 @@ export class PageIndicatorComponent extends AutoUnsubscribe implements OnDestroy
private readonly _configService: ConfigService,
private readonly _changeDetectorRef: ChangeDetectorRef,
private readonly _permissionService: PermissionsService,
private readonly _stateService: FilePreviewStateService,
) {
super();
}
get activePage() {
return this.viewedPages?.find(p => p.page === this.number);
return this._viewedPages.find(p => p.page === this.number);
}
private _setReadState() {
const readBefore = this.read;
const activePage = this.activePage;
if (!activePage) {
this.read = false;
} else {
// console.log('setting read to',activePage.showAsUnseen, !activePage.showAsUnseen);
this.read = !activePage.showAsUnseen;
}
// console.log(this.number, readBefore, activePage, this.read);
this._changeDetectorRef.detectChanges();
private get _viewedPages(): IViewedPage[] {
return this._stateService.fileData?.viewedPages || [];
}
ngOnChanges() {
@ -87,20 +79,33 @@ export class PageIndicatorComponent extends AutoUnsubscribe implements OnDestroy
}
}
private _setReadState() {
const readBefore = this.read;
const activePage = this.activePage;
if (!activePage) {
this.read = false;
} else {
// console.log('setting read to',activePage.showAsUnseen, !activePage.showAsUnseen);
this.read = !activePage.showAsUnseen;
}
// console.log(this.number, readBefore, activePage, this.read);
this._changeDetectorRef.detectChanges();
}
private async _markPageRead() {
await this._viewedPagesService.addPage({ page: this.number }, this.file.dossierId, this.file.fileId).toPromise();
if (this.activePage) {
this.activePage.showAsUnseen = false;
} else {
this.viewedPages?.push({ page: this.number, fileId: this.file.fileId });
this._viewedPages.push({ page: this.number, fileId: this.file.fileId });
}
this._setReadState();
}
private async _markPageUnread() {
await this._viewedPagesService.removePage(this.file.dossierId, this.file.fileId, this.number).toPromise();
this.viewedPages?.splice(
this.viewedPages?.findIndex(p => p.page === this.number),
this._viewedPages.splice(
this._viewedPages.findIndex(p => p.page === this.number),
1,
);
this._setReadState();

View File

@ -36,11 +36,12 @@ import { ActivatedRoute } from '@angular/router';
import { toPosition } from '../../../../utils/pdf-calculation.utils';
import { ViewModeService } from '../../services/view-mode.service';
import { MultiSelectService } from '../../services/multi-select.service';
import { FilePreviewStateService } from '../../services/file-preview-state.service';
import Tools = Core.Tools;
import TextTool = Tools.TextTool;
import Annotation = Core.Annotations.Annotation;
const ALLOWED_KEYBOARD_SHORTCUTS = ['+', '-', 'p', 'r', 'Escape'] as const;
const ALLOWED_KEYBOARD_SHORTCUTS: readonly string[] = ['+', '-', 'p', 'r', 'Escape'] as const;
const dataElements = {
ADD_REDACTION: 'add-redaction',
ADD_DICTIONARY: 'add-dictionary',
@ -62,7 +63,6 @@ const dataElements = {
styleUrls: ['./pdf-viewer.component.scss'],
})
export class PdfViewerComponent implements OnInit, OnChanges {
@Input() fileData: Blob;
@Input() file: File;
@Input() dossier: Dossier;
@Input() canPerformActions = false;
@ -95,6 +95,7 @@ export class PdfViewerComponent implements OnInit, OnChanges {
private readonly _annotationActionsService: AnnotationActionsService,
private readonly _configService: ConfigService,
private readonly _loadingService: LoadingService,
private readonly _stateService: FilePreviewStateService,
readonly viewModeService: ViewModeService,
readonly multiSelectService: MultiSelectService,
) {}
@ -113,6 +114,10 @@ export class PdfViewerComponent implements OnInit, OnChanges {
);
}
private get _fileData(): Blob {
return this._stateService.fileData.fileData;
}
async ngOnInit() {
this._setReadyAndInitialState = this._setReadyAndInitialState.bind(this);
await this.loadViewer();
@ -132,7 +137,7 @@ export class PdfViewerComponent implements OnInit, OnChanges {
}
}
uploadFile(files: any) {
uploadFile(files: FileList) {
const fileToCompare = files[0];
this.compareFileInput.nativeElement.value = null;
@ -148,7 +153,7 @@ export class PdfViewerComponent implements OnInit, OnChanges {
await pdfNet.initialize(environment.licenseKey ? atob(environment.licenseKey) : null);
const compareDocument = await pdfNet.PDFDoc.createFromBuffer(fileReader.result as ArrayBuffer);
const currentDocument = await pdfNet.PDFDoc.createFromBuffer(await this.fileData.arrayBuffer());
const currentDocument = await pdfNet.PDFDoc.createFromBuffer(await this._fileData.arrayBuffer());
const loadCompareDocument = async () => {
this._loadingService.start();
@ -201,7 +206,7 @@ export class PdfViewerComponent implements OnInit, OnChanges {
this.viewModeService.compareMode = false;
const pdfNet = this.instance.Core.PDFNet;
await pdfNet.initialize(environment.licenseKey ? atob(environment.licenseKey) : null);
const currentDocument = await pdfNet.PDFDoc.createFromBuffer(await this.fileData.arrayBuffer());
const currentDocument = await pdfNet.PDFDoc.createFromBuffer(await this._fileData.arrayBuffer());
this.instance.UI.loadDocument(currentDocument, {
filename: this.file ? this.file.filename : 'document.pdf',
});
@ -219,7 +224,7 @@ export class PdfViewerComponent implements OnInit, OnChanges {
css: this._convertPath('/assets/pdftron/stylesheet.css'),
backendType: 'ems',
},
this.viewer.nativeElement,
this.viewer.nativeElement as HTMLElement,
);
this.documentViewer = this.instance.Core.documentViewer;
@ -231,7 +236,7 @@ export class PdfViewerComponent implements OnInit, OnChanges {
this.utils.disableHotkeys();
this._configureTextPopup();
this.annotationManager.addEventListener('annotationSelected', (annotations, action) => {
this.annotationManager.addEventListener('annotationSelected', (annotations: Annotation[], action) => {
this.annotationSelected.emit(this.annotationManager.getSelectedAnnotations().map(ann => ann.Id));
if (action === 'deselected') {
this._toggleRectangleAnnotationAction(true);
@ -241,7 +246,7 @@ export class PdfViewerComponent implements OnInit, OnChanges {
}
});
this.annotationManager.addEventListener('annotationChanged', annotations => {
this.annotationManager.addEventListener('annotationChanged', (annotations: Annotation[]) => {
// when a rectangle is drawn,
// it returns one annotation with tool name 'AnnotationCreateRectangle;
// this will auto select rectangle after drawing
@ -251,7 +256,7 @@ export class PdfViewerComponent implements OnInit, OnChanges {
}
});
this.documentViewer.addEventListener('pageNumberUpdated', pageNumber => {
this.documentViewer.addEventListener('pageNumberUpdated', (pageNumber: number) => {
if (this.shouldDeselectAnnotationsOnPageChange) {
this.utils.deselectAllAnnotations();
}
@ -261,9 +266,9 @@ export class PdfViewerComponent implements OnInit, OnChanges {
this.documentViewer.addEventListener('documentLoaded', this._setReadyAndInitialState);
this.documentViewer.addEventListener('keyUp', $event => {
this.documentViewer.addEventListener('keyUp', ($event: KeyboardEvent) => {
// arrows and full-screen
if ($event.target?.tagName?.toLowerCase() !== 'input') {
if (($event.target as HTMLElement)?.tagName?.toLowerCase() !== 'input') {
if ($event.key.startsWith('Arrow') || $event.key === 'f') {
this._ngZone.run(() => {
this.keyUp.emit($event);
@ -273,7 +278,7 @@ export class PdfViewerComponent implements OnInit, OnChanges {
}
}
if (ALLOWED_KEYBOARD_SHORTCUTS.indexOf($event.key) < 0) {
if (!ALLOWED_KEYBOARD_SHORTCUTS.includes($event.key)) {
$event.preventDefault();
$event.stopPropagation();
}
@ -568,7 +573,7 @@ export class PdfViewerComponent implements OnInit, OnChanges {
}
private _addManualRedactionOfType(type: ManualRedactionEntryType) {
const selectedQuads = this.documentViewer.getSelectedTextQuads();
const selectedQuads: Readonly<Record<string, Core.Math.Quad[]>> = this.documentViewer.getSelectedTextQuads();
const text = this.documentViewer.getSelectedText();
const manualRedaction = this._getManualRedaction(selectedQuads, text, true);
this.manualAnnotationRequested.emit(new ManualRedactionEntryWrapper(selectedQuads, manualRedaction, type));
@ -624,7 +629,7 @@ export class PdfViewerComponent implements OnInit, OnChanges {
for (const quad of quads[key]) {
const page = parseInt(key, 10);
const pageHeight = this.documentViewer.getPageHeight(page);
entry.positions.push(toPosition(page, pageHeight, convertQuads ? this.utils.translateQuads(page, quad) : quad));
entry.positions.push(toPosition(page, pageHeight, convertQuads ? this.utils.translateQuad(page, quad) : quad));
}
}
@ -634,11 +639,11 @@ export class PdfViewerComponent implements OnInit, OnChanges {
}
private _loadDocument() {
if (!this.fileData) {
if (!this._fileData) {
return;
}
this.instance.UI.loadDocument(this.fileData, {
this.instance.UI.loadDocument(this._fileData, {
filename: this.file ? this.file.filename : 'document.pdf',
});
}
@ -647,7 +652,7 @@ export class PdfViewerComponent implements OnInit, OnChanges {
this._ngZone.run(() => {
this.utils.ready = true;
this.viewerReady.emit(this.instance);
const routePageNumber = this._activatedRoute.snapshot.queryParams.page;
const routePageNumber: number = this._activatedRoute.snapshot.queryParams.page;
this.pageChanged.emit(routePageNumber || 1);
this._setInitialDisplayMode();
this._updateTooltipsVisibility();

View File

@ -4,7 +4,7 @@ import { ViewModeService } from '../../services/view-mode.service';
import { FileDataModel } from '@models/file/file-data.model';
@Component({
selector: 'redaction-view-switch [file] [fileData]',
selector: 'redaction-view-switch [file]',
templateUrl: './view-switch.component.html',
styleUrls: ['./view-switch.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
@ -12,7 +12,6 @@ import { FileDataModel } from '@models/file/file-data.model';
export class ViewSwitchComponent implements OnChanges {
@Output() readonly switchView = new EventEmitter<ViewMode>();
@Input() file: File;
@Input() fileData: FileDataModel;
canSwitchToDeltaView = false;
canSwitchToRedactedView = false;

View File

@ -3,7 +3,7 @@
<section [class.fullscreen]="fullScreen">
<div class="page-header">
<div class="flex flex-1">
<redaction-view-switch (switchView)="switchView($event)" [fileData]="fileData" [file]="file"></redaction-view-switch>
<redaction-view-switch (switchView)="switchView($event)" [file]="file"></redaction-view-switch>
</div>
<div class="flex-1 actions-container">
@ -76,7 +76,6 @@
[canPerformActions]="canPerformAnnotationActions$ | async"
[class.hidden]="!ready"
[dossier]="dossier"
[fileData]="fileData?.fileData"
[file]="file"
[shouldDeselectAnnotationsOnPageChange]="shouldDeselectAnnotationsOnPageChange"
></redaction-pdf-viewer>
@ -110,7 +109,6 @@
[dialogRef]="dialogRef"
[file]="file"
[selectedAnnotations]="selectedAnnotations"
[viewedPages]="fileData?.viewedPages"
[viewer]="activeViewer"
></redaction-file-workload>
</div>

View File

@ -20,7 +20,6 @@ import { MatDialogRef, MatDialogState } from '@angular/material/dialog';
import { ManualRedactionEntryWrapper } from '@models/file/manual-redaction-entry.wrapper';
import { AnnotationWrapper } from '@models/file/annotation.wrapper';
import { ManualAnnotationResponse } from '@models/file/manual-annotation-response';
import { FileDataModel } from '@models/file/file-data.model';
import { AnnotationDrawService } from './services/annotation-draw.service';
import { AnnotationProcessingService } from '../../services/annotation-processing.service';
import { Dossier, File, ViewMode } from '@red/domain';
@ -49,6 +48,8 @@ import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
import { CommentingService } from './services/commenting.service';
import { SkippedService } from './services/skipped.service';
import { AnnotationActionsService } from './services/annotation-actions.service';
import { FilePreviewStateService } from './services/file-preview-state.service';
import { FileDataModel } from '../../../../models/file/file-data.model';
import Annotation = Core.Annotations.Annotation;
import PDFNet = Core.PDFNet;
@ -67,6 +68,7 @@ const ALL_HOTKEY_ARRAY = ['Escape', 'F', 'f', 'ArrowUp', 'ArrowDown'];
SkippedService,
AnnotationDrawService,
AnnotationActionsService,
FilePreviewStateService,
],
})
export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnInit, OnDestroy, OnAttach, OnDetach {
@ -75,7 +77,6 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
dialogRef: MatDialogRef<unknown>;
fullScreen = false;
shouldDeselectAnnotationsOnPageChange = true;
fileData: FileDataModel;
selectedAnnotations: AnnotationWrapper[] = [];
displayPdfViewer = false;
activeViewerPage: number = null;
@ -98,6 +99,7 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
constructor(
readonly permissionsService: PermissionsService,
readonly userPreferenceService: UserPreferenceService,
private readonly _stateService: FilePreviewStateService,
private readonly _watermarkService: WatermarkService,
private readonly _changeDetectorRef: ChangeDetectorRef,
private readonly _activatedRoute: ActivatedRoute,
@ -142,11 +144,11 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
}
get visibleAnnotations(): AnnotationWrapper[] {
return this.fileData ? this.fileData.getVisibleAnnotations(this.viewModeService.viewMode) : [];
return this._fileData ? this._fileData.getVisibleAnnotations(this.viewModeService.viewMode) : [];
}
get allAnnotations(): AnnotationWrapper[] {
return this.fileData ? this.fileData.allAnnotations : [];
return this._fileData ? this._fileData.allAnnotations : [];
}
get activeViewer(): WebViewerInstance {
@ -160,8 +162,12 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
);
}
private get _fileData(): FileDataModel {
return this._stateService.fileData;
}
async updateViewMode(): Promise<void> {
const ocrAnnotationIds = this.fileData.allAnnotations.filter(a => a.isOCR).map(a => a.id);
const ocrAnnotationIds = this._fileData.allAnnotations.filter(a => a.isOCR).map(a => a.id);
const annotations = this._getAnnotations(a => a.getCustomData('redact-manager'));
const redactions = annotations.filter(a => a.getCustomData('redaction'));
@ -256,7 +262,7 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
this._filterService.addFilterGroup({
slug: 'secondaryFilters',
filterTemplate: this._filterTemplate,
filters: processFilters(secondaryFilters, AnnotationProcessingService.secondaryAnnotationFilters(this.fileData?.viewedPages)),
filters: processFilters(secondaryFilters, AnnotationProcessingService.secondaryAnnotationFilters(this._fileData?.viewedPages)),
});
console.log(`[REDACTION] Process time: ${new Date().getTime() - processStartTime} ms`);
console.log(`[REDACTION] Filter rebuild time: ${new Date().getTime() - startTime}`);
@ -466,13 +472,14 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
}
private async _reloadFile(file: File): Promise<void> {
const previousFile = this.fileData?.file;
const previousFile = this._fileData?.file;
await this._loadFileData(file);
const fileHasBeenExcludedOrIncluded = previousFile?.excluded !== this.fileData?.file?.excluded;
const excludedPagesHaveChanged = JSON.stringify(previousFile?.excludedPages) !== JSON.stringify(this.fileData?.file?.excludedPages);
const fileHasBeenExcludedOrIncluded = previousFile?.excluded !== this._fileData?.file?.excluded;
const excludedPagesHaveChanged =
JSON.stringify(previousFile?.excludedPages) !== JSON.stringify(this._fileData?.file?.excludedPages);
if (fileHasBeenExcludedOrIncluded || excludedPagesHaveChanged) {
await this._deleteAnnotations();
this._deleteAnnotations();
await this._cleanupAndRedrawAnnotations();
}
@ -546,7 +553,7 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
this.addActiveScreenSubscription = this._filesMapService.fileReanalysed$
.pipe(filter(file => file.fileId === this.fileId))
.subscribe(async file => {
if (file.lastProcessed !== this.fileData?.file.lastProcessed) {
if (file.lastProcessed !== this._fileData?.file.lastProcessed) {
const previousAnnotations = this.visibleAnnotations;
await this._loadFileData(file);
await this._reloadAnnotations(previousAnnotations);
@ -586,13 +593,13 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
return this._router.navigate([this._dossiersService.find(this.dossierId).routerLink]);
}
const fileData = await this._fileDownloadService.loadDataFor(file, this.fileData).toPromise();
const fileData = await this._fileDownloadService.loadDataFor(file, this._fileData).toPromise();
if (file.isPending) {
return;
}
this.fileData = fileData;
this._stateService.fileData = fileData;
}
@Debounce(0)
@ -607,16 +614,16 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
}
private async _reloadAnnotationsForPage(page: number) {
this.fileData.file = await this._filesService.reload(this.dossierId, this.fileId).toPromise();
this._fileData.file = await this._filesService.reload(this.dossierId, this.fileId).toPromise();
// if this action triggered a re-processing,
// we don't want to redraw for this page since they will get redrawn as soon as processing ends;
if (this.fileData.file.isProcessing) {
if (this._fileData.file.isProcessing) {
return;
}
const currentPageAnnotations = this.visibleAnnotations.filter(a => a.pageNumber === page);
this.fileData.redactionLog = await this._fileDownloadService.loadRedactionLogFor(this.dossierId, this.fileId).toPromise();
this._fileData.redactionLog = await this._fileDownloadService.loadRedactionLogFor(this.dossierId, this.fileId).toPromise();
this._deleteAnnotations(currentPageAnnotations);
await this._cleanupAndRedrawAnnotations(currentPageAnnotations, annotation => annotation.pageNumber === page);

View File

@ -0,0 +1,21 @@
import { Injectable } from '@angular/core';
import { BehaviorSubject, Observable } from 'rxjs';
import { FileDataModel } from '@models/file/file-data.model';
@Injectable()
export class FilePreviewStateService {
readonly fileData$: Observable<FileDataModel>;
private readonly _fileData$ = new BehaviorSubject<FileDataModel>(undefined);
constructor() {
this.fileData$ = this._fileData$.asObservable();
}
get fileData(): FileDataModel {
return this._fileData$.value;
}
set fileData(fileDataModel: FileDataModel) {
this._fileData$.next(fileDataModel);
}
}

View File

@ -111,9 +111,9 @@ export class PdfViewerUtils {
}
}
translateQuads(page: number, quads: any) {
translateQuad(page: number, quad: Core.Math.Quad) {
const rotation = this._documentViewer.getCompleteRotation(page);
return translateQuads(page, rotation, quads);
return translateQuads(page, rotation, quad);
}
deselectAllAnnotations() {