diff --git a/apps/red-ui/src/app/models/file/file-data.model.ts b/apps/red-ui/src/app/models/file/file-data.model.ts index 8a3d50aa3..8c5f7e34b 100644 --- a/apps/red-ui/src/app/models/file/file-data.model.ts +++ b/apps/red-ui/src/app/models/file/file-data.model.ts @@ -3,34 +3,34 @@ import { AnnotationWrapper } from './annotation.wrapper'; import { RedactionLogEntryWrapper } from './redaction-log-entry.wrapper'; import * as moment from 'moment'; -export class AnnotationData { - visibleAnnotations: AnnotationWrapper[]; - allAnnotations: AnnotationWrapper[]; -} - export class FileDataModel { static readonly DELTA_VIEW_TIME = 10 * 60 * 1000; // 10 minutes; hasChangeLog: boolean; + allAnnotations: AnnotationWrapper[]; - constructor(public file: File, public fileData: Blob, public redactionLog: IRedactionLog, public viewedPages?: IViewedPage[]) {} + constructor( + public file: File, + public fileData: Blob, + private _redactionLog: IRedactionLog, + public viewedPages?: IViewedPage[], + private _dictionaryData?: { [p: string]: Dictionary }, + private _areDevFeaturesEnabled?: boolean, + ) { + this._buildAllAnnotations(); + } - getAnnotations( - dictionaryData: { [p: string]: Dictionary }, - currentUser: User, - viewMode: ViewMode, - areDevFeaturesEnabled: boolean, - ): AnnotationData { - const entries: RedactionLogEntryWrapper[] = this._convertData(dictionaryData); - let allAnnotations = entries - .map(entry => AnnotationWrapper.fromData(entry)) - .filter(ann => ann.manual || !this.file.excludedPages.includes(ann.pageNumber)); + set redactionLog(redactionLog: IRedactionLog) { + this._redactionLog = redactionLog; + this._buildAllAnnotations(); + } - if (!areDevFeaturesEnabled) { - allAnnotations = allAnnotations.filter(annotation => !annotation.isFalsePositive); - } + get redactionLog() { + return this._redactionLog; + } - const visibleAnnotations = allAnnotations.filter(annotation => { + getVisibleAnnotations(viewMode: ViewMode) { + return this.allAnnotations.filter(annotation => { if (viewMode === 'STANDARD') { return !annotation.isChangeLogRemoved; } else if (viewMode === 'DELTA') { @@ -39,14 +39,33 @@ export class FileDataModel { return annotation.isRedacted; } }); - - return { - visibleAnnotations: visibleAnnotations, - allAnnotations: allAnnotations, - }; } - private _convertData(dictionaryData: { [p: string]: Dictionary }): RedactionLogEntryWrapper[] { + private _buildAllAnnotations() { + const entries: RedactionLogEntryWrapper[] = this._convertData(); + + const previousAnnotations = this.allAnnotations || []; + this.allAnnotations = entries + .map(entry => AnnotationWrapper.fromData(entry)) + .filter(ann => ann.manual || !this.file.excludedPages.includes(ann.pageNumber)); + + if (!this._areDevFeaturesEnabled) { + this.allAnnotations = this.allAnnotations.filter(annotation => !annotation.isFalsePositive); + } + + this._setHiddenPropertyToNewAnnotations(this.allAnnotations, previousAnnotations); + } + + private _setHiddenPropertyToNewAnnotations(newAnnotations: AnnotationWrapper[], oldAnnotations: AnnotationWrapper[]) { + newAnnotations.forEach(newAnnotation => { + const oldAnnotation = oldAnnotations.find(a => a.annotationId === newAnnotation.annotationId); + if (oldAnnotation) { + newAnnotation.hidden = oldAnnotation.hidden; + } + }); + } + + private _convertData(): RedactionLogEntryWrapper[] { let result: RedactionLogEntryWrapper[] = []; const reasonAnnotationIds: { [key: string]: RedactionLogEntryWrapper[] } = {}; @@ -55,7 +74,7 @@ export class FileDataModel { const redactionLogEntryWrapper: RedactionLogEntryWrapper = {}; Object.assign(redactionLogEntryWrapper, redactionLogEntry); redactionLogEntryWrapper.type = redactionLogEntry.type; - redactionLogEntryWrapper.hintDictionary = dictionaryData[redactionLogEntry.type].hint; + redactionLogEntryWrapper.hintDictionary = this._dictionaryData[redactionLogEntry.type].hint; this._isChangeLogEntry(redactionLogEntry, redactionLogEntryWrapper); diff --git a/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/components/annotation-actions/annotation-actions.component.html b/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/components/annotation-actions/annotation-actions.component.html index 1dc525fb2..c3d7d4599 100644 --- a/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/components/annotation-actions/annotation-actions.component.html +++ b/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/components/annotation-actions/annotation-actions.component.html @@ -124,7 +124,7 @@ > - + { - const ocrAnnotationIds = this.annotationData.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')); @@ -251,18 +243,7 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni console.log(`[REDACTION] Delete previous annotations time: ${new Date().getTime() - startTime} ms`); } const processStartTime = new Date().getTime(); - const dossier = this._dossiersService.find(this.dossierId); - const newAnnotationsData = this.fileData.getAnnotations( - this._appStateService.dictionaryData[dossier.dossierTemplateId], - this._userService.currentUser, - this.viewModeService.viewMode, - this.userPreferenceService.areDevFeaturesEnabled, - ); - if (this.annotationData) { - this._setHiddenPropertyToNewAnnotations(newAnnotationsData.visibleAnnotations, this.annotationData.visibleAnnotations); - this._setHiddenPropertyToNewAnnotations(newAnnotationsData.allAnnotations, this.annotationData.allAnnotations); - } - this.annotationData = newAnnotationsData; + const annotationFilters = this._annotationProcessingService.getAnnotationFilter(this.annotations); const primaryFilters = this._filterService.getGroup('primaryFilters')?.filters; this._filterService.addFilterGroup({ @@ -324,7 +305,7 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni response.manualRedactionEntryWrapper.rectId, ); this._instance.Core.annotationManager.deleteAnnotation(annotation); - // await this._filesService.reload(this.dossierId, this.fileId).toPromise(); + // await this._filesService.reload(this.dossierId, this.fileId).toPromise(); const distinctPages = manualRedactionEntryWrapper.manualRedactionEntry.positions .map(p => p.page) .filter((item, pos, self) => self.indexOf(item) === pos); @@ -431,10 +412,6 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni await this._reloadAnnotationsForPage(annotation?.pageNumber || this.activeViewerPage); } - ocredFile(): void { - this._reloadFileOnReanalysis = true; - } - closeFullScreen() { if (!!document.fullscreenElement && document.exitFullscreen) { document.exitFullscreen().then(); @@ -480,19 +457,10 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni } private async _reloadFile(file: File): Promise { - await this._loadFileData(file, true); + await this._loadFileData(file); await this._stampPDF(); } - private _setHiddenPropertyToNewAnnotations(newAnnotations: AnnotationWrapper[], oldAnnotations: AnnotationWrapper[]) { - newAnnotations.forEach(newAnnotation => { - const oldAnnotation = oldAnnotations.find(a => a.annotationId === newAnnotation.annotationId); - if (oldAnnotation) { - newAnnotation.hidden = oldAnnotation.hidden; - } - }); - } - private async _stampPDF() { if (!this._instance) { return; @@ -554,36 +522,26 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni this.addSubscription = this._filesMapService.fileReanalysed$ .pipe(filter(file => file.fileId === this.fileId)) .subscribe(async file => { - await this._loadFileData(file, !this._reloadFileOnReanalysis); - this._reloadFileOnReanalysis = false; - await this._reloadAnnotations(); + if (file.lastProcessed !== this.fileData?.file.lastProcessed) { + await this._loadFileData(file); + await this._reloadAnnotations(); + } this._loadingService.stop(); }); } - private async _loadFileData(file: File, performUpdate = false): Promise { + private async _loadFileData(file: File): Promise { if (!file || file.isError) { return this._router.navigate([this._dossiersService.find(this.dossierId).routerLink]); } - const fileData = await this._fileDownloadService.loadDataFor(file).toPromise(); + const fileData = await this._fileDownloadService.loadDataFor(file, this.fileData).toPromise(); if (file.isPending) { return; } - if (performUpdate && !!this.fileData) { - this.fileData.redactionLog = fileData.redactionLog; - this.fileData.viewedPages = fileData.viewedPages; - const excludedOrIncludedPages = new Set(diff(this.fileData.file.excludedPages, file.excludedPages)); - const currentPageAnnotations = this.annotations.filter(a => excludedOrIncludedPages.has(a.pageNumber)); - this.fileData.file = file; - if (excludedOrIncludedPages?.size) { - await this._cleanupAndRedrawAnnotations(currentPageAnnotations, a => excludedOrIncludedPages.has(a.pageNumber)); - } - } else { - this.fileData = fileData; - } + this.fileData = fileData; } @Debounce(0) @@ -600,6 +558,7 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni private async _reloadAnnotationsForPage(page: number) { const currentPageAnnotations = this.annotations.filter(a => a.pageNumber === page); + this.fileData.file = await this._filesService.reload(this.dossierId, this.fileId).toPromise(); this.fileData.redactionLog = await this._fileDownloadService.loadRedactionLogFor(this.dossierId, this.fileId).toPromise(); await this._cleanupAndRedrawAnnotations(currentPageAnnotations, annotation => annotation.pageNumber === page); @@ -625,7 +584,7 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni } } - private _redrawAnnotations(annotations = this.annotationData.allAnnotations) { + private _redrawAnnotations(annotations = this.fileData.allAnnotations) { return this._annotationDrawService.drawAnnotations( this._instance, annotations, diff --git a/apps/red-ui/src/app/modules/dossier/services/annotation-actions.service.ts b/apps/red-ui/src/app/modules/dossier/services/annotation-actions.service.ts index 170566f58..003b14a86 100644 --- a/apps/red-ui/src/app/modules/dossier/services/annotation-actions.service.ts +++ b/apps/red-ui/src/app/modules/dossier/services/annotation-actions.service.ts @@ -113,7 +113,7 @@ export class AnnotationActionsService { ); } - suggestRemoveAnnotation( + removeOrSuggestRemoveAnnotation( $event: MouseEvent, annotations: AnnotationWrapper[], file: File, @@ -290,7 +290,7 @@ export class AnnotationActionsService { title: this._translateService.instant('annotation-actions.remove-annotation.remove-from-dict'), onClick: () => { this._ngZone.run(() => { - this.suggestRemoveAnnotation(null, annotations, file, true, annotationsChanged); + this.removeOrSuggestRemoveAnnotation(null, annotations, file, true, annotationsChanged); }); }, }); @@ -405,7 +405,7 @@ export class AnnotationActionsService { title: this._translateService.instant('annotation-actions.remove-annotation.only-here'), onClick: () => { this._ngZone.run(() => { - this.suggestRemoveAnnotation(null, annotations, file, false, annotationsChanged); + this.removeOrSuggestRemoveAnnotation(null, annotations, file, false, annotationsChanged); }); }, }); @@ -441,7 +441,7 @@ export class AnnotationActionsService { viewer: WebViewerInstance, file: File, annotationWrapper: AnnotationWrapper, - annotationsChanged: EventEmitter, + annotationsChanged?: EventEmitter, ) { const data = { dossier: this._dossier(file) }; this._dialogService.openDialog('resizeAnnotation', $event, data, async (result: { comment: string }) => { diff --git a/apps/red-ui/src/app/modules/dossier/services/pdf-viewer-data.service.ts b/apps/red-ui/src/app/modules/dossier/services/pdf-viewer-data.service.ts index fb20bd16c..6625f138f 100644 --- a/apps/red-ui/src/app/modules/dossier/services/pdf-viewer-data.service.ts +++ b/apps/red-ui/src/app/modules/dossier/services/pdf-viewer-data.service.ts @@ -7,14 +7,20 @@ import { File } from '@red/domain'; import { FileManagementService } from '@services/entity-services/file-management.service'; import { RedactionLogService } from './redaction-log.service'; import { ViewedPagesService } from '@services/entity-services/viewed-pages.service'; +import { AppStateService } from '../../../state/app-state.service'; +import { DossiersService } from '../../../services/entity-services/dossiers.service'; +import { UserPreferenceService } from '../../../services/user-preference.service'; @Injectable() export class PdfViewerDataService { constructor( + private readonly _dossiersService: DossiersService, private readonly _permissionsService: PermissionsService, private readonly _fileManagementService: FileManagementService, private readonly _redactionLogService: RedactionLogService, private readonly _viewedPagesService: ViewedPagesService, + private readonly _appStateService: AppStateService, + private readonly _userPreferenceService: UserPreferenceService, ) {} loadRedactionLogFor(dossierId: string, fileId: string) { @@ -24,12 +30,24 @@ export class PdfViewerDataService { ); } - loadDataFor(file: File): Observable { - const file$ = this.downloadOriginalFile(file); + loadDataFor(file: File, fileData?: FileDataModel): Observable { + const file$ = fileData?.file.cacheIdentifier === file.cacheIdentifier ? of(fileData.fileData) : this.downloadOriginalFile(file); const reactionLog$ = this.loadRedactionLogFor(file.dossierId, file.fileId); const viewedPages$ = this.getViewedPagesFor(file); - return forkJoin([file$, reactionLog$, viewedPages$]).pipe(map(data => new FileDataModel(file, ...data))); + const dossier = this._dossiersService.find(file.dossierId); + + return forkJoin([file$, reactionLog$, viewedPages$]).pipe( + map( + data => + new FileDataModel( + file, + ...data, + this._appStateService.dictionaryData[dossier.dossierTemplateId], + this._userPreferenceService.areDevFeaturesEnabled, + ), + ), + ); } getViewedPagesFor(file: File) { diff --git a/apps/red-ui/src/app/modules/dossier/shared/components/file-actions/file-actions.component.ts b/apps/red-ui/src/app/modules/dossier/shared/components/file-actions/file-actions.component.ts index 964825091..150f44c55 100644 --- a/apps/red-ui/src/app/modules/dossier/shared/components/file-actions/file-actions.component.ts +++ b/apps/red-ui/src/app/modules/dossier/shared/components/file-actions/file-actions.component.ts @@ -51,7 +51,6 @@ export class FileActionsComponent extends AutoUnsubscribe implements OnDestroy, @Input() file: File; @Input() type: 'file-preview' | 'dossier-overview-list' | 'dossier-overview-workflow'; @Input() maxWidth: number; - @Output() readonly ocredFile = new EventEmitter(); toggleTooltip?: string; assignTooltip?: string; @@ -308,7 +307,6 @@ export class FileActionsComponent extends AutoUnsubscribe implements OnDestroy, $event.stopPropagation(); this._loadingService.start(); await this._reanalysisService.ocrFiles([this.file.fileId], this.file.dossierId).toPromise(); - this.ocredFile.emit(); this._loadingService.stop(); } diff --git a/apps/red-ui/src/app/services/entity-services/platform-search.service.ts b/apps/red-ui/src/app/services/entity-services/platform-search.service.ts index 752b6f07d..000d563c1 100644 --- a/apps/red-ui/src/app/services/entity-services/platform-search.service.ts +++ b/apps/red-ui/src/app/services/entity-services/platform-search.service.ts @@ -15,7 +15,7 @@ export class PlatformSearchService extends GenericService { private readonly _dossiersService: DossiersService, private readonly _filesMapService: FilesMapService, ) { - super(_injector, 'search'); + super(_injector, 'search-v2'); } search({ dossierIds, query }: ISearchInput): Observable { @@ -29,7 +29,7 @@ export class PlatformSearchService extends GenericService { const body: ISearchRequest = { dossierIds, queryString: query ?? '', - page: 1, + page: 0, returnSections: false, pageSize: 300, }; diff --git a/libs/common-ui b/libs/common-ui index 6527ccd30..3f1c419e0 160000 --- a/libs/common-ui +++ b/libs/common-ui @@ -1 +1 @@ -Subproject commit 6527ccd3077bfcb4bedf729bf09edc5bd1449502 +Subproject commit 3f1c419e0acdc92514b615611bdaa29e000a0b32 diff --git a/libs/red-domain/src/lib/file-attributes/file-attributes-config.ts b/libs/red-domain/src/lib/file-attributes/file-attributes-config.ts index e3363aebe..7aa96c500 100644 --- a/libs/red-domain/src/lib/file-attributes/file-attributes-config.ts +++ b/libs/red-domain/src/lib/file-attributes/file-attributes-config.ts @@ -2,6 +2,8 @@ import { IFileAttributeConfig } from './file-attribute-config'; export interface IFileAttributesConfig { delimiter?: string; - fileAttributeConfigs?: IFileAttributeConfig[]; + encoding?: string; filenameMappingColumnHeaderName?: string; + + fileAttributeConfigs?: IFileAttributeConfig[]; } diff --git a/libs/red-domain/src/lib/files/file.model.ts b/libs/red-domain/src/lib/files/file.model.ts index 81148059e..aa3f705da 100644 --- a/libs/red-domain/src/lib/files/file.model.ts +++ b/libs/red-domain/src/lib/files/file.model.ts @@ -93,9 +93,7 @@ export class File extends Entity implements IFile { this.hasSuggestions = !!file.hasSuggestions; this.statusSort = StatusSorter[this.workflowStatus]; - if (this.lastUpdated && this.lastOCRTime) { - this.cacheIdentifier = btoa((this.lastUploaded ?? '') + this.lastOCRTime); - } + this.cacheIdentifier = btoa((this.lastUploaded ?? '') + (this.lastOCRTime ?? '')); this.hintsOnly = this.hasHints && !this.hasRedactions; this.hasNone = !this.hasRedactions && !this.hasHints && !this.hasSuggestions; this.isPending = this.processingStatus === ProcessingFileStatuses.UNPROCESSED;