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 6f508752a..7b73d7228 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 @@ -19,7 +19,6 @@ export class FileDataModel { allAnnotations: AnnotationWrapper[]; readonly hasChangeLog$ = new BehaviorSubject(false); readonly blob$ = new BehaviorSubject(undefined); - readonly file$ = new BehaviorSubject(undefined); constructor( private readonly _file: File, @@ -29,19 +28,10 @@ export class FileDataModel { private _dictionaryData?: { [p: string]: Dictionary }, private _areDevFeaturesEnabled?: boolean, ) { - this.file$.next(_file); this.blob$.next(_blob); this._buildAllAnnotations(); } - get file(): File { - return this.file$.value; - } - - set file(file: File) { - this.file$.next(file); - } - get redactionLog(): IRedactionLog { return this._redactionLog; } diff --git a/apps/red-ui/src/app/modules/dossier/dialogs/manual-redaction-dialog/manual-annotation-dialog.component.html b/apps/red-ui/src/app/modules/dossier/dialogs/manual-redaction-dialog/manual-annotation-dialog.component.html index 9ef9e3101..fee0300fa 100644 --- a/apps/red-ui/src/app/modules/dossier/dialogs/manual-redaction-dialog/manual-annotation-dialog.component.html +++ b/apps/red-ui/src/app/modules/dossier/dialogs/manual-redaction-dialog/manual-annotation-dialog.component.html @@ -69,7 +69,7 @@
- +
@@ -80,5 +80,5 @@ - + diff --git a/apps/red-ui/src/app/modules/dossier/dialogs/manual-redaction-dialog/manual-annotation-dialog.component.ts b/apps/red-ui/src/app/modules/dossier/dialogs/manual-redaction-dialog/manual-annotation-dialog.component.ts index cd7c01318..1b38a8599 100644 --- a/apps/red-ui/src/app/modules/dossier/dialogs/manual-redaction-dialog/manual-annotation-dialog.component.ts +++ b/apps/red-ui/src/app/modules/dossier/dialogs/manual-redaction-dialog/manual-annotation-dialog.component.ts @@ -4,10 +4,9 @@ import { AppStateService } from '@state/app-state.service'; import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { ManualRedactionEntryWrapper } from '@models/file/manual-redaction-entry.wrapper'; import { ManualAnnotationService } from '../../services/manual-annotation.service'; -import { ManualAnnotationResponse } from '@models/file/manual-annotation-response'; import { PermissionsService } from '@services/permissions.service'; import { JustificationsService } from '@services/entity-services/justifications.service'; -import { Dictionary, Dossier, File, IAddRedactionRequest, IManualAddResponse } from '@red/domain'; +import { Dictionary, Dossier, IAddRedactionRequest } from '@red/domain'; import { DossiersService } from '@services/entity-services/dossiers.service'; import { DictionaryService } from '@shared/services/dictionary.service'; import { BaseDialogComponent } from '@iqser/common-ui'; @@ -20,7 +19,6 @@ export interface LegalBasisOption { } @Component({ - selector: 'redaction-manual-annotation-dialog', templateUrl: './manual-annotation-dialog.component.html', styleUrls: ['./manual-annotation-dialog.component.scss'], }) @@ -44,10 +42,10 @@ export class ManualAnnotationDialogComponent extends BaseDialogComponent impleme private readonly _dictionaryService: DictionaryService, protected readonly _injector: Injector, protected readonly _dialogRef: MatDialogRef, - @Inject(MAT_DIALOG_DATA) public data: { manualRedactionEntryWrapper: ManualRedactionEntryWrapper; file: File }, + @Inject(MAT_DIALOG_DATA) public data: { manualRedactionEntryWrapper: ManualRedactionEntryWrapper; dossierId: string }, ) { super(_injector, _dialogRef); - this._dossier = this._dossiersService.find(this.data.file.dossierId); + this._dossier = this._dossiersService.find(this.data.dossierId); this.isDocumentAdmin = this._permissionsService.isApprover(this._dossier); this.isFalsePositiveRequest = this.data.manualRedactionEntryWrapper.type === 'FALSE_POSITIVE'; @@ -73,6 +71,10 @@ export class ManualAnnotationDialogComponent extends BaseDialogComponent impleme return this.form.invalid; } + get commentIsMandatory() { + return !this.isDocumentAdmin && !this.isDictionaryRequest; + } + async ngOnInit() { super.ngOnInit(); this.possibleDictionaries = await this._appStateService.getDictionariesOptions( @@ -91,11 +93,7 @@ export class ManualAnnotationDialogComponent extends BaseDialogComponent impleme save() { this._enhanceManualRedaction(this.data.manualRedactionEntryWrapper.manualRedactionEntry); - this._manualAnnotationService.addAnnotation(this.data.manualRedactionEntryWrapper.manualRedactionEntry, this.data.file).subscribe( - (response: IManualAddResponse) => - this._dialogRef.close(new ManualAnnotationResponse(this.data.manualRedactionEntryWrapper, response)), - () => this._dialogRef.close(), - ); + this._dialogRef.close(this.data.manualRedactionEntryWrapper); } format(value: string) { @@ -118,10 +116,6 @@ export class ManualAnnotationDialogComponent extends BaseDialogComponent impleme }); } - get commentIsMandatory() { - return !this.isDocumentAdmin && !this.isDictionaryRequest; - } - private _enhanceManualRedaction(addRedactionRequest: IAddRedactionRequest) { const legalOption: LegalBasisOption = this.form.get('reason').value; addRedactionRequest.type = this.form.get('dictionary').value; 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 90a83df7a..6502158f1 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 @@ -32,7 +32,7 @@ > +
- {{ annotationReferences.length }} - {{ (annotationReferences.length === 1 ? 'references.singular' : 'references.plural') | translate }} + {{ references.length }} + {{ (references.length === 1 ? 'references.singular' : 'references.plural') | translate }}
-
+
@@ -18,7 +18,7 @@
diff --git a/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/components/annotation-references-list/annotation-references-list.component.ts b/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/components/annotation-references-list/annotation-references-list.component.ts index b759f0d94..6657f3c96 100644 --- a/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/components/annotation-references-list/annotation-references-list.component.ts +++ b/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/components/annotation-references-list/annotation-references-list.component.ts @@ -1,8 +1,10 @@ -import { ChangeDetectionStrategy, Component, EventEmitter, Input, OnChanges, Output } from '@angular/core'; +import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from '@angular/core'; import { AnnotationWrapper } from '@models/file/annotation.wrapper'; import { AnnotationReferencesService } from '../../services/annotation-references.service'; import { File } from '@red/domain'; -import { FileDataModel } from '@models/file/file-data.model'; +import { FilePreviewStateService } from '../../services/file-preview-state.service'; +import { combineLatest, Observable } from 'rxjs'; +import { filter, map } from 'rxjs/operators'; @Component({ selector: 'redaction-annotation-references-list', @@ -10,18 +12,23 @@ import { FileDataModel } from '@models/file/file-data.model'; styleUrls: ['./annotation-references-list.component.scss'], changeDetection: ChangeDetectionStrategy.OnPush, }) -export class AnnotationReferencesListComponent implements OnChanges { - @Input() annotation: AnnotationWrapper; +export class AnnotationReferencesListComponent { @Input() file: File; - @Input() fileData: FileDataModel; @Input() selectedAnnotations: AnnotationWrapper[]; @Output() readonly referenceClicked = new EventEmitter(); - annotationReferences: AnnotationWrapper[]; + references$ = this._annotationReferences; - constructor(readonly annotationReferencesService: AnnotationReferencesService) {} + constructor( + readonly annotationReferencesService: AnnotationReferencesService, + private readonly _filePreviewStateService: FilePreviewStateService, + ) {} - ngOnChanges(): void { - this.annotationReferences = this.fileData.allAnnotations.filter(a => this.annotation.reference.includes(a.annotationId)); + private get _annotationReferences(): Observable { + const combination = combineLatest([this.annotationReferencesService.annotation$, this._filePreviewStateService.fileData$]); + return combination.pipe( + filter(([annotation]) => !!annotation), + map(([{ reference }, fileData]) => fileData.allAnnotations.filter(a => reference.includes(a.annotationId))), + ); } isSelected(annotationId: string): boolean { diff --git a/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/components/annotations-list/annotations-list.component.html b/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/components/annotations-list/annotations-list.component.html index d45e29274..0ad1cdb71 100644 --- a/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/components/annotations-list/annotations-list.component.html +++ b/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/components/annotations-list/annotations-list.component.html @@ -44,11 +44,9 @@
- + diff --git a/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/components/annotations-list/annotations-list.component.ts b/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/components/annotations-list/annotations-list.component.ts index bffbe67f4..7b182a364 100644 --- a/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/components/annotations-list/annotations-list.component.ts +++ b/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/components/annotations-list/annotations-list.component.ts @@ -4,7 +4,6 @@ import { FilterService, HelpModeService, IqserEventTarget } from '@iqser/common- import { File } from '@red/domain'; import { MultiSelectService } from '../../services/multi-select.service'; import { AnnotationReferencesService } from '../../services/annotation-references.service'; -import { FileDataModel } from '../../../../../../models/file/file-data.model'; @Component({ selector: 'redaction-annotations-list', @@ -14,7 +13,6 @@ import { FileDataModel } from '../../../../../../models/file/file-data.model'; }) export class AnnotationsListComponent implements OnChanges { @Input() file: File; - @Input() fileData: FileDataModel; @Input() annotations: AnnotationWrapper[]; @Input() selectedAnnotations: AnnotationWrapper[]; @Input() annotationActionsTemplate: TemplateRef; diff --git a/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/components/document-info/document-info.component.html b/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/components/document-info/document-info.component.html index 6d6f693e2..fc1556029 100644 --- a/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/components/document-info/document-info.component.html +++ b/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/components/document-info/document-info.component.html @@ -1,54 +1,58 @@ -
-
- + +
+
+ - -
-
- -
-
-
-
{{ attr.label }}:
-
{{ attr.value || '-' }}
+
-
-
- - {{ 'file-preview.tabs.document-info.details.dossier' | translate: { dossierName: dossier.dossierName } }} +
+
+
+
{{ attr.label }}:
+
{{ attr.value || '-' }}
+
-
- - {{ 'file-preview.tabs.document-info.details.pages' | translate: { pages: file.numberOfPages } }} -
+
+
+ + {{ 'file-preview.tabs.document-info.details.dossier' | translate: { dossierName: dossier.dossierName } }} +
-
- - {{ 'file-preview.tabs.document-info.details.created-on' | translate: { date: file.added | date: 'mediumDate' } }} -
+
+ + {{ 'file-preview.tabs.document-info.details.pages' | translate: { pages: file.numberOfPages } }} +
-
- - {{ 'file-preview.tabs.document-info.details.due' | translate: { date: dossier.dueDate | date: 'mediumDate' } }} -
+
+ + {{ + 'file-preview.tabs.document-info.details.created-on' | translate: { date: file.added | date: 'mediumDate' } + }} +
-
- - {{ dossierTemplateName }} +
+ + {{ 'file-preview.tabs.document-info.details.due' | translate: { date: dossier.dueDate | date: 'mediumDate' } }} +
+ +
+ + {{ dossierTemplateName$ | async }} +
-
+ diff --git a/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/components/document-info/document-info.component.ts b/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/components/document-info/document-info.component.ts index 0ab68a6d7..323443061 100644 --- a/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/components/document-info/document-info.component.ts +++ b/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/components/document-info/document-info.component.ts @@ -1,44 +1,40 @@ -import { ChangeDetectionStrategy, Component, Input, OnInit } from '@angular/core'; -import { Dossier, File } from '@red/domain'; +import { ChangeDetectionStrategy, Component } from '@angular/core'; import { DossiersDialogService } from '../../../../services/dossiers-dialog.service'; -import { AutoUnsubscribe } from '@iqser/common-ui'; import { DossierTemplatesService } from '@services/entity-services/dossier-templates.service'; import { DocumentInfoService } from '../../services/document-info.service'; -import { Observable } from 'rxjs'; -import { PermissionsService } from '@services/permissions.service'; +import { combineLatest, Observable, switchMap } from 'rxjs'; +import { PermissionsService } from '../../../../../../services/permissions.service'; +import { FilePreviewStateService } from '../../services/file-preview-state.service'; +import { map } from 'rxjs/operators'; +import { File } from '@red/domain'; @Component({ - selector: 'redaction-document-info [file] [dossier]', + selector: 'redaction-document-info', templateUrl: './document-info.component.html', styleUrls: ['./document-info.component.scss'], changeDetection: ChangeDetectionStrategy.OnPush, }) -export class DocumentInfoComponent extends AutoUnsubscribe implements OnInit { - @Input() file: File; - @Input() dossier: Dossier; - - fileAttributes$: Observable<{ label: string; value: string }[]>; - dossierTemplateName: string; +export class DocumentInfoComponent { + readonly fileAttributes$: Observable<{ label: string; value: string }[]>; + readonly dossierTemplateName$: Observable; constructor( private readonly _dossierTemplatesService: DossierTemplatesService, private readonly _dialogService: DossiersDialogService, + readonly stateService: FilePreviewStateService, readonly permissionsService: PermissionsService, readonly documentInfoService: DocumentInfoService, ) { - super(); - } - - ngOnInit(): void { - this.dossierTemplateName = this._dossierTemplatesService.find(this.dossier.dossierTemplateId).name; - this.fileAttributes$ = this.documentInfoService.fileAttributes$( - this.file.fileId, - this.file.dossierId, - this.dossier.dossierTemplateId, + this.fileAttributes$ = combineLatest([this.stateService.file$, this.stateService.dossier$]).pipe( + switchMap(([file, dossier]) => this.documentInfoService.fileAttributes$(file.fileId, dossier.id, dossier.dossierTemplateId)), + ); + this.dossierTemplateName$ = this.stateService.dossier$.pipe( + switchMap(dossier => this._dossierTemplatesService.getEntityChanged$(dossier.dossierTemplateId)), + map(dossierTemplate => dossierTemplate.name), ); } - edit() { - this._dialogService.openDialog('documentInfo', null, this.file); + edit(file: File) { + this._dialogService.openDialog('documentInfo', null, file); } } diff --git a/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/components/file-workload/file-workload.component.html b/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/components/file-workload/file-workload.component.html index e87104673..71c41366d 100644 --- a/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/components/file-workload/file-workload.component.html +++ b/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/components/file-workload/file-workload.component.html @@ -203,7 +203,6 @@ [annotationActionsTemplate]="annotationActionsTemplate" [annotations]="(displayedAnnotations$ | async)?.get(activeViewerPage)" [canMultiSelect]="!isReadOnly" - [fileData]="fileData" [file]="file" [selectedAnnotations]="selectedAnnotations" iqserHelpMode="workload-annotations-list" diff --git a/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/components/file-workload/file-workload.component.ts b/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/components/file-workload/file-workload.component.ts index 2420dba07..584a25e84 100644 --- a/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/components/file-workload/file-workload.component.ts +++ b/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/components/file-workload/file-workload.component.ts @@ -33,7 +33,6 @@ import { ExcludedPagesService } from '../../services/excluded-pages.service'; import { MultiSelectService } from '../../services/multi-select.service'; import { DocumentInfoService } from '../../services/document-info.service'; import { SkippedService } from '../../services/skipped.service'; -import { FileDataModel } from '../../../../../../models/file/file-data.model'; const COMMAND_KEY_ARRAY = ['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown', 'Escape']; const ALL_HOTKEY_ARRAY = ['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown']; @@ -56,7 +55,6 @@ export class FileWorkloadComponent { @Input() file!: File; @Input() annotationActionsTemplate: TemplateRef; @Input() viewer: WebViewerInstance; - @Input() fileData: FileDataModel; @Output() readonly shouldDeselectAnnotationsOnPageChangeChange = new EventEmitter(); @Output() readonly selectAnnotations = new EventEmitter(); @Output() readonly deselectAnnotations = new EventEmitter(); diff --git a/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/components/pdf-viewer/pdf-viewer.component.html b/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/components/pdf-viewer/pdf-viewer.component.html index 251630448..97eab9b6a 100644 --- a/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/components/pdf-viewer/pdf-viewer.component.html +++ b/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/components/pdf-viewer/pdf-viewer.component.html @@ -1,5 +1,5 @@
-
+
diff --git a/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/components/pdf-viewer/pdf-viewer.component.ts b/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/components/pdf-viewer/pdf-viewer.component.ts index f0c0968a4..c489cb7ed 100644 --- a/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/components/pdf-viewer/pdf-viewer.component.ts +++ b/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/components/pdf-viewer/pdf-viewer.component.ts @@ -37,7 +37,7 @@ 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 { filter, switchMap, tap } from 'rxjs/operators'; +import { filter, switchMap, tap, withLatestFrom } from 'rxjs/operators'; import Tools = Core.Tools; import TextTool = Tools.TextTool; import Annotation = Core.Annotations.Annotation; @@ -64,7 +64,6 @@ const dataElements = { styleUrls: ['./pdf-viewer.component.scss'], }) export class PdfViewerComponent extends AutoUnsubscribe implements OnInit, OnChanges { - @Input() file: File; @Input() dossier: Dossier; @Input() canPerformActions = false; @Input() annotations: AnnotationWrapper[]; @@ -96,7 +95,7 @@ export class PdfViewerComponent extends AutoUnsubscribe implements OnInit, OnCha private readonly _annotationActionsService: AnnotationActionsService, private readonly _configService: ConfigService, private readonly _loadingService: LoadingService, - private readonly _stateService: FilePreviewStateService, + readonly stateService: FilePreviewStateService, readonly viewModeService: ViewModeService, readonly multiSelectService: MultiSelectService, ) { @@ -121,24 +120,25 @@ export class PdfViewerComponent extends AutoUnsubscribe implements OnInit, OnCha this._setReadyAndInitialState = this._setReadyAndInitialState.bind(this); await this._loadViewer(); - this.addActiveScreenSubscription = this._stateService.fileData$ + this.addActiveScreenSubscription = this.stateService.fileData$ .pipe( filter(fileData => !!fileData), switchMap(fileData => fileData.blob$), // Skip document reload if file content hasn't changed shareDistinctLast(), - tap(() => this._loadDocument()), + withLatestFrom(this.stateService.file$), + tap(([blob, file]) => this._loadDocument(blob, file)), ) .subscribe(); } - ngOnChanges(changes: SimpleChanges): void { + async ngOnChanges(changes: SimpleChanges): Promise { if (!this.instance) { return; } if (changes.canPerformActions) { - this._handleCustomActions(); + await this._handleCustomActions(); } } @@ -158,18 +158,19 @@ export class PdfViewerComponent extends AutoUnsubscribe implements OnInit, OnCha 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._stateService.fileData.blob$.value.arrayBuffer()); + const currentDocument = await pdfNet.PDFDoc.createFromBuffer(await this.stateService.fileData.blob$.value.arrayBuffer()); const loadCompareDocument = async () => { this._loadingService.start(); this.utils.ready = false; const mergedDocument = await pdfNet.PDFDoc.create(); + const file = await this.stateService.file; await loadCompareDocumentWrapper( currentDocument, compareDocument, mergedDocument, this.instance, - this.file, + file, () => { this.viewModeService.compareMode = true; }, @@ -211,10 +212,11 @@ export class PdfViewerComponent extends AutoUnsubscribe implements OnInit, OnCha 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._stateService.fileData.blob$.value.arrayBuffer()); - this.instance.UI.loadDocument(currentDocument, { - filename: this.file ? this.file.filename : 'document.pdf', - }); + const currentDocument = await pdfNet.PDFDoc.createFromBuffer(await this.stateService.fileData.blob$.value.arrayBuffer()); + + const filename = (await this.stateService.file).filename ?? 'document.pdf'; + this.instance.UI.loadDocument(currentDocument, { filename }); + this.instance.UI.disableElements([dataElements.CLOSE_COMPARE_BUTTON]); this.instance.UI.enableElements([dataElements.COMPARE_BUTTON]); this.utils.navigateToPage(1); @@ -239,7 +241,7 @@ export class PdfViewerComponent extends AutoUnsubscribe implements OnInit, OnCha this._setSelectionMode(); this._configureElements(); this.utils.disableHotkeys(); - this._configureTextPopup(); + await this._configureTextPopup(); this.annotationManager.addEventListener('annotationSelected', (annotations: Annotation[], action) => { this.annotationSelected.emit(this.annotationManager.getSelectedAnnotations().map(ann => ann.Id)); @@ -266,7 +268,7 @@ export class PdfViewerComponent extends AutoUnsubscribe implements OnInit, OnCha this.utils.deselectAllAnnotations(); } this._ngZone.run(() => this.pageChanged.emit(pageNumber)); - this._handleCustomActions(); + return this._handleCustomActions(); }); this.documentViewer.addEventListener('documentLoaded', this._setReadyAndInitialState); @@ -289,11 +291,12 @@ export class PdfViewerComponent extends AutoUnsubscribe implements OnInit, OnCha } }); - this.documentViewer.addEventListener('textSelected', (quads, selectedText) => { + this.documentViewer.addEventListener('textSelected', async (quads, selectedText) => { this._selectedText = selectedText; const textActions = [dataElements.ADD_DICTIONARY, dataElements.ADD_FALSE_POSITIVE]; - if (selectedText.length > 2 && this.canPerformActions && !this.utils.isCurrentPageExcluded(this.file)) { + const file = await this.stateService.file; + if (selectedText.length > 2 && this.canPerformActions && !this.utils.isCurrentPageExcluded(file)) { this.instance.UI.enableElements(textActions); } else { this.instance.UI.disableElements(textActions); @@ -480,9 +483,13 @@ export class PdfViewerComponent extends AutoUnsubscribe implements OnInit, OnCha ]); } - this.instance.UI.annotationPopup.add( - this._annotationActionsService.getViewerAvailableActions(this.instance, this.file, annotationWrappers, this.annotationsChanged), + const actions = this._annotationActionsService.getViewerAvailableActions( + this.instance, + this.dossier, + annotationWrappers, + this.annotationsChanged, ); + this.instance.UI.annotationPopup.add(actions); } private _configureRectangleAnnotationPopup() { @@ -572,7 +579,7 @@ export class PdfViewerComponent extends AutoUnsubscribe implements OnInit, OnCha }, ]); - this._handleCustomActions(); + return this._handleCustomActions(); } private _addManualRedactionOfType(type: ManualRedactionEntryType) { @@ -582,7 +589,7 @@ export class PdfViewerComponent extends AutoUnsubscribe implements OnInit, OnCha this.manualAnnotationRequested.emit(new ManualRedactionEntryWrapper(selectedQuads, manualRedaction, type)); } - private _handleCustomActions() { + private async _handleCustomActions() { this.instance.UI.setToolMode('AnnotationEdit'); const { ANNOTATION_POPUP, ADD_RECTANGLE, ADD_REDACTION, SHAPE_TOOL_GROUP_BUTTON } = dataElements; const elements = [ @@ -594,7 +601,9 @@ export class PdfViewerComponent extends AutoUnsubscribe implements OnInit, OnCha ANNOTATION_POPUP, ]; - if (this.canPerformActions && !this.utils.isCurrentPageExcluded(this.file)) { + const isCurrentPageExcluded = this.utils.isCurrentPageExcluded(await this.stateService.file); + + if (this.canPerformActions && !isCurrentPageExcluded) { try { this.instance.UI.enableTools(['AnnotationCreateRectangle']); } catch (e) { @@ -611,7 +620,7 @@ export class PdfViewerComponent extends AutoUnsubscribe implements OnInit, OnCha let elementsToDisable = [...elements, ADD_RECTANGLE]; - if (this.utils.isCurrentPageExcluded(this.file)) { + if (isCurrentPageExcluded) { const allowedActionsWhenPageExcluded: string[] = [ANNOTATION_POPUP, ADD_RECTANGLE, ADD_REDACTION, SHAPE_TOOL_GROUP_BUTTON]; elementsToDisable = elementsToDisable.filter(element => !allowedActionsWhenPageExcluded.includes(element)); } else { @@ -641,10 +650,8 @@ export class PdfViewerComponent extends AutoUnsubscribe implements OnInit, OnCha return entry; } - private _loadDocument() { - this.instance.UI.loadDocument(this._stateService.fileData.blob$.value, { - filename: this.file ? this.file.filename : 'document.pdf', - }); + private _loadDocument(blob: Blob, file: File) { + this.instance.UI.loadDocument(blob, { filename: file?.filename ?? 'document.pdf' }); } private _setReadyAndInitialState(): void { diff --git a/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/components/user-management/user-management.component.html b/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/components/user-management/user-management.component.html index 1fc165006..53669e553 100644 --- a/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/components/user-management/user-management.component.html +++ b/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/components/user-management/user-management.component.html @@ -1,47 +1,49 @@ - + -
- {{ translations[file.workflowStatus] | translate }} - {{ 'by' | translate }}: -
+ +
+ {{ translations[file.workflowStatus] | translate }} + {{ 'by' | translate }}: +
- - -
- - - -
- + > - -
+
+ + + +
+ + + +
+
diff --git a/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/components/user-management/user-management.component.ts b/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/components/user-management/user-management.component.ts index 0101f238f..075571ce1 100644 --- a/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/components/user-management/user-management.component.ts +++ b/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/components/user-management/user-management.component.ts @@ -1,4 +1,4 @@ -import { ChangeDetectionStrategy, Component, Input, OnChanges } from '@angular/core'; +import { ChangeDetectionStrategy, Component } from '@angular/core'; import { Dossier, File, StatusBarConfigs, User } from '@red/domain'; import { List, LoadingService, Toaster } from '@iqser/common-ui'; import { PermissionsService } from '@services/permissions.service'; @@ -8,7 +8,10 @@ import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker'; import { UserService } from '@services/user.service'; import { FilesService } from '@services/entity-services/files.service'; import { TranslateService } from '@ngx-translate/core'; -import { firstValueFrom } from 'rxjs'; +import { BehaviorSubject, combineLatest, firstValueFrom, Observable, switchMap } from 'rxjs'; +import { FilePreviewStateService } from '../../services/file-preview-state.service'; +import { distinctUntilChanged, map } from 'rxjs/operators'; +import { DossiersService } from '@services/entity-services/dossiers.service'; @Component({ selector: 'redaction-user-management', @@ -16,22 +19,19 @@ import { firstValueFrom } from 'rxjs'; styleUrls: ['./user-management.component.scss'], changeDetection: ChangeDetectionStrategy.OnPush, }) -export class UserManagementComponent implements OnChanges { +export class UserManagementComponent { readonly translations = workflowFileStatusTranslations; - - @Input() file: File; - @Input() dossier: Dossier; - - editingReviewer = false; - statusBarConfigs: StatusBarConfigs; - canAssignToSelf = false; - canAssignUser = false; - canUnassignUser = false; - canAssign = false; - canAssignOrUnassign = false; - canAssignReviewer = false; - assignTooltip: string; - usersOptions: List; + readonly statusBarConfig$: Observable; + readonly assignTooltip$: Observable; + readonly canAssignReviewer$: Observable; + readonly canAssignToSelf$: Observable; + readonly editingReviewer$ = new BehaviorSubject(false); + readonly canAssignOrUnassign$: Observable; + readonly canAssign$: Observable; + readonly usersOptions$: Observable; + private readonly _dossier$: Observable; + private readonly _canAssignUser$: Observable; + private readonly _canUnassignUser$: Observable; constructor( readonly fileAssignService: FileAssignService, @@ -41,44 +41,55 @@ export class UserManagementComponent implements OnChanges { readonly toaster: Toaster, readonly loadingService: LoadingService, readonly translateService: TranslateService, - ) {} + readonly stateService: FilePreviewStateService, + private readonly _dossiersService: DossiersService, + ) { + this._dossier$ = this.stateService.file$.pipe(switchMap(file => this._dossiersService.getEntityChanged$(file.dossierId))); + this.statusBarConfig$ = this.stateService.file$.pipe(map(file => [{ length: 1, color: file.workflowStatus }])); + this.assignTooltip$ = this.stateService.file$.pipe( + map(file => + file.isUnderApproval + ? this.translateService.instant(_('dossier-overview.assign-approver')) + : file.assignee + ? this.translateService.instant(_('file-preview.change-reviewer')) + : this.translateService.instant(_('file-preview.assign-reviewer')), + ), + ); - private get _statusBarConfig(): StatusBarConfigs { - return [{ length: 1, color: this.file.workflowStatus }]; - } + this.canAssignToSelf$ = this.stateService.file$.pipe( + map(file => this.permissionsService.canAssignToSelf(file)), + distinctUntilChanged(), + ); + this._canAssignUser$ = this.stateService.file$.pipe( + map(file => this.permissionsService.canAssignUser(file)), + distinctUntilChanged(), + ); + this._canUnassignUser$ = this.stateService.file$.pipe( + map(file => this.permissionsService.canUnassignUser(file)), + distinctUntilChanged(), + ); - private get _assignOrChangeReviewerTooltip(): string { - return this.file.assignee - ? this.translateService.instant(_('file-preview.change-reviewer')) - : this.translateService.instant(_('file-preview.assign-reviewer')); - } + this.canAssignOrUnassign$ = combineLatest([this._canAssignUser$, this._canUnassignUser$]).pipe( + map(([canAssignUser, canUnassignUser]) => canAssignUser || canUnassignUser), + distinctUntilChanged(), + ); - private get _assignTooltip(): string { - return this.file.isUnderApproval - ? this.translateService.instant(_('dossier-overview.assign-approver')) - : this._assignOrChangeReviewerTooltip; - } + this.canAssign$ = combineLatest([this.canAssignToSelf$, this.canAssignOrUnassign$]).pipe( + map(([canAssignToSelf, canAssignOrUnassign]) => canAssignToSelf || canAssignOrUnassign), + distinctUntilChanged(), + ); - private get _canAssignReviewer(): boolean { - return !this.file.assignee && this.canAssignUser && this.dossier.hasReviewers; - } + this.canAssignReviewer$ = combineLatest([this.stateService.file$, this._canAssignUser$, this._dossier$]).pipe( + map(([file, canAssignUser, dossier]) => !file.assignee && canAssignUser && dossier.hasReviewers), + distinctUntilChanged(), + ); - private get _usersOptions(): List { - const unassignUser = this.canUnassignUser ? [undefined] : []; - return this.file.isUnderApproval ? [...this.dossier.approverIds, ...unassignUser] : [...this.dossier.memberIds, ...unassignUser]; - } - - ngOnChanges() { - this.canAssignToSelf = this.permissionsService.canAssignToSelf(this.file); - this.canAssignUser = this.permissionsService.canAssignUser(this.file); - this.canUnassignUser = this.permissionsService.canUnassignUser(this.file); - this.canAssignOrUnassign = this.canAssignUser || this.canUnassignUser; - this.canAssign = this.canAssignToSelf || this.canAssignOrUnassign; - - this.statusBarConfigs = this._statusBarConfig; - this.canAssignReviewer = this._canAssignReviewer; - this.assignTooltip = this._assignTooltip; - this.usersOptions = this._usersOptions; + this.usersOptions$ = combineLatest([this._canUnassignUser$, this.stateService.file$, this._dossier$]).pipe( + map(([canUnassignUser, file, dossier]) => { + const unassignUser = canUnassignUser ? [undefined] : []; + return file.isUnderApproval ? [...dossier.approverIds, ...unassignUser] : [...dossier.memberIds, ...unassignUser]; + }), + ); } async assignReviewer(file: File, user: User | string) { @@ -99,6 +110,6 @@ export class UserManagementComponent implements OnChanges { this.loadingService.stop(); this.toaster.info(_('assignment.reviewer'), { params: { reviewerName, filename } }); - this.editingReviewer = false; + this.editingReviewer$.next(false); } } diff --git a/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/components/view-switch/view-switch.component.html b/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/components/view-switch/view-switch.component.html index c99ce4ba5..c3836a11a 100644 --- a/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/components/view-switch/view-switch.component.html +++ b/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/components/view-switch/view-switch.component.html @@ -21,9 +21,9 @@
diff --git a/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/dialogs/accept-recommendation-dialog/accept-recommendation-dialog.component.ts b/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/dialogs/accept-recommendation-dialog/accept-recommendation-dialog.component.ts index 32392f6c8..63663c1e9 100644 --- a/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/dialogs/accept-recommendation-dialog/accept-recommendation-dialog.component.ts +++ b/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/dialogs/accept-recommendation-dialog/accept-recommendation-dialog.component.ts @@ -3,7 +3,7 @@ import { FormBuilder, FormGroup, Validators } from '@angular/forms'; import { AppStateService } from '@state/app-state.service'; import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { PermissionsService } from '@services/permissions.service'; -import { Dictionary, Dossier, File } from '@red/domain'; +import { Dictionary, Dossier } from '@red/domain'; import { DossiersService } from '@services/entity-services/dossiers.service'; import { BaseDialogComponent } from '@iqser/common-ui'; import { DictionaryService } from '@shared/services/dictionary.service'; @@ -12,7 +12,7 @@ import { AnnotationWrapper } from '../../../../../../models/file/annotation.wrap export interface AcceptRecommendationData { readonly annotations: AnnotationWrapper[]; - readonly file: File; + readonly dossierId: string; } export interface AcceptRecommendationReturnType { @@ -42,7 +42,7 @@ export class AcceptRecommendationDialogComponent extends BaseDialogComponent imp @Inject(MAT_DIALOG_DATA) readonly data: AcceptRecommendationData, ) { super(_injector, _dialogRef); - this._dossier = this._dossiersService.find(this.data.file.dossierId); + this._dossier = this._dossiersService.find(this.data.dossierId); this.isDocumentAdmin = this._permissionsService.isApprover(this._dossier); this.form = this._getForm(); this.initialFormValue = this.form.getRawValue(); diff --git a/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/file-preview-providers.ts b/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/file-preview-providers.ts new file mode 100644 index 000000000..79d5b5720 --- /dev/null +++ b/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/file-preview-providers.ts @@ -0,0 +1,27 @@ +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 { CommentingService } from './services/commenting.service'; +import { SkippedService } from './services/skipped.service'; +import { AnnotationDrawService } from './services/annotation-draw.service'; +import { AnnotationActionsService } from './services/annotation-actions.service'; +import { FilePreviewStateService } from './services/file-preview-state.service'; +import { PdfViewerDataService } from '../../services/pdf-viewer-data.service'; +import { AnnotationReferencesService } from './services/annotation-references.service'; +import { FilterService } from '../../../../../../../../libs/common-ui/src'; + +export const filePreviewScreenProviders = [ + FilterService, + ExcludedPagesService, + ViewModeService, + MultiSelectService, + DocumentInfoService, + CommentingService, + SkippedService, + AnnotationDrawService, + AnnotationActionsService, + FilePreviewStateService, + PdfViewerDataService, + AnnotationReferencesService, +]; diff --git a/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/file-preview-screen.component.html b/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/file-preview-screen.component.html index b8a4fe9b0..6e5a51511 100644 --- a/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/file-preview-screen.component.html +++ b/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/file-preview-screen.component.html @@ -1,9 +1,9 @@ - - + +
@@ -89,11 +88,7 @@ icon="red:needs-work" > - + ; - readonly dossier$: Observable; - readonly fileId: string; + readonly fileId = this.stateService.fileId; + readonly dossierId = this.stateService.dossierId; ready = false; private _instance: WebViewerInstance; private _lastPage: string; @@ -102,7 +87,7 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni constructor( readonly permissionsService: PermissionsService, readonly userPreferenceService: UserPreferenceService, - private readonly _stateService: FilePreviewStateService, + readonly stateService: FilePreviewStateService, private readonly _watermarkService: WatermarkService, private readonly _changeDetectorRef: ChangeDetectorRef, private readonly _activatedRoute: ActivatedRoute, @@ -122,15 +107,13 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni private readonly _reanalysisService: ReanalysisService, private readonly _errorService: ErrorService, private readonly _skippedService: SkippedService, + private readonly _manualAnnotationService: ManualAnnotationService, readonly excludedPagesService: ExcludedPagesService, readonly viewModeService: ViewModeService, readonly multiSelectService: MultiSelectService, readonly documentInfoService: DocumentInfoService, ) { super(); - this.dossierId = _activatedRoute.snapshot.paramMap.get('dossierId'); - this.dossier$ = _dossiersService.getEntityChanged$(this.dossierId); - this.fileId = _activatedRoute.snapshot.paramMap.get('fileId'); this.canPerformAnnotationActions$ = this._canPerformAnnotationActions$; document.documentElement.addEventListener('fullscreenchange', () => { @@ -141,34 +124,30 @@ 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 _fileData(): FileDataModel { + return this.stateService.fileData; } private get _canPerformAnnotationActions$() { - return combineLatest([ - this._stateService.fileData$.pipe(switchMap(fileData => fileData.file$)), - this.viewModeService.viewMode$, - this.viewModeService.compareMode$, - ]).pipe( + return combineLatest([this.stateService.file$, this.viewModeService.viewMode$, this.viewModeService.compareMode$]).pipe( map(([file, viewMode]) => this.permissionsService.canPerformAnnotationActions(file) && viewMode === 'STANDARD'), shareDistinctLast(), ); } async updateViewMode(): Promise { - 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')); @@ -211,7 +190,7 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni } async ngOnAttach(previousRoute: ActivatedRouteSnapshot): Promise { - const file = this._filesMapService.get(this.dossierId, this.fileId); + const file = await this.stateService.file; if (!file.canBeOpened) { return this._router.navigate([this._dossiersService.find(this.dossierId)?.routerLink]); } @@ -228,9 +207,10 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni await this.userPreferenceService.saveLastOpenedFileForDossier(this.dossierId, this.fileId); this._subscribeToFileUpdates(); - const file = this._filesMapService.get(this.dossierId, this.fileId); + const file = await this.stateService.file; if (file?.analysisRequired) { - await this._reanalysisService.reanalyzeFilesForDossier([this.fileId], this.dossierId, true).toPromise(); + const reanalyzeFiles = this._reanalysisService.reanalyzeFilesForDossier([this.fileId], this.dossierId, true); + await firstValueFrom(reanalyzeFiles); } this.displayPdfViewer = true; @@ -263,7 +243,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}`); @@ -301,18 +281,22 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni openManualAnnotationDialog(manualRedactionEntryWrapper: ManualRedactionEntryWrapper) { this._ngZone.run(() => { - const file = this._filesMapService.get(this.dossierId, this.fileId); this.dialogRef = this._dialogService.openDialog( 'manualAnnotation', null, - { manualRedactionEntryWrapper, file }, - async (response: ManualAnnotationResponse) => { + { manualRedactionEntryWrapper, dossierId: this.dossierId }, + async (entryWrapper: ManualRedactionEntryWrapper) => { + const addAnnotation$ = this._manualAnnotationService + .addAnnotation(entryWrapper.manualRedactionEntry, this.dossierId, this.fileId) + .pipe(catchError(() => of(undefined))); + const addAnnotationResponse = await firstValueFrom(addAnnotation$); + const response = new ManualAnnotationResponse(entryWrapper, addAnnotationResponse); + if (response?.annotationId) { const annotation = this._instance.Core.annotationManager.getAnnotationById( response.manualRedactionEntryWrapper.rectId, ); this._instance.Core.annotationManager.deleteAnnotation(annotation); - // 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); @@ -470,19 +454,6 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni }, 100); } - private async _reloadFile(file: File): Promise { - 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) { - return; - } - } - } - private async _stampPDF() { if (!this._instance?.Core.documentViewer.getDocument()) { return; @@ -538,23 +509,19 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni } } - private async _fileUpdated(file: File): Promise { - 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 async _fileUpdated(file: File): Promise { + await this._loadFileData(file); + await this._reloadAnnotations(); + await this._stampPDF(); } private _subscribeToFileUpdates(): void { this.addActiveScreenSubscription = this._filesMapService .watch$(this.dossierId, this.fileId) - .pipe(switchMap(file => this._fileUpdated(file))) + .pipe( + filter(f => !!f), + switchMap(file => this._fileUpdated(file)), + ) .subscribe(); this.addActiveScreenSubscription = timer(0, 5000) @@ -593,13 +560,11 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni return this._router.navigate([this._dossiersService.find(this.dossierId).routerLink]); } - const fileData = await this._pdfViewerDataService.loadDataFor(file).toPromise(); - if (file.isPending) { return; } - this._stateService.fileData = fileData; + this.stateService.fileData = await firstValueFrom(this._pdfViewerDataService.loadDataFor(file)); } @Debounce(0) @@ -608,25 +573,24 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni this._workloadComponent?.scrollAnnotations(); } - private async _reloadAnnotations(previousAnnotations?: AnnotationWrapper[]) { + private async _reloadAnnotations() { this._deleteAnnotations(); - await this._cleanupAndRedrawAnnotations(previousAnnotations); + await this._cleanupAndRedrawAnnotations(); } private async _reloadAnnotationsForPage(page: number) { - this.fileData.file = await this._filesService.reload(this.dossierId, this.fileId).toPromise(); - + const file = await firstValueFrom(this._filesService.reload(this.dossierId, this.fileId)); // 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 (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 firstValueFrom(this._pdfViewerDataService.loadRedactionLogFor(this.dossierId, this.fileId)); this._deleteAnnotations(currentPageAnnotations); - await this._cleanupAndRedrawAnnotations(currentPageAnnotations, annotation => annotation.pageNumber === page); + await this._cleanupAndRedrawAnnotations(annotation => annotation.pageNumber === page); } private _deleteAnnotations(annotationsToDelete?: AnnotationWrapper[]) { @@ -645,10 +609,7 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni }); } - private async _cleanupAndRedrawAnnotations( - currentAnnotations?: AnnotationWrapper[], - newAnnotationsFilter?: (annotation: AnnotationWrapper) => boolean, - ) { + private async _cleanupAndRedrawAnnotations(newAnnotationsFilter?: (annotation: AnnotationWrapper) => boolean) { if (!this._instance?.Core.documentViewer.getDocument()) { return; } @@ -656,17 +617,18 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni const currentFilters = this._filterService.getGroup('primaryFilters')?.filters || []; this.rebuildFilters(); - if (this.viewModeService.viewMode === 'STANDARD') { - const startTime = new Date().getTime(); - const newAnnotations = newAnnotationsFilter ? this.visibleAnnotations.filter(newAnnotationsFilter) : this.visibleAnnotations; - if (currentFilters) { - this._handleDeltaAnnotationFilters(currentFilters, this.visibleAnnotations); - } - await this._redrawAnnotations(newAnnotations); - console.log( - `[REDACTION] Annotations redraw time: ${new Date().getTime() - startTime} ms for ${newAnnotations.length} annotations`, - ); + if (!this.viewModeService.isStandard) { + return; } + + const startTime = new Date().getTime(); + const newAnnotations = newAnnotationsFilter ? this.visibleAnnotations.filter(newAnnotationsFilter) : this.visibleAnnotations; + + if (currentFilters) { + this._handleDeltaAnnotationFilters(currentFilters, this.visibleAnnotations); + } + await this._redrawAnnotations(newAnnotations); + console.log(`[REDACTION] Annotations redraw time: ${new Date().getTime() - startTime} ms for ${newAnnotations.length} annotations`); } private _redrawAnnotations(annotations = this.allAnnotations) { diff --git a/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/services/annotation-actions.service.ts b/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/services/annotation-actions.service.ts index 1fd2c111b..bc3598fee 100644 --- a/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/services/annotation-actions.service.ts +++ b/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/services/annotation-actions.service.ts @@ -10,7 +10,7 @@ import { DossiersDialogService } from '../../../services/dossiers-dialog.service import { BASE_HREF } from '../../../../../tokens'; import { UserService } from '@services/user.service'; import { Core, WebViewerInstance } from '@pdftron/webviewer'; -import { Dossier, File, IAddRedactionRequest, ILegalBasisChangeRequest, IRectangle, IResizeRequest } from '@red/domain'; +import { Dossier, IAddRedactionRequest, ILegalBasisChangeRequest, IRectangle, IResizeRequest } from '@red/domain'; import { toPosition } from '../../../utils/pdf-calculation.utils'; import { AnnotationDrawService } from './annotation-draw.service'; import { translateQuads } from '@utils/pdf-coordinates'; @@ -23,6 +23,7 @@ import { import { defaultDialogConfig } from '@iqser/common-ui'; import { filter } from 'rxjs/operators'; import { MatDialog } from '@angular/material/dialog'; +import { FilePreviewStateService } from './file-preview-state.service'; import Annotation = Core.Annotations.Annotation; @Injectable() @@ -38,44 +39,45 @@ export class AnnotationActionsService { private readonly _dialog: MatDialog, private readonly _annotationDrawService: AnnotationDrawService, private readonly _dossiersService: DossiersService, + private readonly _screenStateService: FilePreviewStateService, ) {} - acceptSuggestion( - $event: MouseEvent, - annotations: AnnotationWrapper[], - file: File, - annotationsChanged: EventEmitter, - ) { + private get _dossier(): Dossier { + return this._dossiersService.find(this._screenStateService.dossierId); + } + + acceptSuggestion($event: MouseEvent, annotations: AnnotationWrapper[], annotationsChanged: EventEmitter) { $event?.stopPropagation(); + const { dossierId, fileId } = this._screenStateService; annotations.forEach(annotation => { this._processObsAndEmit( - this._manualAnnotationService.approve(annotation.id, file, annotation.isModifyDictionary), + this._manualAnnotationService.approve(annotation.id, dossierId, fileId, annotation.isModifyDictionary), annotation, annotationsChanged, ); }); } - rejectSuggestion( - $event: MouseEvent, - annotations: AnnotationWrapper[], - file: File, - annotationsChanged: EventEmitter, - ) { + rejectSuggestion($event: MouseEvent, annotations: AnnotationWrapper[], annotationsChanged: EventEmitter) { $event?.stopPropagation(); + const { dossierId, fileId } = this._screenStateService; annotations.forEach(annotation => { - this._processObsAndEmit(this._manualAnnotationService.declineOrRemoveRequest(annotation, file), annotation, annotationsChanged); + this._processObsAndEmit( + this._manualAnnotationService.declineOrRemoveRequest(annotation, dossierId, fileId), + annotation, + annotationsChanged, + ); }); } forceAnnotation( $event: MouseEvent, annotations: AnnotationWrapper[], - file: File, annotationsChanged: EventEmitter, hint: boolean = false, ) { - const data = { dossier: this._dossier(file), hint }; + const { dossierId, fileId } = this._screenStateService; + const data = { dossier: this._dossier, hint }; this._dialogService.openDialog('forceAnnotation', $event, data, (request: ILegalBasisChangeRequest) => { annotations.forEach(annotation => { this._processObsAndEmit( @@ -84,7 +86,8 @@ export class AnnotationActionsService { ...request, annotationId: annotation.id, }, - file, + dossierId, + fileId, ), annotation, annotationsChanged, @@ -93,22 +96,19 @@ export class AnnotationActionsService { }); } - changeLegalBasis( - $event: MouseEvent, - annotations: AnnotationWrapper[], - file: File, - annotationsChanged: EventEmitter, - ) { + changeLegalBasis($event: MouseEvent, annotations: AnnotationWrapper[], annotationsChanged: EventEmitter) { + const { dossierId, fileId } = this._screenStateService; this._dialogService.openDialog( 'changeLegalBasis', $event, - { annotations, dossier: this._dossier(file) }, + { annotations, dossier: this._dossier }, (data: { comment: string; legalBasis: string; section: string; value: string }) => { annotations.forEach(annotation => { this._processObsAndEmit( this._manualAnnotationService.changeLegalBasis( annotation.annotationId, - file, + dossierId, + fileId, data.section, data.value, data.legalBasis, @@ -125,20 +125,26 @@ export class AnnotationActionsService { removeOrSuggestRemoveAnnotation( $event: MouseEvent, annotations: AnnotationWrapper[], - file: File, removeFromDictionary: boolean, annotationsChanged: EventEmitter, ) { const data = { annotationsToRemove: annotations, removeFromDictionary, - dossier: this._dossier(file), + dossier: this._dossier, hint: annotations[0].hintDictionary, }; + const { dossierId, fileId } = this._screenStateService; this._dialogService.openDialog('removeAnnotations', $event, data, (result: { comment: string }) => { annotations.forEach(annotation => { this._processObsAndEmit( - this._manualAnnotationService.removeOrSuggestRemoveAnnotation(annotation, file, removeFromDictionary, result.comment), + this._manualAnnotationService.removeOrSuggestRemoveAnnotation( + annotation, + dossierId, + fileId, + removeFromDictionary, + result.comment, + ), annotation, annotationsChanged, ); @@ -146,28 +152,19 @@ export class AnnotationActionsService { }); } - markAsFalsePositive( - $event: MouseEvent, - annotations: AnnotationWrapper[], - file: File, - annotationsChanged: EventEmitter, - ) { + markAsFalsePositive($event: MouseEvent, annotations: AnnotationWrapper[], annotationsChanged: EventEmitter) { annotations.forEach(annotation => { - this._markAsFalsePositive($event, annotation, file, this._getFalsePositiveText(annotation), annotationsChanged); + this._markAsFalsePositive($event, annotation, this._getFalsePositiveText(annotation), annotationsChanged); }); } - recategorizeImages( - $event: MouseEvent, - annotations: AnnotationWrapper[], - file: File, - annotationsChanged: EventEmitter, - ) { - const data = { annotations, dossier: this._dossier(file) }; + recategorizeImages($event: MouseEvent, annotations: AnnotationWrapper[], annotationsChanged: EventEmitter) { + const data = { annotations, dossier: this._dossier }; + const { dossierId, fileId } = this._screenStateService; this._dialogService.openDialog('recategorizeImage', $event, data, (res: { type: string; comment: string }) => { annotations.forEach(annotation => { this._processObsAndEmit( - this._manualAnnotationService.recategorizeImg(annotation.annotationId, file, res.type, res.comment), + this._manualAnnotationService.recategorizeImg(annotation.annotationId, dossierId, fileId, res.type, res.comment), annotation, annotationsChanged, ); @@ -175,37 +172,37 @@ export class AnnotationActionsService { }); } - undoDirectAction( - $event: MouseEvent, - annotations: AnnotationWrapper[], - file: File, - annotationsChanged: EventEmitter, - ) { + undoDirectAction($event: MouseEvent, annotations: AnnotationWrapper[], annotationsChanged: EventEmitter) { $event?.stopPropagation(); + const { dossierId, fileId } = this._screenStateService; annotations.forEach(annotation => { - this._processObsAndEmit(this._manualAnnotationService.undoRequest(annotation, file), annotation, annotationsChanged); + this._processObsAndEmit( + this._manualAnnotationService.undoRequest(annotation, dossierId, fileId), + annotation, + annotationsChanged, + ); }); } convertRecommendationToAnnotation( $event: any, recommendations: AnnotationWrapper[], - file: File, annotationsChanged: EventEmitter, ) { $event?.stopPropagation(); + const { dossierId, fileId } = this._screenStateService; const dialogRef = this._dialog.open( AcceptRecommendationDialogComponent, - { ...defaultDialogConfig, autoFocus: true, data: { annotations: recommendations, file } }, + { ...defaultDialogConfig, autoFocus: true, data: { annotations: recommendations, dossierId } }, ); const dialogClosed = dialogRef.afterClosed().pipe(filter(value => !!value && !!value.annotations)); dialogClosed.subscribe(({ annotations, comment: commentText }) => { const comment = commentText ? { text: commentText } : undefined; annotations.forEach(annotation => { this._processObsAndEmit( - this._manualAnnotationService.addRecommendation(annotation, file, comment), + this._manualAnnotationService.addRecommendation(annotation, dossierId, fileId, comment), annotation, annotationsChanged, ); @@ -215,13 +212,11 @@ export class AnnotationActionsService { getViewerAvailableActions( viewer: WebViewerInstance, - file: File, + dossier: Dossier, annotations: AnnotationWrapper[], annotationsChanged: EventEmitter, ): Record[] { const availableActions = []; - const dossier = this._dossier(file); - const annotationPermissions = annotations.map(annotation => ({ annotation, permissions: AnnotationPermissions.forUser( @@ -241,21 +236,19 @@ export class AnnotationActionsService { type: 'actionButton', img: this._convertPath('/assets/icons/general/check.svg'), title: this._translateService.instant('annotation-actions.resize-accept.label'), - onClick: () => { + onClick: () => this._ngZone.run(() => { - this.acceptResize(null, viewer, file, firstAnnotation, annotationsChanged); - }); - }, + this.acceptResize(null, viewer, firstAnnotation, annotationsChanged); + }), }); availableActions.push({ type: 'actionButton', img: this._convertPath('/assets/icons/general/close.svg'), title: this._translateService.instant('annotation-actions.resize-cancel.label'), - onClick: () => { + onClick: () => this._ngZone.run(() => { this.cancelResize(null, viewer, firstAnnotation, annotationsChanged); - }); - }, + }), }); return availableActions; } @@ -264,11 +257,7 @@ export class AnnotationActionsService { type: 'actionButton', img: this._convertPath('/assets/icons/general/resize.svg'), title: this._translateService.instant('annotation-actions.resize.label'), - onClick: () => { - this._ngZone.run(() => { - this.resize(null, viewer, annotations[0]); - }); - }, + onClick: () => this._ngZone.run(() => this.resize(null, viewer, annotations[0])), }); } @@ -278,11 +267,10 @@ export class AnnotationActionsService { type: 'actionButton', img: this._convertPath('/assets/icons/general/edit.svg'), title: this._translateService.instant('annotation-actions.edit-reason.label'), - onClick: () => { + onClick: () => this._ngZone.run(() => { - this.changeLegalBasis(null, annotations, file, annotationsChanged); - }); - }, + this.changeLegalBasis(null, annotations, annotationsChanged); + }), }); } @@ -292,11 +280,10 @@ export class AnnotationActionsService { type: 'actionButton', img: this._convertPath('/assets/icons/general/thumb-down.svg'), title: this._translateService.instant('annotation-actions.recategorize-image'), - onClick: () => { + onClick: () => this._ngZone.run(() => { - this.recategorizeImages(null, annotations, file, annotationsChanged); - }); - }, + this.recategorizeImages(null, annotations, annotationsChanged); + }), }); } @@ -309,11 +296,10 @@ export class AnnotationActionsService { type: 'actionButton', img: this._convertPath('/assets/icons/general/remove-from-dict.svg'), title: this._translateService.instant('annotation-actions.remove-annotation.remove-from-dict'), - onClick: () => { + onClick: () => this._ngZone.run(() => { - this.removeOrSuggestRemoveAnnotation(null, annotations, file, true, annotationsChanged); - }); - }, + this.removeOrSuggestRemoveAnnotation(null, annotations, true, annotationsChanged); + }), }); } @@ -323,11 +309,10 @@ export class AnnotationActionsService { type: 'actionButton', img: this._convertPath('/assets/icons/general/check.svg'), title: this._translateService.instant('annotation-actions.accept-recommendation.label'), - onClick: () => { + onClick: () => this._ngZone.run(() => { - this.convertRecommendationToAnnotation(null, annotations, file, annotationsChanged); - }); - }, + this.convertRecommendationToAnnotation(null, annotations, annotationsChanged); + }), }); } @@ -337,11 +322,10 @@ export class AnnotationActionsService { type: 'actionButton', img: this._convertPath('/assets/icons/general/check.svg'), title: this._translateService.instant('annotation-actions.accept-suggestion.label'), - onClick: () => { + onClick: () => this._ngZone.run(() => { - this.acceptSuggestion(null, annotations, file, annotationsChanged); - }); - }, + this.acceptSuggestion(null, annotations, annotationsChanged); + }), }); } @@ -351,11 +335,10 @@ export class AnnotationActionsService { type: 'actionButton', img: this._convertPath('/assets/icons/general/undo.svg'), title: this._translateService.instant('annotation-actions.undo'), - onClick: () => { + onClick: () => this._ngZone.run(() => { - this.undoDirectAction(null, annotations, file, annotationsChanged); - }); - }, + this.undoDirectAction(null, annotations, annotationsChanged); + }), }); } @@ -365,11 +348,10 @@ export class AnnotationActionsService { type: 'actionButton', img: this._convertPath('/assets/icons/general/thumb-down.svg'), title: this._translateService.instant('annotation-actions.remove-annotation.false-positive'), - onClick: () => { + onClick: () => this._ngZone.run(() => { - this.markAsFalsePositive(null, annotations, file, annotationsChanged); - }); - }, + this.markAsFalsePositive(null, annotations, annotationsChanged); + }), }); } @@ -379,11 +361,10 @@ export class AnnotationActionsService { type: 'actionButton', img: this._convertPath('/assets/icons/general/thumb-up.svg'), title: this._translateService.instant('annotation-actions.force-redaction.label'), - onClick: () => { + onClick: () => this._ngZone.run(() => { - this.forceAnnotation(null, annotations, file, annotationsChanged); - }); - }, + this.forceAnnotation(null, annotations, annotationsChanged); + }), }); } @@ -393,11 +374,10 @@ export class AnnotationActionsService { type: 'actionButton', img: this._convertPath('/assets/icons/general/thumb-up.svg'), title: this._translateService.instant('annotation-actions.force-hint.label'), - onClick: () => { + onClick: () => this._ngZone.run(() => { - this.forceAnnotation(null, annotations, file, annotationsChanged, true); - }); - }, + this.forceAnnotation(null, annotations, annotationsChanged, true); + }), }); } @@ -407,11 +387,10 @@ export class AnnotationActionsService { type: 'actionButton', img: this._convertPath('/assets/icons/general/close.svg'), title: this._translateService.instant('annotation-actions.reject-suggestion'), - onClick: () => { + onClick: () => this._ngZone.run(() => { - this.rejectSuggestion(null, annotations, file, annotationsChanged); - }); - }, + this.rejectSuggestion(null, annotations, annotationsChanged); + }), }); } @@ -424,11 +403,10 @@ export class AnnotationActionsService { type: 'actionButton', img: this._convertPath('/assets/icons/general/trash.svg'), title: this._translateService.instant('annotation-actions.remove-annotation.only-here'), - onClick: () => { + onClick: () => this._ngZone.run(() => { - this.removeOrSuggestRemoveAnnotation(null, annotations, file, false, annotationsChanged); - }); - }, + this.removeOrSuggestRemoveAnnotation(null, annotations, false, annotationsChanged); + }), }); } @@ -460,11 +438,11 @@ export class AnnotationActionsService { acceptResize( $event: MouseEvent, viewer: WebViewerInstance, - file: File, annotationWrapper: AnnotationWrapper, annotationsChanged?: EventEmitter, ) { - const data = { dossier: this._dossier(file) }; + const data = { dossier: this._dossier }; + const fileId = this._screenStateService.fileId; this._dialogService.openDialog('resizeAnnotation', $event, data, async (result: { comment: string }) => { const textAndPositions = await this._extractTextAndPositions(viewer, annotationWrapper.id); const text = @@ -478,7 +456,7 @@ export class AnnotationActionsService { }; this._processObsAndEmit( - this._manualAnnotationService.resizeOrSuggestToResize(annotationWrapper, file, resizeRequest), + this._manualAnnotationService.resizeOrSuggestToResize(annotationWrapper, data.dossier.dossierId, fileId, resizeRequest), annotationWrapper, annotationsChanged, ); @@ -503,10 +481,6 @@ export class AnnotationActionsService { annotationsChanged.emit(annotationWrapper); } - private _dossier(file: File): Dossier { - return this._dossiersService.find(file.dossierId); - } - private _processObsAndEmit( obs: Observable, annotation: AnnotationWrapper, @@ -541,7 +515,6 @@ export class AnnotationActionsService { private _markAsFalsePositive( $event: MouseEvent, annotation: AnnotationWrapper, - file: File, text: string, annotationsChanged: EventEmitter, ) { @@ -554,8 +527,13 @@ export class AnnotationActionsService { falsePositiveRequest.positions = annotation.positions; falsePositiveRequest.addToDictionary = true; falsePositiveRequest.comment = { text: 'False Positive' }; + const { dossierId, fileId } = this._screenStateService; - this._processObsAndEmit(this._manualAnnotationService.addAnnotation(falsePositiveRequest, file), annotation, annotationsChanged); + this._processObsAndEmit( + this._manualAnnotationService.addAnnotation(falsePositiveRequest, dossierId, fileId), + annotation, + annotationsChanged, + ); } private _convertPath(path: string): string { diff --git a/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/services/file-preview-state.service.ts b/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/services/file-preview-state.service.ts index f3175e96c..72e6a597b 100644 --- a/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/services/file-preview-state.service.ts +++ b/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/services/file-preview-state.service.ts @@ -1,14 +1,31 @@ import { Injectable } from '@angular/core'; -import { BehaviorSubject, Observable } from 'rxjs'; +import { BehaviorSubject, firstValueFrom, Observable } from 'rxjs'; import { FileDataModel } from '@models/file/file-data.model'; +import { Dossier, File } from '@red/domain'; +import { DossiersService } from '../../../../../services/entity-services/dossiers.service'; +import { ActivatedRoute } from '@angular/router'; +import { FilesMapService } from '../../../../../services/entity-services/files-map.service'; @Injectable() export class FilePreviewStateService { readonly fileData$: Observable; + readonly file$: Observable; + readonly dossier$: Observable; + readonly dossierId: string; + readonly fileId: string; private readonly _fileData$ = new BehaviorSubject(undefined); - constructor() { + constructor( + private readonly _dossiersService: DossiersService, + private readonly _filesMapService: FilesMapService, + activatedRoute: ActivatedRoute, + ) { + this.dossierId = activatedRoute.snapshot.paramMap.get('dossierId'); + this.dossier$ = _dossiersService.getEntityChanged$(this.dossierId); + this.fileId = activatedRoute.snapshot.paramMap.get('fileId'); + this.fileData$ = this._fileData$.asObservable(); + this.file$ = _filesMapService.watch$(this.dossierId, this.fileId); } get fileData(): FileDataModel { @@ -18,4 +35,8 @@ export class FilePreviewStateService { set fileData(fileDataModel: FileDataModel) { this._fileData$.next(fileDataModel); } + + get file(): Promise { + return firstValueFrom(this.file$); + } } diff --git a/apps/red-ui/src/app/modules/dossier/services/manual-annotation.service.ts b/apps/red-ui/src/app/modules/dossier/services/manual-annotation.service.ts index 247d46473..bbf14433c 100644 --- a/apps/red-ui/src/app/modules/dossier/services/manual-annotation.service.ts +++ b/apps/red-ui/src/app/modules/dossier/services/manual-annotation.service.ts @@ -2,7 +2,6 @@ import { Injectable, Injector } from '@angular/core'; import { AppStateService } from '@state/app-state.service'; import { Dossier, - File, IAddRedactionRequest, IApproveRequest, IImageRecategorizationRequest, @@ -57,14 +56,15 @@ export class ManualAnnotationService extends GenericService _makeRequest( mode: AnnotationActionMode, - file: File, + dossierId: string, + fileId: string, body: any, secondParam: any = null, modifyDictionary = false, ): Observable { const obs = !secondParam - ? this[this.CONFIG[mode]](body, file.dossierId, file.id) - : this[this.CONFIG[mode]](body, secondParam, file.dossierId, file.id); + ? this[this.CONFIG[mode]](body, dossierId, fileId) + : this[this.CONFIG[mode]](body, secondParam, dossierId, fileId); return obs.pipe( tap({ @@ -74,7 +74,8 @@ export class ManualAnnotationService extends GenericService this._toaster.error(this._getMessage(mode, modifyDictionary, true, isConflict), { error, params: { - dictionaryName: this._appStateService.getDictionary(body.type, this._dossier(file).dossierTemplateId).label, + dictionaryName: this._appStateService.getDictionary(body.type, this._dossier(dossierId).dossierTemplateId) + .label, content: body.value, }, positionClass: 'toast-file-preview', @@ -96,7 +97,7 @@ export class ManualAnnotationService extends GenericService return super.delete({}, url); } - addRecommendation(annotation: AnnotationWrapper, file: File, comment = { text: 'Accepted Recommendation' }) { + addRecommendation(annotation: AnnotationWrapper, dossierId: string, fileId: string, comment = { text: 'Accepted Recommendation' }) { const manualRedactionEntry: IAddRedactionRequest = {}; manualRedactionEntry.addToDictionary = true; // set the ID as reason, so we can hide the suggestion @@ -105,76 +106,89 @@ export class ManualAnnotationService extends GenericService manualRedactionEntry.positions = annotation.positions; manualRedactionEntry.type = annotation.recommendationType; manualRedactionEntry.comment = comment; - return this.addAnnotation(manualRedactionEntry, file); + return this.addAnnotation(manualRedactionEntry, dossierId, fileId); } // /manualRedaction/request/legalBasis - changeLegalBasis(annotationId: string, file: File, section: string, value: string, legalBasis: string, comment?: string) { - const mode: AnnotationActionMode = this._permissionsService.isApprover(this._dossier(file)) + changeLegalBasis( + annotationId: string, + dossierId: string, + fileId: string, + section: string, + value: string, + legalBasis: string, + comment?: string, + ) { + const mode: AnnotationActionMode = this._permissionsService.isApprover(this._dossier(dossierId)) ? 'change-legal-basis' : 'request-change-legal-basis'; - return this._makeRequest(mode, file, { annotationId, legalBasis, comment, section, value }); + return this._makeRequest(mode, dossierId, fileId, { annotationId, legalBasis, comment, section, value }); } // this wraps // /manualRedaction/redaction/legalBasisChange // /manualRedaction/request/recategorize - recategorizeImg(annotationId: string, file: File, type: string, comment: string) { - const mode: AnnotationActionMode = this._permissionsService.isApprover(this._dossier(file)) + recategorizeImg(annotationId: string, dossierId: string, fileId: string, type: string, comment: string) { + const mode: AnnotationActionMode = this._permissionsService.isApprover(this._dossier(dossierId)) ? 'recategorize-image' : 'request-image-recategorization'; - return this._makeRequest(mode, file, { annotationId, type, comment }); + return this._makeRequest(mode, dossierId, fileId, { annotationId, type, comment }); } // this wraps // /manualRedaction/redaction/recategorize // /manualRedaction/request/add - addAnnotation(manualRedactionEntry: IAddRedactionRequest, file: File) { - const mode: AnnotationActionMode = this._permissionsService.isApprover(this._dossier(file)) ? 'add' : 'suggest'; - return this._makeRequest(mode, file, manualRedactionEntry, null, manualRedactionEntry.addToDictionary); + addAnnotation(manualRedactionEntry: IAddRedactionRequest, dossierId: string, fileId: string) { + const mode: AnnotationActionMode = this._permissionsService.isApprover(this._dossier(dossierId)) ? 'add' : 'suggest'; + return this._makeRequest(mode, dossierId, fileId, manualRedactionEntry, null, manualRedactionEntry.addToDictionary); } // this wraps // /manualRedaction/redaction/add // /manualRedaction/request/force - force(request: ILegalBasisChangeRequest, file: File) { - const mode: AnnotationActionMode = this._permissionsService.isApprover(this._dossier(file)) + force(request: ILegalBasisChangeRequest, dossierId: string, fileId: string) { + const mode: AnnotationActionMode = this._permissionsService.isApprover(this._dossier(dossierId)) ? 'force-redaction' : 'request-force-redaction'; - return this._makeRequest(mode, file, request); + return this._makeRequest(mode, dossierId, fileId, request); } // this wraps // /manualRedaction/redaction/force // /manualRedaction/approve - approve(annotationId: string, file: File, addToDictionary: boolean = false) { + approve(annotationId: string, dossierId: string, fileId: string, addToDictionary: boolean = false) { // for only here - approve the request - return this._makeRequest('approve', file, { addOrRemoveFromDictionary: addToDictionary }, annotationId, addToDictionary); + return this._makeRequest( + 'approve', + dossierId, + fileId, + { addOrRemoveFromDictionary: addToDictionary }, + annotationId, + addToDictionary, + ); } // this wraps - - undoRequest(annotationWrapper: AnnotationWrapper, file: File) { - return this._makeRequest('undo', file, annotationWrapper.id, null, annotationWrapper.isModifyDictionary); - } - // /manualRedaction/undo - declineOrRemoveRequest(annotationWrapper: AnnotationWrapper, file: File) { - const mode: AnnotationActionMode = this._permissionsService.isApprover(this._dossier(file)) ? 'decline' : 'undo'; - return this._makeRequest(mode, file, annotationWrapper.id, null, annotationWrapper.isModifyDictionary); + undoRequest(annotationWrapper: AnnotationWrapper, dossierId: string, fileId: string) { + return this._makeRequest('undo', dossierId, fileId, annotationWrapper.id, null, annotationWrapper.isModifyDictionary); } // this wraps // /manualRedaction/decline/remove + declineOrRemoveRequest(annotationWrapper: AnnotationWrapper, dossierId: string, fileId: string) { + const mode: AnnotationActionMode = this._permissionsService.isApprover(this._dossier(dossierId)) ? 'decline' : 'undo'; + return this._makeRequest(mode, dossierId, fileId, annotationWrapper.id, null, annotationWrapper.isModifyDictionary); + } // /manualRedaction/request/resize/ - resizeOrSuggestToResize(annotationWrapper: AnnotationWrapper, file: File, resizeRequest: IResizeRequest) { - const mode: AnnotationActionMode = this._permissionsService.isApprover(this._dossier(file)) ? 'resize' : 'request-resize'; - return this._makeRequest(mode, file, resizeRequest); + resizeOrSuggestToResize(annotationWrapper: AnnotationWrapper, dossierId: string, fileId: string, resizeRequest: IResizeRequest) { + const mode: AnnotationActionMode = this._permissionsService.isApprover(this._dossier(dossierId)) ? 'resize' : 'request-resize'; + return this._makeRequest(mode, dossierId, fileId, resizeRequest); } // this wraps @@ -183,7 +197,8 @@ export class ManualAnnotationService extends GenericService // /manualRedaction/request/remove/ removeOrSuggestRemoveAnnotation( annotationWrapper: AnnotationWrapper, - file: File, + dossierId: string, + fileId: string, removeFromDictionary: boolean = false, comment: string, ) { @@ -191,7 +206,7 @@ export class ManualAnnotationService extends GenericService body: any, removeDict = false; - if (this._permissionsService.isApprover(this._dossier(file))) { + if (this._permissionsService.isApprover(this._dossier(dossierId))) { // if it was something manual simply decline the existing request mode = 'remove'; body = { @@ -210,7 +225,7 @@ export class ManualAnnotationService extends GenericService removeDict = removeFromDictionary; } - return this._makeRequest(mode, file, body, null, removeDict); + return this._makeRequest(mode, dossierId, fileId, body, null, removeDict); } // this wraps @@ -353,8 +368,8 @@ export class ManualAnnotationService extends GenericService return this._post(body, url); } - private _dossier(file: File): Dossier { - return this._dossiersService.find(file.dossierId); + private _dossier(dossierId: string): Dossier { + return this._dossiersService.find(dossierId); } private _getMessage(mode: AnnotationActionMode, modifyDictionary?: boolean, error = false, isConflict = false) { 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 bdf4a81d6..0a0313f4b 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 @@ -1,6 +1,6 @@ import { Injectable } from '@angular/core'; -import { forkJoin, Observable, of } from 'rxjs'; -import { catchError, map, tap } from 'rxjs/operators'; +import { forkJoin, Observable, of, switchMap } from 'rxjs'; +import { catchError, map, take, tap } from 'rxjs/operators'; import { FileDataModel } from '@models/file/file-data.model'; import { PermissionsService } from '@services/permissions.service'; import { File, IRedactionLog, IViewedPage } from '@red/domain'; @@ -32,19 +32,23 @@ export class PdfViewerDataService { ); } - loadDataFor(file: File): Observable { - const fileData = this._stateService.fileData; - const blob$ = fileData?.file.cacheIdentifier === file.cacheIdentifier ? of(fileData.blob$.value) : this.downloadOriginalFile(file); - const redactionLog$ = this.loadRedactionLogFor(file.dossierId, file.fileId); - const viewedPages$ = this.getViewedPagesFor(file); + loadDataFor(newFile: File): Observable { + const oldBlob$ = this._stateService.fileData?.blob$; + const blob$ = this._stateService.file$.pipe( + map(file => file.cacheIdentifier === newFile.cacheIdentifier && oldBlob$), + switchMap(isSame => (isSame ? oldBlob$ : this.downloadOriginalFile(newFile))), + take(1), + ); + const redactionLog$ = this.loadRedactionLogFor(newFile.dossierId, newFile.fileId); + const viewedPages$ = this.getViewedPagesFor(newFile); - const dossier = this._dossiersService.find(file.dossierId); + const dossier = this._dossiersService.find(newFile.dossierId); return forkJoin([blob$, redactionLog$, viewedPages$]).pipe( map( (data: [blob: Blob, redactionLog: IRedactionLog, viewedPages: IViewedPage[]]) => new FileDataModel( - file, + newFile, ...data, this._appStateService.dictionaryData[dossier.dossierTemplateId], this._userPreferenceService.areDevFeaturesEnabled, @@ -60,7 +64,7 @@ export class PdfViewerDataService { return of([]); } - downloadOriginalFile(file: File): Observable { + downloadOriginalFile(file: File): Observable { return this._fileManagementService.downloadOriginalFile(file.dossierId, file.fileId, 'body', true, file.cacheIdentifier); } } diff --git a/apps/red-ui/src/app/modules/dossier/utils/pdf-viewer.utils.ts b/apps/red-ui/src/app/modules/dossier/utils/pdf-viewer.utils.ts index c903ad0c2..6b3507a45 100644 --- a/apps/red-ui/src/app/modules/dossier/utils/pdf-viewer.utils.ts +++ b/apps/red-ui/src/app/modules/dossier/utils/pdf-viewer.utils.ts @@ -138,7 +138,7 @@ export class PdfViewerUtils { this._annotationManager.deselectAnnotations(ann); } - private _navigateToPage(pageNumber) { + private _navigateToPage(pageNumber: number) { if (this._currentInternalPage !== pageNumber) { this._documentViewer.displayPageLocation(pageNumber, 0, 0); } diff --git a/apps/red-ui/src/app/services/entity-services/files-map.service.ts b/apps/red-ui/src/app/services/entity-services/files-map.service.ts index 6f3232221..151c33723 100644 --- a/apps/red-ui/src/app/services/entity-services/files-map.service.ts +++ b/apps/red-ui/src/app/services/entity-services/files-map.service.ts @@ -2,6 +2,7 @@ import { Injectable } from '@angular/core'; import { BehaviorSubject, Observable, Subject } from 'rxjs'; import { File } from '@red/domain'; import { filter, startWith } from 'rxjs/operators'; +import { shareLast } from '@iqser/common-ui'; @Injectable({ providedIn: 'root' }) export class FilesMapService { @@ -37,7 +38,7 @@ export class FilesMapService { return entities.forEach(entity => this._entityChanged$.next(entity)); } - const changedEntities = []; + const changedEntities: File[] = []; const deletedEntities = this.get(key).filter(oldEntity => !entities.find(newEntity => newEntity.id === oldEntity.id)); // Keep old object references for unchanged entities @@ -77,6 +78,7 @@ export class FilesMapService { return this._entityChanged$.pipe( filter(entity => entity.id === entityId), startWith(this.get(key, entityId)), + shareLast(), ); } diff --git a/apps/red-ui/src/app/services/permissions.service.ts b/apps/red-ui/src/app/services/permissions.service.ts index 55eeaf96b..a69c5e6a7 100644 --- a/apps/red-ui/src/app/services/permissions.service.ts +++ b/apps/red-ui/src/app/services/permissions.service.ts @@ -18,7 +18,8 @@ export class PermissionsService { return this.isApprover(dossier); } - canEditFileAttributes(dossier: Dossier, file: File): boolean { + canEditFileAttributes(file: File): boolean { + const dossier = this._getDossier(file); return ((file.isUnderReview || file.isNew) && this.isDossierMember(dossier)) || (file.isUnderApproval && this.isApprover(dossier)); } diff --git a/apps/red-ui/src/app/utils/filter-utils.ts b/apps/red-ui/src/app/utils/filter-utils.ts index 073584694..1b08223df 100644 --- a/apps/red-ui/src/app/utils/filter-utils.ts +++ b/apps/red-ui/src/app/utils/filter-utils.ts @@ -23,8 +23,6 @@ export function handleFilterDelta(oldFilters: INestedFilter[], newFilters: INest } } - console.log(newFiltersDelta); - for (const key of Object.keys(newFiltersDelta)) { const foundFilter = allFilters.find(f => f.id === key); if (foundFilter) {