diff --git a/apps/red-ui/src/app/common/file-actions/file-actions.component.html b/apps/red-ui/src/app/common/file-actions/file-actions.component.html index 09531137e..926aec0c3 100644 --- a/apps/red-ui/src/app/common/file-actions/file-actions.component.html +++ b/apps/red-ui/src/app/common/file-actions/file-actions.component.html @@ -1,80 +1,96 @@ - +
+ +
+ +
-
- + + + + + + + + +
- - - - - - diff --git a/apps/red-ui/src/app/common/file-actions/file-actions.component.scss b/apps/red-ui/src/app/common/file-actions/file-actions.component.scss index e69de29bb..36fe9c897 100644 --- a/apps/red-ui/src/app/common/file-actions/file-actions.component.scss +++ b/apps/red-ui/src/app/common/file-actions/file-actions.component.scss @@ -0,0 +1,3 @@ +.file-actions { + display: flex; +} diff --git a/apps/red-ui/src/app/common/file-actions/file-actions.component.ts b/apps/red-ui/src/app/common/file-actions/file-actions.component.ts index ff6c849e0..985c79cc7 100644 --- a/apps/red-ui/src/app/common/file-actions/file-actions.component.ts +++ b/apps/red-ui/src/app/common/file-actions/file-actions.component.ts @@ -1,27 +1,51 @@ -import { Component, Input, OnInit, Output, EventEmitter } from '@angular/core'; +import { AfterViewInit, Component, EventEmitter, Input, OnInit, Output, ViewChild } from '@angular/core'; import { PermissionsService } from '../service/permissions.service'; import { FileStatusWrapper } from '../../screens/file/model/file-status.wrapper'; import { DialogService } from '../../dialogs/dialog.service'; import { AppStateService } from '../../state/app-state.service'; import { FileActionService } from '../../screens/file/service/file-action.service'; +import { MatTooltip } from '@angular/material/tooltip'; @Component({ selector: 'redaction-file-actions', templateUrl: './file-actions.component.html', styleUrls: ['./file-actions.component.scss'] }) -export class FileActionsComponent implements OnInit { +export class FileActionsComponent implements OnInit, AfterViewInit { @Input() fileStatus: FileStatusWrapper; @Output() actionPerformed = new EventEmitter(); + @ViewChild('reanalyseTooltip') private _reanalyseTooltip: MatTooltip; + + screen: 'file-preview' | 'project-overview'; constructor( public readonly permissionsService: PermissionsService, + public readonly appStateService: AppStateService, private readonly _dialogService: DialogService, - private readonly _appStateService: AppStateService, private readonly _fileActionService: FileActionService ) {} - ngOnInit(): void {} + ngOnInit(): void { + if (!this.fileStatus) { + this.fileStatus = this.appStateService.activeFile; + this.screen = 'file-preview'; + this.appStateService.fileChanged.subscribe((fileStatus: FileStatusWrapper) => { + if (fileStatus.fileId === this.fileStatus?.fileId) { + this.fileStatus = this.appStateService.activeFile; + } + }); + } else { + this.screen = 'project-overview'; + } + } + + ngAfterViewInit(): void { + setTimeout(() => { + if (this._reanalyseTooltip) { + this._reanalyseTooltip.show(); + } + }, 1000); + } openDeleteFileDialog($event: MouseEvent, fileStatusWrapper: FileStatusWrapper) { this._dialogService.openDeleteFileDialog($event, fileStatusWrapper.projectId, fileStatusWrapper.fileId, () => { @@ -31,7 +55,7 @@ export class FileActionsComponent implements OnInit { downloadFileRedactionReport($event: MouseEvent, file: FileStatusWrapper) { $event.stopPropagation(); - this._appStateService.downloadFileRedactionReport(file); + this.appStateService.downloadFileRedactionReport(file); } assignReviewer($event: MouseEvent, file: FileStatusWrapper) { @@ -67,8 +91,8 @@ export class FileActionsComponent implements OnInit { }); } - public reloadProjects(action: string) { - this._appStateService.getFiles().then(() => { + reloadProjects(action: string) { + this.appStateService.getFiles().then(() => { this.actionPerformed.emit(action); }); } diff --git a/apps/red-ui/src/app/common/filter/utils/filter-utils.ts b/apps/red-ui/src/app/common/filter/utils/filter-utils.ts index 7cb7d2d02..8942631b6 100644 --- a/apps/red-ui/src/app/common/filter/utils/filter-utils.ts +++ b/apps/red-ui/src/app/common/filter/utils/filter-utils.ts @@ -1,6 +1,6 @@ import { FilterModel } from '../model/filter.model'; import { FileStatusWrapper } from '../../../screens/file/model/file-status.wrapper'; -import { ProjectWrapper } from '../../../state/app-state.service'; +import { ProjectWrapper } from '../../../state/model/project.wrapper'; export const RedactionFilterSorter = { hint: 1, diff --git a/apps/red-ui/src/app/common/service/permissions.service.ts b/apps/red-ui/src/app/common/service/permissions.service.ts index 829dd6207..13208cbbf 100644 --- a/apps/red-ui/src/app/common/service/permissions.service.ts +++ b/apps/red-ui/src/app/common/service/permissions.service.ts @@ -19,22 +19,34 @@ export class PermissionsService { } isReviewerOrOwner(fileStatus?: FileStatusWrapper, user?: User) { - return this.isActiveFileDocumentReviewer() || this.isManagerAndOwner(); + return this.isFileReviewer(fileStatus) || this.isManagerAndOwner(); + } + + fileRequiresReanalysis(fileStatus?: FileStatusWrapper) { + if (!fileStatus) { + fileStatus = this._appStateService.activeFile; + } + return ( + ((fileStatus.status === 'UNASSIGNED' || fileStatus.status === 'UNDER_REVIEW' || fileStatus.status === 'UNDER_APPROVAL') && + (fileStatus.dictionaryVersion !== this._appStateService.dictionaryVersion || + fileStatus.rulesVersion !== this._appStateService.rulesVersion || + fileStatus.hasUnappliedSuggestions)) || + fileStatus.isError + ); } canReanalyseFile(fileStatus?: FileStatusWrapper) { if (!fileStatus) { fileStatus = this._appStateService.activeFile; } - // can reanalyse file if error, has requests not up to date with dictionary and is owner or reviewer - return ( - ((!fileStatus.isApproved && this._appStateService.fileNotUpToDateWithDictionary(fileStatus)) || fileStatus.isError || fileStatus.hasRequests) && - (this.isManagerAndOwner() || this.isActiveFileDocumentReviewer()) - ); + return this.fileRequiresReanalysis(fileStatus) && this.isReviewerOrOwner(fileStatus); } - isActiveFileDocumentReviewer() { - return this._appStateService.activeFile?.currentReviewer === this._userService.userId; + isFileReviewer(fileStatus?: FileStatusWrapper) { + if (!fileStatus) { + fileStatus = this._appStateService.activeFile; + } + return fileStatus.currentReviewer === this._userService.userId; } canDeleteFile(fileStatus?: FileStatusWrapper) { @@ -74,7 +86,7 @@ export class PermissionsService { user = this._userService.user; } if (!project) { - project = this._appStateService.activeProject; + project = this._appStateService.activeProject.project; } return user.isManager && project.ownerId === user.id; } @@ -84,7 +96,7 @@ export class PermissionsService { user = this._userService.user; } if (!project) { - project = this._appStateService.activeProject; + project = this._appStateService.activeProject.project; } return project.memberIds?.includes(user.id); } @@ -93,14 +105,14 @@ export class PermissionsService { if (!fileStatus) { fileStatus = this._appStateService.activeFile; } - return (fileStatus.status === 'UNDER_APPROVAL' || fileStatus.status === 'UNDER_REVIEW') && this._userService.userId === fileStatus.currentReviewer; + return (fileStatus.status === 'UNDER_APPROVAL' || fileStatus.status === 'UNDER_REVIEW') && this.isFileReviewer(fileStatus); } public canOpenFile(fileStatus: FileStatusWrapper) { if (!fileStatus) { fileStatus = this._appStateService.activeFile; } - return !fileStatus.isError && !fileStatus.isProcessing; + return !fileStatus.isError && !fileStatus.isProcessing && this.isReviewerOrOwner(fileStatus); } canShowRedactionReportDownloadBtn(fileStatus?: FileStatusWrapper) { @@ -114,7 +126,21 @@ export class PermissionsService { return this.isProjectMember() && !fileStatus.isError && !fileStatus.isApprovedOrUnderApproval; } - canUndoApproval(fileStatus: any) {} + canUndoApproval(fileStatus: FileStatusWrapper) { + if (!fileStatus) { + fileStatus = this._appStateService.activeFile; + } + return fileStatus.status === 'APPROVED' && this.isManagerAndOwner(); + } - canUndoUnderApproval(fileStatus: any) {} + canUndoUnderApproval(fileStatus: any) { + if (!fileStatus) { + fileStatus = this._appStateService.activeFile; + } + return fileStatus.status === 'UNDER_APPROVAL' && this.isManagerAndOwner(); + } + + canMarkPagesAsViewed() { + return this.isReviewerOrOwner(); + } } diff --git a/apps/red-ui/src/app/dialogs/manual-redaction-dialog/manual-annotation-dialog.component.ts b/apps/red-ui/src/app/dialogs/manual-redaction-dialog/manual-annotation-dialog.component.ts index d1b57c1c4..24d2008f4 100644 --- a/apps/red-ui/src/app/dialogs/manual-redaction-dialog/manual-annotation-dialog.component.ts +++ b/apps/red-ui/src/app/dialogs/manual-redaction-dialog/manual-annotation-dialog.component.ts @@ -9,6 +9,7 @@ import { UserService } from '../../user/user.service'; import { ManualRedactionEntryWrapper } from '../../screens/file/model/manual-redaction-entry.wrapper'; import { ManualAnnotationService } from '../../screens/file/service/manual-annotation.service'; import { ManualAnnotationResponse } from '../../screens/file/model/manual-annotation-response'; +import { PermissionsService } from '../../common/service/permissions.service'; @Component({ selector: 'redaction-manual-annotation-dialog', @@ -30,12 +31,13 @@ export class ManualAnnotationDialogComponent implements OnInit { private readonly _notificationService: NotificationService, private readonly _translateService: TranslateService, private readonly _manualAnnotationService: ManualAnnotationService, + private readonly _permissionsService: PermissionsService, public dialogRef: MatDialogRef, @Inject(MAT_DIALOG_DATA) public manualRedactionEntryWrapper: ManualRedactionEntryWrapper ) {} async ngOnInit() { - this.isDocumentAdmin = this._appStateService.isActiveProjectOwnerAndManager; + this.isDocumentAdmin = this._permissionsService.isManagerAndOwner(); const commentField = this.isDocumentAdmin ? [null] : [null, Validators.required]; this.isDictionaryRequest = this.manualRedactionEntryWrapper.type === 'DICTIONARY'; @@ -60,18 +62,14 @@ export class ManualAnnotationDialogComponent implements OnInit { handleAddRedaction() { this._enhanceManualRedaction(this.manualRedactionEntryWrapper.manualRedactionEntry); - this._manualAnnotationService - .addAnnotation(this.manualRedactionEntryWrapper.manualRedactionEntry) - .subscribe( - (response) => { - this.dialogRef.close( - new ManualAnnotationResponse(this.manualRedactionEntryWrapper, response) - ); - }, - () => { - this.dialogRef.close(); - } - ); + this._manualAnnotationService.addAnnotation(this.manualRedactionEntryWrapper.manualRedactionEntry).subscribe( + (response) => { + this.dialogRef.close(new ManualAnnotationResponse(this.manualRedactionEntryWrapper, response)); + }, + () => { + this.dialogRef.close(); + } + ); } get title() { @@ -80,8 +78,7 @@ export class ManualAnnotationDialogComponent implements OnInit { private _enhanceManualRedaction(addRedactionRequest: AddRedactionRequest) { addRedactionRequest.type = this.redactionForm.get('dictionary').value; - addRedactionRequest.addToDictionary = - this.manualRedactionEntryWrapper.type === 'DICTIONARY'; + addRedactionRequest.addToDictionary = this.manualRedactionEntryWrapper.type === 'DICTIONARY'; addRedactionRequest.reason = this.redactionForm.get('reason').value; // todo fix this in backend if (!addRedactionRequest.reason) { diff --git a/apps/red-ui/src/app/screens/file/file-preview-screen/file-preview-screen.component.html b/apps/red-ui/src/app/screens/file/file-preview-screen/file-preview-screen.component.html index a72e578af..2aad64b0a 100644 --- a/apps/red-ui/src/app/screens/file/file-preview-screen/file-preview-screen.component.html +++ b/apps/red-ui/src/app/screens/file/file-preview-screen/file-preview-screen.component.html @@ -12,14 +12,14 @@
- +  {{ appStateService.activeFile.filename }}
- + diff --git a/apps/red-ui/src/app/screens/file/file-preview-screen/file-preview-screen.component.scss b/apps/red-ui/src/app/screens/file/file-preview-screen/file-preview-screen.component.scss index 50d6755fc..2058d434a 100644 --- a/apps/red-ui/src/app/screens/file/file-preview-screen/file-preview-screen.component.scss +++ b/apps/red-ui/src/app/screens/file/file-preview-screen/file-preview-screen.component.scss @@ -20,6 +20,7 @@ redaction-pdf-viewer { display: flex; justify-content: center; align-items: center; + gap: 4px; } .right-fixed-container { diff --git a/apps/red-ui/src/app/screens/file/file-preview-screen/file-preview-screen.component.ts b/apps/red-ui/src/app/screens/file/file-preview-screen/file-preview-screen.component.ts index 380a988b3..0cce2eb53 100644 --- a/apps/red-ui/src/app/screens/file/file-preview-screen/file-preview-screen.component.ts +++ b/apps/red-ui/src/app/screens/file/file-preview-screen/file-preview-screen.component.ts @@ -40,7 +40,6 @@ export class FilePreviewScreenComponent implements OnInit { @ViewChild(PdfViewerComponent) private _viewerComponent: PdfViewerComponent; @ViewChild('annotationsElement') private _annotationsElement: ElementRef; @ViewChild('quickNavigation') private _quickNavigationElement: ElementRef; - @ViewChild('reanalyseTooltip') private _reanalyseTooltip: MatTooltip; fileData: FileDataModel; fileId: string; @@ -98,17 +97,19 @@ export class FilePreviewScreenComponent implements OnInit { } get canNotSwitchToRedactedView() { - return this.appStateService.fileNotUpToDateWithDictionary() || this.fileData?.entriesToAdd?.length > 0; + return this.permissionsService.fileRequiresReanalysis(); } ngOnInit(): void { - this.canPerformAnnotationActions = this.permissionsService.canPerformAnnotationActions(); - this._loadFileData().subscribe(() => {}); + this._loadFileData().subscribe(() => { + this.canPerformAnnotationActions = this.permissionsService.canPerformAnnotationActions(); + }); this.appStateService.fileReanalysed.subscribe((fileStatus: FileStatusWrapper) => { if (fileStatus.fileId === this.fileId) { this._loadFileData().subscribe(() => { this.viewReady = true; this.loadingMessage = null; + this.canPerformAnnotationActions = this.permissionsService.canPerformAnnotationActions(); this._cleanupAndRedrawManualAnnotations(); }); } @@ -353,9 +354,6 @@ export class FilePreviewScreenComponent implements OnInit { viewerReady($event: WebViewerInstance) { this.instance = $event; this.viewReady = true; - if (this._reanalyseTooltip) { - this._reanalyseTooltip.show(); - } this._cleanupAndRedrawManualAnnotations(); } diff --git a/apps/red-ui/src/app/screens/file/model/file-status.wrapper.ts b/apps/red-ui/src/app/screens/file/model/file-status.wrapper.ts index 9789d529d..e99c415bc 100644 --- a/apps/red-ui/src/app/screens/file/model/file-status.wrapper.ts +++ b/apps/red-ui/src/app/screens/file/model/file-status.wrapper.ts @@ -11,9 +11,8 @@ export class FileStatusWrapper { return this.fileStatus.added; } - // TODO use this for suggestions - get allManualRedactionsApplied() { - return this.fileStatus.allManualRedactionsApplied; + get hasUnappliedSuggestions() { + return !this.fileStatus.allManualRedactionsApplied; } get currentReviewer() { diff --git a/apps/red-ui/src/app/screens/file/page-indicator/page-indicator.component.ts b/apps/red-ui/src/app/screens/file/page-indicator/page-indicator.component.ts index 4ece4d33c..c34b856fe 100644 --- a/apps/red-ui/src/app/screens/file/page-indicator/page-indicator.component.ts +++ b/apps/red-ui/src/app/screens/file/page-indicator/page-indicator.component.ts @@ -1,15 +1,7 @@ -import { - Component, - EventEmitter, - HostListener, - Input, - OnChanges, - OnInit, - Output, - SimpleChanges -} from '@angular/core'; +import { Component, EventEmitter, HostListener, Input, OnChanges, OnInit, Output, SimpleChanges } from '@angular/core'; import { ViewedPages, ViewedPagesControllerService } from '@redaction/red-ui-http'; import { AppStateService } from '../../../state/app-state.service'; +import { PermissionsService } from '../../../common/service/permissions.service'; @Component({ selector: 'redaction-page-indicator', @@ -28,11 +20,12 @@ export class PageIndicatorComponent implements OnChanges, OnInit { constructor( private readonly _viewedPagesControllerService: ViewedPagesControllerService, - private readonly _appStateService: AppStateService + private readonly _appStateService: AppStateService, + private readonly _permissionService: PermissionsService ) {} ngOnInit(): void { - this.canMarkPagesAsViewed = this._appStateService.canMarkPagesAsViewedForActiveFile; + this.canMarkPagesAsViewed = this._permissionService.canMarkPagesAsViewed(); } ngOnChanges(changes: SimpleChanges): void { @@ -62,26 +55,16 @@ export class PageIndicatorComponent implements OnChanges, OnInit { private _markPageRead() { this._viewedPagesControllerService - .addPage( - { page: this.number }, - this._appStateService.activeProjectId, - this._appStateService.activeFileId - ) + .addPage({ page: this.number }, this._appStateService.activeProjectId, this._appStateService.activeFileId) .subscribe(() => { this.viewedPages?.pages?.push(this.number); }); } private _markPageUnread() { - this._viewedPagesControllerService - .removePage( - this._appStateService.activeProjectId, - this._appStateService.activeFileId, - this.number - ) - .subscribe(() => { - this.viewedPages?.pages?.splice(this.viewedPages?.pages?.indexOf(this.number), 1); - }); + this._viewedPagesControllerService.removePage(this._appStateService.activeProjectId, this._appStateService.activeFileId, this.number).subscribe(() => { + this.viewedPages?.pages?.splice(this.viewedPages?.pages?.indexOf(this.number), 1); + }); } // @HostListener('window:keydown', ['$event']) diff --git a/apps/red-ui/src/app/screens/file/pdf-viewer/pdf-viewer.component.ts b/apps/red-ui/src/app/screens/file/pdf-viewer/pdf-viewer.component.ts index 0f65cf2a5..c0442132b 100644 --- a/apps/red-ui/src/app/screens/file/pdf-viewer/pdf-viewer.component.ts +++ b/apps/red-ui/src/app/screens/file/pdf-viewer/pdf-viewer.component.ts @@ -1,16 +1,4 @@ -import { - AfterViewInit, - Component, - ElementRef, - EventEmitter, - Input, - NgZone, - OnChanges, - OnInit, - Output, - SimpleChanges, - ViewChild -} from '@angular/core'; +import { AfterViewInit, Component, ElementRef, EventEmitter, Input, NgZone, OnChanges, OnInit, Output, SimpleChanges, ViewChild } from '@angular/core'; import { AppConfigKey, AppConfigService } from '../../../app-config/app-config.service'; import { ManualRedactionEntry, Rectangle } from '@redaction/red-ui-http'; import WebViewer, { WebViewerInstance } from '@pdftron/webviewer'; @@ -171,36 +159,20 @@ export class PdfViewerComponent implements OnInit, AfterViewInit, OnChanges { type: 'actionButton', dataElement: 'add-dictionary', img: '/assets/icons/general/add-dictionary.svg', - title: this._translateService.instant( - this._manualAnnotationService.getTitle('DICTIONARY') - ), + title: this._translateService.instant(this._manualAnnotationService.getTitle('DICTIONARY')), onClick: () => { const mre = this._getManualRedactionEntry(); - this.manualAnnotationRequested.emit( - new ManualRedactionEntryWrapper( - this.instance.docViewer.getSelectedTextQuads(), - mre, - 'DICTIONARY' - ) - ); + this.manualAnnotationRequested.emit(new ManualRedactionEntryWrapper(this.instance.docViewer.getSelectedTextQuads(), mre, 'DICTIONARY')); } }); this.instance.textPopup.add({ type: 'actionButton', dataElement: 'add-redaction', img: '/assets/icons/general/add-redaction.svg', - title: this._translateService.instant( - this._manualAnnotationService.getTitle('REDACTION') - ), + title: this._translateService.instant(this._manualAnnotationService.getTitle('REDACTION')), onClick: () => { const mre = this._getManualRedactionEntry(); - this.manualAnnotationRequested.emit( - new ManualRedactionEntryWrapper( - this.instance.docViewer.getSelectedTextQuads(), - mre, - 'REDACTION' - ) - ); + this.manualAnnotationRequested.emit(new ManualRedactionEntryWrapper(this.instance.docViewer.getSelectedTextQuads(), mre, 'REDACTION')); } }); this._handleCustomActions(); @@ -292,14 +264,18 @@ export class PdfViewerComponent implements OnInit, AfterViewInit, OnChanges { } private _restoreState(viewerState: ViewerState, instance: WebViewerInstance) { - if (this._viewerState) { + if (viewerState) { instance.docViewer.setCurrentPage(viewerState.pageNumber); instance.setLayoutMode(viewerState.layoutMode); const instanceDisplayMode = instance.docViewer.getDisplayModeManager().getDisplayMode(); instanceDisplayMode.mode = viewerState.displayMode; instance.docViewer.getDisplayModeManager().setDisplayMode(instanceDisplayMode); // Synchronize zoom - needs to be done before scrolling - instance.docViewer.zoomTo(viewerState.zoom); + if (viewerState.zoom === 0) { + this.instance.setFitMode('FitPage'); + } else { + instance.docViewer.zoomTo(viewerState.zoom); + } const viewerScrollElement = instance.docViewer.getScrollViewElement(); viewerScrollElement.scrollTo(viewerState.scrollLeft, viewerState.scrollTop); diff --git a/apps/red-ui/src/app/screens/file/service/file-download.service.ts b/apps/red-ui/src/app/screens/file/service/file-download.service.ts index 69b6b0e6f..d65e149e2 100644 --- a/apps/red-ui/src/app/screens/file/service/file-download.service.ts +++ b/apps/red-ui/src/app/screens/file/service/file-download.service.ts @@ -4,11 +4,13 @@ import { map, tap } from 'rxjs/operators'; import { FileUploadControllerService, ManualRedactionControllerService, - RedactionLogControllerService + RedactionLogControllerService, + ViewedPagesControllerService } from '@redaction/red-ui-http'; import { FileType } from '../model/file-type'; import { FileDataModel } from '../model/file-data.model'; import { AppStateService } from '../../../state/app-state.service'; +import { PermissionsService } from '../../../common/service/permissions.service'; @Injectable({ providedIn: 'root' @@ -16,80 +18,69 @@ import { AppStateService } from '../../../state/app-state.service'; export class FileDownloadService { constructor( private readonly _appStateService: AppStateService, + private readonly _permissionsService: PermissionsService, private readonly _fileUploadControllerService: FileUploadControllerService, private readonly _manualRedactionControllerService: ManualRedactionControllerService, - private readonly _redactionLogControllerService: RedactionLogControllerService + private readonly _redactionLogControllerService: RedactionLogControllerService, + private readonly _viewedPagesControllerService: ViewedPagesControllerService ) {} public loadActiveFileManualAnnotations() { - return this._manualRedactionControllerService.getManualRedaction( - this._appStateService.activeProjectId, - this._appStateService.activeFileId - ); + return this._manualRedactionControllerService.getManualRedaction(this._appStateService.activeProjectId, this._appStateService.activeFileId); } public loadActiveFileData(): Observable { const annotatedObs = this.loadFile('ANNOTATED', this._appStateService.activeFileId); const redactedObs = this.loadFile('REDACTED', this._appStateService.activeFileId); - const reactionLogObs = this._redactionLogControllerService.getRedactionLog( - this._appStateService.activeFileId - ); + const reactionLogObs = this._redactionLogControllerService.getRedactionLog(this._appStateService.activeFileId); const manualRedactionsObs = this._manualRedactionControllerService.getManualRedaction( this._appStateService.activeProjectId, this._appStateService.activeFileId ); - const viewedPagesObs = this._appStateService.getViewedPagesForActiveFile(); + const viewedPagesObs = this.getViewedPagesForActiveFile(); - return forkJoin([ - annotatedObs, - redactedObs, - reactionLogObs, - manualRedactionsObs, - viewedPagesObs - ]).pipe(map((data) => new FileDataModel(this._appStateService.activeFile, ...data))); + return forkJoin([annotatedObs, redactedObs, reactionLogObs, manualRedactionsObs, viewedPagesObs]).pipe( + map((data) => new FileDataModel(this._appStateService.activeFile, ...data)) + ); } - loadFile( - fileType: FileType | string, - fileId: string, - saveTo: (fileData) => void = () => null, - fetch: () => any = () => null - ): Observable { + getViewedPagesForActiveFile() { + if (this._permissionsService.canMarkPagesAsViewed()) { + return this._viewedPagesControllerService.getViewedPages(this._appStateService.activeProjectId, this._appStateService.activeFileId); + } + return of({ pages: [] }); + } + + loadFile(fileType: FileType | string, fileId: string, saveTo: (fileData) => void = () => null, fetch: () => any = () => null): Observable { let fileObs$: Observable; switch (fileType) { case FileType.ANNOTATED: fileObs$ = fetch() ? of(fetch()) - : this._fileUploadControllerService - .downloadAnnotatedFile(fileId, true, 'body') - .pipe( - tap((data) => { - saveTo(data); - }) - ); + : this._fileUploadControllerService.downloadAnnotatedFile(fileId, true, 'body').pipe( + tap((data) => { + saveTo(data); + }) + ); break; case FileType.REDACTED: fileObs$ = fetch() ? of(fetch()) - : this._fileUploadControllerService - .downloadRedactedFile(fileId, true, 'body') - .pipe( - tap((data) => { - saveTo(data); - }) - ); + : this._fileUploadControllerService.downloadRedactedFile(fileId, true, 'body').pipe( + tap((data) => { + saveTo(data); + }) + ); break; case FileType.ORIGINAL: default: fileObs$ = fetch() ? of(fetch()) - : this._fileUploadControllerService - .downloadOriginalFile(fileId, true, 'body') - .pipe( - tap((data) => { - saveTo(data); - }) - ); + : this._fileUploadControllerService.downloadOriginalFile(fileId, true, 'body').pipe( + tap((data) => { + saveTo(data); + }) + ); break; } return fileObs$; diff --git a/apps/red-ui/src/app/screens/file/service/manual-annotation.service.ts b/apps/red-ui/src/app/screens/file/service/manual-annotation.service.ts index 5deeed715..4fc86e54f 100644 --- a/apps/red-ui/src/app/screens/file/service/manual-annotation.service.ts +++ b/apps/red-ui/src/app/screens/file/service/manual-annotation.service.ts @@ -1,15 +1,12 @@ import { Injectable } from '@angular/core'; import { AppStateService } from '../../../state/app-state.service'; -import { - DictionaryControllerService, - ManualRedactionControllerService, - ManualRedactionEntry -} from '@redaction/red-ui-http'; +import { DictionaryControllerService, ManualRedactionControllerService, ManualRedactionEntry } from '@redaction/red-ui-http'; import { AnnotationWrapper } from '../model/annotation.wrapper'; import { NotificationService, NotificationType } from '../../../notification/notification.service'; import { TranslateService } from '@ngx-translate/core'; import { tap } from 'rxjs/operators'; import { UserService } from '../../../user/user.service'; +import { PermissionsService } from '../../../common/service/permissions.service'; @Injectable({ providedIn: 'root' @@ -21,7 +18,8 @@ export class ManualAnnotationService { private readonly _translateService: TranslateService, private readonly _notificationService: NotificationService, private readonly _manualRedactionControllerService: ManualRedactionControllerService, - private readonly _dictionaryControllerService: DictionaryControllerService + private readonly _dictionaryControllerService: DictionaryControllerService, + private readonly _permissionsService: PermissionsService ) {} // Comments @@ -49,7 +47,7 @@ export class ManualAnnotationService { // /manualRedaction/redaction/add // /manualRedaction/request/add addAnnotation(manualRedactionEntry: ManualRedactionEntry) { - if (this._appStateService.isActiveProjectOwnerAndManager) { + if (this._permissionsService.isManagerAndOwner()) { return this._makeRedaction(manualRedactionEntry); } else { return this._makeRedactionRequest(manualRedactionEntry); @@ -81,19 +79,12 @@ export class ManualAnnotationService { undoRequest(annotationWrapper: AnnotationWrapper) { return this._manualRedactionControllerService - .undo( - this._appStateService.activeProjectId, - this._appStateService.activeFileId, - annotationWrapper.id - ) + .undo(this._appStateService.activeProjectId, this._appStateService.activeFileId, annotationWrapper.id) .pipe( tap( () => this._notify('manual-annotation.undo-request.success'), () => { - this._notify( - 'manual-annotation.undo-request.error', - NotificationType.ERROR - ); + this._notify('manual-annotation.undo-request.error', NotificationType.ERROR); } ) ); @@ -103,39 +94,25 @@ export class ManualAnnotationService { // /manualRedaction/decline/remove // /manualRedaction/undo declineOrRemoveRequest(annotationWrapper: AnnotationWrapper) { - if (this._appStateService.isActiveProjectOwnerAndManager) { + if (this._permissionsService.isManagerAndOwner()) { return this._manualRedactionControllerService - .declineRequest( - this._appStateService.activeProjectId, - this._appStateService.activeFileId, - annotationWrapper.id - ) + .declineRequest(this._appStateService.activeProjectId, this._appStateService.activeFileId, annotationWrapper.id) .pipe( tap( () => this._notify('manual-annotation.undo-request.success'), () => { - this._notify( - 'manual-annotation.undo-request.error', - NotificationType.ERROR - ); + this._notify('manual-annotation.undo-request.error', NotificationType.ERROR); } ) ); } else { return this._manualRedactionControllerService - .undo( - this._appStateService.activeProjectId, - this._appStateService.activeFileId, - annotationWrapper.id - ) + .undo(this._appStateService.activeProjectId, this._appStateService.activeFileId, annotationWrapper.id) .pipe( tap( () => this._notify('manual-annotation.undo-request.success'), () => { - this._notify( - 'manual-annotation.undo-request.error', - NotificationType.ERROR - ); + this._notify('manual-annotation.undo-request.error', NotificationType.ERROR); } ) ); @@ -145,11 +122,8 @@ export class ManualAnnotationService { // this wraps // /manualRedaction/redaction/remove/ // /manualRedaction/request/remove/ - removeOrSuggestRemoveAnnotation( - annotationWrapper: AnnotationWrapper, - removeFromDictionary: boolean = false - ) { - if (this._appStateService.isActiveProjectOwnerAndManager) { + removeOrSuggestRemoveAnnotation(annotationWrapper: AnnotationWrapper, removeFromDictionary: boolean = false) { + if (this._permissionsService.isManagerAndOwner()) { return this._manualRedactionControllerService .removeRedaction( { @@ -164,10 +138,7 @@ export class ManualAnnotationService { tap( () => this._notify('manual-annotation.remove-redaction-request.success'), () => { - this._notify( - 'manual-annotation.remove-redaction-request.error', - NotificationType.ERROR - ); + this._notify('manual-annotation.remove-redaction-request.error', NotificationType.ERROR); } ) ); @@ -186,10 +157,7 @@ export class ManualAnnotationService { tap( () => this._notify('manual-annotation.remove-redaction-request.success'), () => { - this._notify( - 'manual-annotation.remove-redaction-request.error', - NotificationType.ERROR - ); + this._notify('manual-annotation.remove-redaction-request.error', NotificationType.ERROR); } ) ); @@ -198,19 +166,12 @@ export class ManualAnnotationService { private _makeRedactionRequest(manualRedactionEntry: ManualRedactionEntry) { return this._manualRedactionControllerService - .requestAddRedaction( - manualRedactionEntry, - this._appStateService.activeProject.project.projectId, - this._appStateService.activeFile.fileId - ) + .requestAddRedaction(manualRedactionEntry, this._appStateService.activeProject.project.projectId, this._appStateService.activeFile.fileId) .pipe( tap( () => this._notify('manual-annotation.redaction-request.success'), () => { - this._notify( - 'manual-annotation.redaction-request.error', - NotificationType.ERROR - ); + this._notify('manual-annotation.redaction-request.error', NotificationType.ERROR); } ) ); @@ -218,34 +179,23 @@ export class ManualAnnotationService { private _makeRedaction(manualRedactionEntry: ManualRedactionEntry) { return this._manualRedactionControllerService - .addRedaction( - manualRedactionEntry, - this._appStateService.activeProject.project.projectId, - this._appStateService.activeFile.fileId - ) + .addRedaction(manualRedactionEntry, this._appStateService.activeProject.project.projectId, this._appStateService.activeFile.fileId) .pipe( tap( () => this._notify('manual-annotation.redaction-add.success'), () => { - this._notify( - 'manual-annotation.redaction-add.error', - NotificationType.ERROR - ); + this._notify('manual-annotation.redaction-add.error', NotificationType.ERROR); } ) ); } private _notify(key: string, type: NotificationType = NotificationType.SUCCESS) { - this._notificationService.showToastNotification( - this._translateService.instant(key), - null, - type - ); + this._notificationService.showToastNotification(this._translateService.instant(key), null, type); } getTitle(type: 'DICTIONARY' | 'REDACTION') { - if (this._appStateService.isActiveProjectOwnerAndManager) { + if (this._permissionsService.isManagerAndOwner()) { if (type === 'DICTIONARY') { return 'manual-redaction.dialog.header.dictionary'; } else { diff --git a/apps/red-ui/src/app/screens/project-listing-screen/project-listing-screen.component.ts b/apps/red-ui/src/app/screens/project-listing-screen/project-listing-screen.component.ts index c2e9410ad..af966198f 100644 --- a/apps/red-ui/src/app/screens/project-listing-screen/project-listing-screen.component.ts +++ b/apps/red-ui/src/app/screens/project-listing-screen/project-listing-screen.component.ts @@ -55,7 +55,7 @@ export class ProjectListingScreenComponent implements OnInit { public ngOnInit(): void { this.appStateService.reset(); this._calculateData(); - this.appStateService.fileStatusChanged.subscribe(() => { + this.appStateService.fileChanged.subscribe(() => { this._calculateData(); }); } diff --git a/apps/red-ui/src/app/screens/project-overview-screen/project-details/project-details.component.ts b/apps/red-ui/src/app/screens/project-overview-screen/project-details/project-details.component.ts index 95a3c318a..312fc2e7e 100644 --- a/apps/red-ui/src/app/screens/project-overview-screen/project-details/project-details.component.ts +++ b/apps/red-ui/src/app/screens/project-overview-screen/project-details/project-details.component.ts @@ -29,7 +29,7 @@ export class ProjectDetailsComponent implements OnInit { ngOnInit(): void { this.calculateChartConfig(); - this.appStateService.fileStatusChanged.subscribe((event) => { + this.appStateService.fileChanged.subscribe((event) => { this.calculateChartConfig(); }); } diff --git a/apps/red-ui/src/app/screens/project-overview-screen/project-overview-screen.component.html b/apps/red-ui/src/app/screens/project-overview-screen/project-overview-screen.component.html index 10a22c9eb..3c9f78d16 100644 --- a/apps/red-ui/src/app/screens/project-overview-screen/project-overview-screen.component.html +++ b/apps/red-ui/src/app/screens/project-overview-screen/project-overview-screen.component.html @@ -122,11 +122,7 @@
{{ fileStatus.filename }}
- +
@@ -163,7 +159,11 @@ >
- + { + this.appStateService.fileChanged.subscribe(() => { this.calculateData(); }); } @@ -71,43 +70,45 @@ export class ProjectOverviewScreenComponent implements OnInit, OnDestroy { ngOnInit(): void { this._fileDropOverlayService.initFileDropHandling(); this.calculateData(); - this._displayNewRuleToast(); + this._displayOutdatedToast(); } ngOnDestroy(): void { this._fileDropOverlayService.cleanupFileDropHandling(); } - private _displayNewRuleToast() { - // @ts-ignore - if (!this.appStateService.activeProject.files.filter((file) => this.appStateService.fileNotUpToDateWithDictionary(file)).length) { - return; - } - - this._notificationService.showToastNotification( - `${this._translateService.instant('project-overview.new-rule.toast.message-project')} ${this._translateService.instant( - 'project-overview.new-rule.label' - )}`, - null, - NotificationType.WARNING, - { - disableTimeOut: true, - positionClass: 'toast-top-left', - actions: [ - { - title: this._translateService.instant('project-overview.new-rule.toast.actions.reanalyse-all'), - action: () => - this.appStateService - .reanalyzeProject() - .toPromise() - .then(() => this.reloadProjects()) - }, - { - title: this._translateService.instant('project-overview.new-rule.toast.actions.later') - } - ] + private _displayOutdatedToast() { + if (this.permissionsService.isManagerAndOwner()) { + // @ts-ignore + if (!this.appStateService.activeProject.files.filter((file) => this.permissionsService.fileRequiresReanalysis(file)).length) { + return; } - ); + + this._notificationService.showToastNotification( + `${this._translateService.instant('project-overview.new-rule.toast.message-project')} ${this._translateService.instant( + 'project-overview.new-rule.label' + )}`, + null, + NotificationType.WARNING, + { + disableTimeOut: true, + positionClass: 'toast-top-left', + actions: [ + { + title: this._translateService.instant('project-overview.new-rule.toast.actions.reanalyse-all'), + action: () => + this.appStateService + .reanalyzeProject() + .toPromise() + .then(() => this.reloadProjects()) + }, + { + title: this._translateService.instant('project-overview.new-rule.toast.actions.later') + } + ] + } + ); + } } reloadProjects() { diff --git a/apps/red-ui/src/app/state/app-state.service.ts b/apps/red-ui/src/app/state/app-state.service.ts index d4fa48a1d..3d71c83ce 100644 --- a/apps/red-ui/src/app/state/app-state.service.ts +++ b/apps/red-ui/src/app/state/app-state.service.ts @@ -39,7 +39,7 @@ export interface AppState { export class AppStateService { private _appState: AppState; private _dictionaryData: { [key: string]: TypeValue } = null; - public fileStatusChanged = new EventEmitter(); + public fileChanged = new EventEmitter(); public fileReanalysed = new EventEmitter(); constructor( @@ -52,8 +52,7 @@ export class AppStateService { private readonly _translateService: TranslateService, private readonly _dictionaryControllerService: DictionaryControllerService, private readonly _statusControllerService: StatusControllerService, - private readonly _versionsControllerService: VersionsControllerService, - private readonly _viewedPagesControllerService: ViewedPagesControllerService + private readonly _versionsControllerService: VersionsControllerService ) { this._appState = { projects: [], @@ -98,10 +97,6 @@ export class AppStateService { return this._dictionaryData; } - getViewedPagesForActiveFile() { - return this._viewedPagesControllerService.getViewedPages(this.activeProjectId, this.activeFileId); - } - reanalyzeProject(project?: Project) { if (!project) { project = this.activeProject.project; @@ -206,13 +201,12 @@ export class AppStateService { for (const oldFile of oldFiles) { if (oldFile.fileId === file.fileId) { // emit when analysis count changed - if (oldFile.lastUpdated !== file.lastUpdated) { - const fileStatusWrapper = new FileStatusWrapper(file, this._userService.getNameForId(file.currentReviewer)); + const fileStatusWrapper = new FileStatusWrapper(file, this._userService.getNameForId(file.currentReviewer)); + if (JSON.stringify(oldFile) !== JSON.stringify(fileStatusWrapper)) { fileStatusChangedEvent.push(fileStatusWrapper); - - if (oldFile.lastProcessed !== file.lastProcessed) { - fileReanalysedEvent.push(fileStatusWrapper); - } + } + if (oldFile.lastProcessed !== file.lastProcessed) { + fileReanalysedEvent.push(fileStatusWrapper); } found = true; break; @@ -230,7 +224,7 @@ export class AppStateService { this._computeStats(); fileReanalysedEvent.forEach((file) => this.fileReanalysed.emit(file)); - fileStatusChangedEvent.forEach((file) => this.fileStatusChanged.emit(file)); + fileStatusChangedEvent.forEach((file) => this.fileChanged.emit(file)); return files; } @@ -429,16 +423,6 @@ export class AppStateService { } } - fileNotUpToDateWithDictionary(fileStatus?: FileStatusWrapper) { - if (!fileStatus) { - fileStatus = this.activeFile; - } - return ( - (fileStatus.status === 'UNASSIGNED' || fileStatus.status === 'UNDER_REVIEW' || fileStatus.status === 'UNDER_APPROVAL') && - (fileStatus.dictionaryVersion !== this.dictionaryVersion || fileStatus.rulesVersion !== this.rulesVersion) - ); - } - async updateDictionaryVersion() { const result = await this._versionsControllerService.getVersions().toPromise(); this._appState.dictionaryVersion = result.dictionaryVersion;