This commit is contained in:
Adina Țeudan 2022-01-22 13:31:09 +02:00
parent df20f7ba85
commit 527d4933a0
5 changed files with 56 additions and 60 deletions

View File

@ -9,20 +9,30 @@ export class FileDataModel {
allAnnotations: AnnotationWrapper[];
readonly hasChangeLog$ = new BehaviorSubject<boolean>(false);
readonly blob$ = new BehaviorSubject<Blob>(undefined);
readonly file$ = new BehaviorSubject<File>(undefined);
constructor(
public file: File,
private readonly _file: File,
private readonly _blob: Blob,
private _redactionLog: IRedactionLog,
public viewedPages?: IViewedPage[],
private _dictionaryData?: { [p: string]: Dictionary },
private _areDevFeaturesEnabled?: boolean,
) {
this.file$.next(_file);
this.blob$.next(_blob);
this._buildAllAnnotations();
}
get redactionLog() {
get file(): File {
return this.file$.value;
}
set file(file: File) {
this.file$.next(file);
}
get redactionLog(): IRedactionLog {
return this._redactionLog;
}
@ -49,7 +59,7 @@ export class FileDataModel {
const previousAnnotations = this.allAnnotations || [];
this.allAnnotations = entries
.map(entry => AnnotationWrapper.fromData(entry))
.filter(ann => ann.manual || !this.file.excludedPages.includes(ann.pageNumber));
.filter(ann => ann.manual || !this._file.excludedPages.includes(ann.pageNumber));
if (!this._areDevFeaturesEnabled) {
this.allAnnotations = this.allAnnotations.filter(annotation => !annotation.isFalsePositive);
@ -123,7 +133,7 @@ export class FileDataModel {
}
private _isChangeLogEntry(redactionLogEntry: IRedactionLogEntry, wrapper: RedactionLogEntryWrapper) {
if (this.file.numberOfAnalyses > 1) {
if (this._file.numberOfAnalyses > 1) {
const viableChanges = redactionLogEntry.changes.filter(c => c.analysisNumber > 1);
viableChanges.sort((a, b) => moment(a.dateTime).valueOf() - moment(b.dateTime).valueOf());

View File

@ -27,7 +27,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, LoadingService } from '@iqser/common-ui';
import { AutoUnsubscribe, ConfirmationDialogInput, LoadingService, shareDistinctLast } from '@iqser/common-ui';
import { DossiersDialogService } from '../../../../services/dossiers-dialog.service';
import { loadCompareDocumentWrapper } from '../../../../utils/compare-mode.utils';
import { PdfViewerUtils } from '../../../../utils/pdf-viewer.utils';
@ -125,6 +125,8 @@ export class PdfViewerComponent extends AutoUnsubscribe implements OnInit, OnCha
.pipe(
filter(fileData => !!fileData),
switchMap(fileData => fileData.blob$),
// Skip document reload if file content hasn't changed
shareDistinctLast(),
tap(() => this._loadDocument()),
)
.subscribe();

View File

@ -1,5 +1,5 @@
<ng-container *ngIf="dossier$ | async as dossier">
<ng-container *ngIf="file$ | async as file">
<ng-container *ngIf="fileData?.file$ | async as file">
<section [class.fullscreen]="fullScreen">
<div class="page-header">
<div class="flex flex-1">

View File

@ -14,7 +14,6 @@ import {
OnDetach,
processFilters,
shareDistinctLast,
shareLast,
} from '@iqser/common-ui';
import { MatDialogRef, MatDialogState } from '@angular/material/dialog';
import { ManualRedactionEntryWrapper } from '@models/file/manual-redaction-entry.wrapper';
@ -24,7 +23,7 @@ import { AnnotationDrawService } from './services/annotation-draw.service';
import { AnnotationProcessingService } from '../../services/annotation-processing.service';
import { Dossier, File, ViewMode } from '@red/domain';
import { PermissionsService } from '@services/permissions.service';
import { combineLatest, from, Observable, timer } from 'rxjs';
import { combineLatest, Observable, timer } from 'rxjs';
import { UserPreferenceService } from '@services/user-preference.service';
import { PdfViewerDataService } from '../../services/pdf-viewer-data.service';
import { download } from '@utils/file-download-utils';
@ -36,7 +35,7 @@ import { handleFilterDelta } from '@utils/filter-utils';
import { FilesService } from '@services/entity-services/files.service';
import { DossiersService } from '@services/entity-services/dossiers.service';
import { FileManagementService } from '@services/entity-services/file-management.service';
import { filter, map, switchMap, tap } from 'rxjs/operators';
import { map, 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';
@ -85,7 +84,6 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
readonly dossierId: string;
readonly canPerformAnnotationActions$: Observable<boolean>;
readonly dossier$: Observable<Dossier>;
readonly file$: Observable<File>;
readonly fileId: string;
ready = false;
private _instance: WebViewerInstance;
@ -129,12 +127,6 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
this.dossierId = _activatedRoute.snapshot.paramMap.get('dossierId');
this.dossier$ = _dossiersService.getEntityChanged$(this.dossierId);
this.fileId = _activatedRoute.snapshot.paramMap.get('fileId');
this.file$ = _filesMapService.watch$(this.dossierId, this.fileId).pipe(
// Filter reanalysed$ events to prevent multiple reloads
filter(file => !this._fileData || file.lastProcessed === this._fileData.file.lastProcessed),
switchMap(file => from(this._reloadFile(file)).pipe(map(() => file))),
shareLast(),
);
this.canPerformAnnotationActions$ = this._canPerformAnnotationActions$;
document.documentElement.addEventListener('fullscreenchange', () => {
@ -145,30 +137,34 @@ 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 {
return this._instance;
}
get fileData(): FileDataModel {
return this._stateService.fileData;
}
private get _canPerformAnnotationActions$() {
return combineLatest([this.file$, this.viewModeService.viewMode$, this.viewModeService.compareMode$]).pipe(
return combineLatest([
this._stateService.fileData$.pipe(switchMap(fileData => fileData.file$)),
this.viewModeService.viewMode$,
this.viewModeService.compareMode$,
]).pipe(
map(([file, viewMode]) => this.permissionsService.canPerformAnnotationActions(file) && viewMode === 'STANDARD'),
shareDistinctLast(),
);
}
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'));
@ -263,15 +259,13 @@ 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}`);
console.log();
}
handleAnnotationSelected(annotationIds: string[]) {
// TODO: use includes() here
this.selectedAnnotations = annotationIds
.map(id => this.visibleAnnotations.find(annotationWrapper => annotationWrapper.id === id))
.filter(ann => ann !== undefined);
@ -473,23 +467,15 @@ 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);
// file already loaded at least once
if (previousFile) {
// If it has been OCRd, we need to wait for it to load into the viewer
if (previousFile.lastOCRTime !== this._fileData?.file?.lastOCRTime) {
if (previousFile.lastOCRTime !== this.fileData?.file?.lastOCRTime) {
return;
}
// excluded pages or document exclusion has changed
const fileHasBeenExcludedOrIncluded = previousFile.excluded !== this._fileData.file.excluded;
const excludedPagesHaveChanged =
JSON.stringify(previousFile.excludedPages) !== JSON.stringify(this._fileData.file.excludedPages);
if (fileHasBeenExcludedOrIncluded || excludedPagesHaveChanged) {
this._deleteAnnotations();
await this._cleanupAndRedrawAnnotations();
}
}
}
@ -548,20 +534,28 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
}
}
private async _fileUpdated(file: File): Promise<File> {
if (!this.fileData || file.lastProcessed === this.fileData.file.lastProcessed) {
await this._reloadFile(file);
} else {
// File reanalysed
const previousAnnotations = this.visibleAnnotations;
await this._loadFileData(file);
await this._reloadAnnotations(previousAnnotations);
}
return file;
}
private _subscribeToFileUpdates(): void {
this.addActiveScreenSubscription = this._filesMapService
.watch$(this.dossierId, this.fileId)
.pipe(switchMap(file => this._fileUpdated(file)))
.subscribe();
this.addActiveScreenSubscription = timer(0, 5000)
.pipe(switchMap(() => this._filesService.reload(this.dossierId, this.fileId)))
.subscribe();
this.addActiveScreenSubscription = this._filesMapService.fileReanalysed$
.pipe(filter(file => file.fileId === this.fileId))
.subscribe(async file => {
if (file.lastProcessed !== this._fileData?.file.lastProcessed) {
const previousAnnotations = this.visibleAnnotations;
await this._loadFileData(file);
await this._reloadAnnotations(previousAnnotations);
}
this._loadingService.stop();
});
this.addActiveScreenSubscription = this._dossiersService
.getEntityDeleted$(this.dossierId)
@ -616,16 +610,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._pdfViewerDataService.loadRedactionLogFor(this.dossierId, this.fileId).toPromise();
this.fileData.redactionLog = await this._pdfViewerDataService.loadRedactionLogFor(this.dossierId, this.fileId).toPromise();
this._deleteAnnotations(currentPageAnnotations);
await this._cleanupAndRedrawAnnotations(currentPageAnnotations, annotation => annotation.pageNumber === page);

View File

@ -5,7 +5,6 @@ import { filter, startWith } from 'rxjs/operators';
@Injectable({ providedIn: 'root' })
export class FilesMapService {
readonly fileReanalysed$ = new Subject<File>();
private readonly _entityChanged$ = new Subject<File>();
private readonly _entityDeleted$ = new Subject<File>();
private readonly _map = new Map<string, BehaviorSubject<File[]>>();
@ -38,7 +37,6 @@ export class FilesMapService {
return entities.forEach(entity => this._entityChanged$.next(entity));
}
const reanalysedEntities = [];
const changedEntities = [];
const deletedEntities = this.get(key).filter(oldEntity => !entities.find(newEntity => newEntity.id === oldEntity.id));
@ -46,10 +44,6 @@ export class FilesMapService {
const newEntities = entities.map(newEntity => {
const oldEntity = this.get(key, newEntity.id);
if (oldEntity?.lastProcessed !== newEntity.lastProcessed) {
reanalysedEntities.push(newEntity);
}
if (newEntity.isEqual(oldEntity)) {
return oldEntity;
}
@ -62,10 +56,6 @@ export class FilesMapService {
// Emit observables only after entities have been updated
for (const file of reanalysedEntities) {
this.fileReanalysed$.next(file);
}
for (const file of changedEntities) {
this._entityChanged$.next(file);
}