- {{ page }}
+ {{ page }}
{{ annotation.superType | humanize }}
- : {{ annotation.dictionary | humanize }}
+ : {{ annotation.dictionary | humanize }}
- : {{ annotation.content }}
+ : {{ annotation.content }}
@@ -235,16 +116,11 @@
-
+
-
+
{{ filter.label ? (filter.label | translate) : (filter.key | humanize) }}
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 58328cfd7..380a988b3 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
@@ -1,23 +1,11 @@
-import {
- ChangeDetectorRef,
- Component,
- ElementRef,
- HostListener,
- NgZone,
- OnInit,
- ViewChild
-} from '@angular/core';
+import { ChangeDetectorRef, Component, ElementRef, HostListener, NgZone, OnInit, ViewChild } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
-import { ReanalysisControllerService } from '@redaction/red-ui-http';
import { AppStateService } from '../../../state/app-state.service';
import { WebViewerInstance } from '@pdftron/webviewer';
import { PdfViewerComponent } from '../pdf-viewer/pdf-viewer.component';
-import { UserService } from '../../../user/user.service';
import { debounce } from '../../../utils/debounce';
import scrollIntoView from 'scroll-into-view-if-needed';
import { FileDownloadService } from '../service/file-download.service';
-import { saveAs } from 'file-saver';
-import { FileType } from '../model/file-type';
import { DialogService } from '../../../dialogs/dialog.service';
import { MatDialogRef, MatDialogState } from '@angular/material/dialog';
import { ManualRedactionEntryWrapper } from '../model/manual-redaction-entry.wrapper';
@@ -34,6 +22,7 @@ import { NotificationService } from '../../../notification/notification.service'
import { TranslateService } from '@ngx-translate/core';
import { FileStatusWrapper } from '../model/file-status.wrapper';
import { MatTooltip } from '@angular/material/tooltip';
+import { PermissionsService } from '../../../common/service/permissions.service';
const KEY_ARRAY = ['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown'];
@@ -53,21 +42,21 @@ export class FilePreviewScreenComponent implements OnInit {
@ViewChild('quickNavigation') private _quickNavigationElement: ElementRef;
@ViewChild('reanalyseTooltip') private _reanalyseTooltip: MatTooltip;
- public fileData: FileDataModel;
- public fileId: string;
- public annotations: AnnotationWrapper[] = [];
- public displayedAnnotations: { [key: number]: { annotations: AnnotationWrapper[] } } = {};
- public selectedAnnotation: AnnotationWrapper;
- public pagesPanelActive = true;
- public viewReady = false;
- public filters: FilterModel[];
+ fileData: FileDataModel;
+ fileId: string;
+ annotations: AnnotationWrapper[] = [];
+ displayedAnnotations: { [key: number]: { annotations: AnnotationWrapper[] } } = {};
+ selectedAnnotation: AnnotationWrapper;
+ pagesPanelActive = true;
+ viewReady = false;
+ filters: FilterModel[];
loadingMessage: string;
canPerformAnnotationActions: boolean;
constructor(
public readonly appStateService: AppStateService,
- public readonly userService: UserService,
+ public readonly permissionsService: PermissionsService,
private readonly _changeDetectorRef: ChangeDetectorRef,
private readonly _activatedRoute: ActivatedRoute,
private readonly _dialogService: DialogService,
@@ -79,7 +68,6 @@ export class FilePreviewScreenComponent implements OnInit {
private readonly _fileActionService: FileActionService,
private readonly _manualAnnotationService: ManualAnnotationService,
private readonly _fileDownloadService: FileDownloadService,
- private readonly _reanalysisControllerService: ReanalysisControllerService,
private ngZone: NgZone
) {
this._activatedRoute.params.subscribe((params) => {
@@ -89,23 +77,19 @@ export class FilePreviewScreenComponent implements OnInit {
});
}
- public get user() {
- return this.userService.user;
- }
-
- public get redactedView() {
+ get redactedView() {
return this._activeViewer === 'REDACTED';
}
- public set redactedView(value: boolean) {
+ set redactedView(value: boolean) {
this._activeViewer = value ? 'REDACTED' : 'ANNOTATED';
}
- public get activeViewer() {
+ get activeViewer() {
return this.instance;
}
- public get displayedPages(): number[] {
+ get displayedPages(): number[] {
return Object.keys(this.displayedAnnotations).map((key) => Number(key));
}
@@ -114,14 +98,11 @@ export class FilePreviewScreenComponent implements OnInit {
}
get canNotSwitchToRedactedView() {
- return (
- this.appStateService.fileNotUpToDateWithDictionary() ||
- this.fileData?.entriesToAdd?.length > 0
- );
+ return this.appStateService.fileNotUpToDateWithDictionary() || this.fileData?.entriesToAdd?.length > 0;
}
- public ngOnInit(): void {
- this.canPerformAnnotationActions = this.appStateService.canPerformAnnotationActionsOnCurrentFile();
+ ngOnInit(): void {
+ this.canPerformAnnotationActions = this.permissionsService.canPerformAnnotationActions();
this._loadFileData().subscribe(() => {});
this.appStateService.fileReanalysed.subscribe((fileStatus: FileStatusWrapper) => {
if (fileStatus.fileId === this.fileId) {
@@ -145,18 +126,12 @@ export class FilePreviewScreenComponent implements OnInit {
private _rebuildFilters() {
const manualRedactionAnnotations = this.fileData.entriesToAdd.map((mr) =>
- AnnotationWrapper.fromManualRedaction(
- mr,
- this.fileData.manualRedactions,
- this.appStateService.dictionaryData,
- this.user
- )
+ AnnotationWrapper.fromManualRedaction(mr, this.fileData.manualRedactions, this.appStateService.dictionaryData, this.permissionsService.currentUser)
);
const redactionLogAnnotations = this.fileData.redactionLog.redactionLogEntry.map((rde) =>
- AnnotationWrapper.fromRedactionLog(rde, this.fileData.manualRedactions, this.user)
+ AnnotationWrapper.fromRedactionLog(rde, this.fileData.manualRedactions, this.permissionsService.currentUser)
);
- //this.annotations.splice(0, this.annotations.length);
this.annotations = [];
this.annotations.push(...manualRedactionAnnotations);
this.annotations.push(...redactionLogAnnotations);
@@ -164,48 +139,13 @@ export class FilePreviewScreenComponent implements OnInit {
this.filtersChanged(this.filters);
}
- public openFileDetailsDialog($event: MouseEvent) {
- this._dialogRef = this._dialogService.openFileDetailsDialog(
- $event,
- this.appStateService.activeFile
- );
- }
-
- public reanalyseFile($event?: MouseEvent) {
- $event?.stopPropagation();
- this.viewReady = false;
- this.loadingMessage = 'file-preview.reanalyse-file';
- this._reanalysisControllerService
- .reanalyzeFile(this.appStateService.activeProject.project.projectId, this.fileId)
- .subscribe(async () => {
- await this.appStateService.reloadActiveProjectFiles();
- });
- }
-
- public openDeleteFileDialog($event: MouseEvent) {
- this._dialogRef = this._dialogService.openDeleteFileDialog(
- $event,
- this.projectId,
- this.fileId,
- () => {
- this._router.navigate([`/ui/projects/${this.projectId}`]);
- }
- );
- }
-
- public assignReviewer() {
- this._fileActionService.assignProjectReviewer(null, () => {
- this.canPerformAnnotationActions = this.appStateService.canPerformAnnotationActionsOnCurrentFile();
- });
- }
-
- public handleAnnotationSelected(annotationId: string) {
+ handleAnnotationSelected(annotationId: string) {
this.selectedAnnotation = this.annotations.find((a) => a.id === annotationId);
this.scrollToSelectedAnnotation();
this._changeDetectorRef.detectChanges();
}
- public selectAnnotation(annotation: AnnotationWrapper) {
+ selectAnnotation(annotation: AnnotationWrapper) {
this._viewerComponent.selectAnnotation(annotation);
}
@@ -214,25 +154,20 @@ export class FilePreviewScreenComponent implements OnInit {
if (!this.selectedAnnotation) {
return;
}
- const elements: any[] = this._annotationsElement.nativeElement.querySelectorAll(
- `div[annotation-id="${this.selectedAnnotation.id}"].active`
- );
+ const elements: any[] = this._annotationsElement.nativeElement.querySelectorAll(`div[annotation-id="${this.selectedAnnotation.id}"].active`);
this._scrollToFirstElement(elements);
}
- public selectPage(pageNumber: number) {
+ selectPage(pageNumber: number) {
this._viewerComponent.navigateToPage(pageNumber);
this._scrollAnnotationsToPage(pageNumber, 'always');
}
- public openManualRedactionDialog($event: ManualRedactionEntryWrapper) {
+ openManualRedactionDialog($event: ManualRedactionEntryWrapper) {
this.ngZone.run(() => {
- this._dialogRef = this._dialogService.openManualRedactionDialog(
- $event,
- (response: ManualAnnotationResponse) => {
- this._cleanupAndRedrawManualAnnotations();
- }
- );
+ this._dialogRef = this._dialogService.openManualRedactionDialog($event, (response: ManualAnnotationResponse) => {
+ this._cleanupAndRedrawManualAnnotations();
+ });
});
}
@@ -243,9 +178,7 @@ export class FilePreviewScreenComponent implements OnInit {
}
private _scrollQuickNavigation() {
- const elements: any[] = this._quickNavigationElement.nativeElement.querySelectorAll(
- `#quick-nav-page-${this.activeViewerPage}`
- );
+ const elements: any[] = this._quickNavigationElement.nativeElement.querySelectorAll(`#quick-nav-page-${this.activeViewerPage}`);
this._scrollToFirstElement(elements);
}
@@ -257,16 +190,11 @@ export class FilePreviewScreenComponent implements OnInit {
}
private _scrollAnnotationsToPage(page: number, mode: 'always' | 'if-needed' = 'if-needed') {
- const elements: any[] = this._annotationsElement.nativeElement.querySelectorAll(
- `div[anotation-page-header="${page}"]`
- );
+ const elements: any[] = this._annotationsElement.nativeElement.querySelectorAll(`div[anotation-page-header="${page}"]`);
this._scrollToFirstElement(elements, mode);
}
- private _scrollToFirstElement(
- elements: HTMLElement[],
- mode: 'always' | 'if-needed' = 'if-needed'
- ) {
+ private _scrollToFirstElement(elements: HTMLElement[], mode: 'always' | 'if-needed' = 'if-needed') {
if (elements.length > 0) {
scrollIntoView(elements[0], {
behavior: 'smooth',
@@ -277,18 +205,9 @@ export class FilePreviewScreenComponent implements OnInit {
}
}
- public downloadFile(type: FileType | string) {
- this._fileDownloadService.loadFile(type, this.fileId).subscribe((data) => {
- saveAs(data, this.appStateService.activeFile.filename);
- });
- }
-
@HostListener('window:keyup', ['$event'])
- public handleKeyEvent($event: KeyboardEvent) {
- if (
- !KEY_ARRAY.includes($event.key) ||
- this._dialogRef?.getState() === MatDialogState.OPEN
- ) {
+ handleKeyEvent($event: KeyboardEvent) {
+ if (!KEY_ARRAY.includes($event.key) || this._dialogRef?.getState() === MatDialogState.OPEN) {
return;
}
@@ -318,8 +237,7 @@ export class FilePreviewScreenComponent implements OnInit {
private _selectFirstAnnotationOnCurrentPageIfNecessary() {
if (
- (!this.selectedAnnotation ||
- this.activeViewerPage !== this.selectedAnnotation.pageNumber) &&
+ (!this.selectedAnnotation || this.activeViewerPage !== this.selectedAnnotation.pageNumber) &&
this.displayedPages.indexOf(this.activeViewerPage) >= 0
) {
this.selectAnnotation(this.displayedAnnotations[this.activeViewerPage].annotations[0]);
@@ -327,16 +245,11 @@ export class FilePreviewScreenComponent implements OnInit {
}
private _navigateAnnotations($event: KeyboardEvent) {
- if (
- !this.selectedAnnotation ||
- this.activeViewerPage !== this.selectedAnnotation.pageNumber
- ) {
+ if (!this.selectedAnnotation || this.activeViewerPage !== this.selectedAnnotation.pageNumber) {
const pageIdx = this.displayedPages.indexOf(this.activeViewerPage);
if (pageIdx !== -1) {
// Displayed page has annotations
- this.selectAnnotation(
- this.displayedAnnotations[this.activeViewerPage].annotations[0]
- );
+ this.selectAnnotation(this.displayedAnnotations[this.activeViewerPage].annotations[0]);
} else {
// Displayed page doesn't have annotations
if ($event.key === 'ArrowDown') {
@@ -360,9 +273,7 @@ export class FilePreviewScreenComponent implements OnInit {
this.selectAnnotation(annotationsOnPage[idx + 1]);
} else if (pageIdx + 1 < this.displayedPages.length) {
// If not last page
- const nextPageAnnotations = this.displayedAnnotations[
- this.displayedPages[pageIdx + 1]
- ].annotations;
+ const nextPageAnnotations = this.displayedAnnotations[this.displayedPages[pageIdx + 1]].annotations;
this.selectAnnotation(nextPageAnnotations[0]);
}
} else {
@@ -371,9 +282,7 @@ export class FilePreviewScreenComponent implements OnInit {
this.selectAnnotation(annotationsOnPage[idx - 1]);
} else if (pageIdx) {
// If not first page
- const prevPageAnnotations = this.displayedAnnotations[
- this.displayedPages[pageIdx - 1]
- ].annotations;
+ const prevPageAnnotations = this.displayedAnnotations[this.displayedPages[pageIdx - 1]].annotations;
this.selectAnnotation(prevPageAnnotations[prevPageAnnotations.length - 1]);
}
}
@@ -451,10 +360,7 @@ export class FilePreviewScreenComponent implements OnInit {
}
filtersChanged(filters: FilterModel[]) {
- this.displayedAnnotations = this._annotationProcessingService.filterAndGroupAnnotations(
- this.annotations,
- filters
- );
+ this.displayedAnnotations = this._annotationProcessingService.filterAndGroupAnnotations(this.annotations, filters);
this._changeDetectorRef.markForCheck();
}
@@ -465,62 +371,41 @@ export class FilePreviewScreenComponent implements OnInit {
}
private _cleanupAndRedrawManualAnnotations() {
- this._fileDownloadService
- .loadActiveFileManualAnnotations()
- .subscribe((manualRedactions) => {
- const annotationsToRemove = [];
- this.fileData.entriesToAdd.forEach((manuallyAddedEntry) => {
- const annotation = this.activeViewer.annotManager.getAnnotationById(
- manuallyAddedEntry.id
- );
- if (annotation) {
- annotationsToRemove.push(annotation);
- }
- });
- this.activeViewer.annotManager.deleteAnnotations(annotationsToRemove, false, true);
-
- this.fileData.manualRedactions = manualRedactions;
- this._annotationDrawService.drawAnnotations(
- this.instance,
- this.fileData.entriesToAdd
- );
- this._rebuildFilters();
+ this._fileDownloadService.loadActiveFileManualAnnotations().subscribe((manualRedactions) => {
+ const annotationsToRemove = [];
+ this.fileData.entriesToAdd.forEach((manuallyAddedEntry) => {
+ const annotation = this.activeViewer.annotManager.getAnnotationById(manuallyAddedEntry.id);
+ if (annotation) {
+ annotationsToRemove.push(annotation);
+ }
});
- }
+ this.activeViewer.annotManager.deleteAnnotations(annotationsToRemove, false, true);
- get fileReadyForDownload() {
- return this.appStateService.activeFile.status === 'APPROVED';
- }
-
- isApprovedOrUnderApproval() {
- return (
- this.appStateService.activeFile.status === 'APPROVED' ||
- this.appStateService.activeFile.status === 'UNDER_APPROVAL'
- );
- }
-
- isApproved() {
- return this.appStateService.activeFile.status === 'APPROVED';
- }
-
- canApprove() {
- return (
- this.appStateService.activeFile.status === 'UNDER_REVIEW' ||
- this.appStateService.activeFile.status === 'UNDER_APPROVAL'
- );
- }
-
- requestApprovalOrApproveFile($event: MouseEvent) {
- $event.stopPropagation();
- this._fileActionService.requestApprovalOrApproveFile().subscribe(() => {});
- }
-
- undoApproveOrUnderApproval($event: MouseEvent) {
- $event.stopPropagation();
- this._fileActionService.undoApproveOrUnderApproval().subscribe(() => {});
+ this.fileData.manualRedactions = manualRedactions;
+ this._annotationDrawService.drawAnnotations(this.instance, this.fileData.entriesToAdd);
+ this._rebuildFilters();
+ });
}
annotationsChangedByReviewAction() {
this._cleanupAndRedrawManualAnnotations();
}
+
+ async fileActionPerformed(action: string) {
+ switch (action) {
+ case 'delete':
+ await this._router.navigate([`/ui/projects/${this.projectId}`]);
+ break;
+
+ case 'reanalyse':
+ this.viewReady = false;
+ this.loadingMessage = 'file-preview.reanalyse-file';
+ break;
+ }
+
+ this.canPerformAnnotationActions = this.permissionsService.canPerformAnnotationActions();
+ await this.appStateService.reloadActiveProjectFiles();
+ }
+
+ // allManualRedactionsApplied
}
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 a487932f8..9789d529d 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,6 +11,7 @@ export class FileStatusWrapper {
return this.fileStatus.added;
}
+ // TODO use this for suggestions
get allManualRedactionsApplied() {
return this.fileStatus.allManualRedactionsApplied;
}
@@ -39,6 +40,10 @@ export class FileStatusWrapper {
return this.fileStatus.hasHints;
}
+ get hintsOnly() {
+ return this.fileStatus.hasHints && !this.fileStatus.hasRedactions;
+ }
+
get hasRedactions() {
return this.fileStatus.hasRedactions;
}
@@ -79,6 +84,18 @@ export class FileStatusWrapper {
return this.fileStatus.uploader;
}
+ get isPending() {
+ return this.status === FileStatus.StatusEnum.UNPROCESSED;
+ }
+
+ get isProcessing() {
+ return [FileStatus.StatusEnum.REPROCESS, FileStatus.StatusEnum.PROCESSING].includes(this.status);
+ }
+
+ get isWorkable() {
+ return !this.isProcessing && !this.isPending && !this.isError;
+ }
+
get isApproved() {
return this.fileStatus.status === 'APPROVED';
}
@@ -99,7 +116,7 @@ export class FileStatusWrapper {
}
get isUnassigned() {
- return this.status === 'UNASSIGNED';
+ return !this.currentReviewer;
}
get canApprove() {
diff --git a/apps/red-ui/src/app/screens/file/service/file-action.service.ts b/apps/red-ui/src/app/screens/file/service/file-action.service.ts
index 9f8f4e2c6..6036be20c 100644
--- a/apps/red-ui/src/app/screens/file/service/file-action.service.ts
+++ b/apps/red-ui/src/app/screens/file/service/file-action.service.ts
@@ -2,9 +2,10 @@ import { Injectable } from '@angular/core';
import { DialogService } from '../../../dialogs/dialog.service';
import { AppStateService } from '../../../state/app-state.service';
import { UserService } from '../../../user/user.service';
-import { StatusControllerService } from '@redaction/red-ui-http';
+import { ReanalysisControllerService, StatusControllerService } from '@redaction/red-ui-http';
import { FileStatus } from '@redaction/red-ui-http';
import { FileStatusWrapper } from '../model/file-status.wrapper';
+import { PermissionsService } from '../../../common/service/permissions.service';
@Injectable({
providedIn: 'root'
@@ -12,29 +13,31 @@ import { FileStatusWrapper } from '../model/file-status.wrapper';
export class FileActionService {
constructor(
private readonly _dialogService: DialogService,
+ private readonly _permissionsService: PermissionsService,
private readonly _userService: UserService,
private readonly _statusControllerService: StatusControllerService,
- private _appStateService: AppStateService
+ private readonly _reanalysisControllerService: ReanalysisControllerService,
+ private readonly _appStateService: AppStateService
) {}
+ public reanalyseFile(fileStatusWrapper?: FileStatusWrapper) {
+ if (!fileStatusWrapper) {
+ fileStatusWrapper = this._appStateService.activeFile;
+ }
+ return this._reanalysisControllerService.reanalyzeFile(this._appStateService.activeProject.project.projectId, fileStatusWrapper.fileId);
+ }
+
public assignProjectReviewer(file?: FileStatus, callback?: Function) {
- if (this._appStateService.isActiveProjectOwnerAndManager) {
- this._dialogService.openAssignFileReviewerDialog(
- file ? file : this._appStateService.activeFile,
- async () => {
- await this._appStateService.reloadActiveProjectFiles();
- if (callback) {
- callback();
- }
+ if (this._permissionsService.isManagerAndOwner()) {
+ this._dialogService.openAssignFileReviewerDialog(file ? file : this._appStateService.activeFile, async () => {
+ await this._appStateService.reloadActiveProjectFiles();
+ if (callback) {
+ callback();
}
- );
+ });
} else {
this._statusControllerService
- .assignProjectOwner(
- this._appStateService.activeProjectId,
- file ? file.fileId : this._appStateService.activeFileId,
- this._userService.userId
- )
+ .assignProjectOwner(this._appStateService.activeProjectId, file ? file.fileId : this._appStateService.activeFileId, this._userService.userId)
.subscribe(async () => {
await this._appStateService.reloadActiveProjectFiles();
if (callback) {
@@ -44,46 +47,15 @@ export class FileActionService {
}
}
- setUnderApproval(fileStatus: FileStatusWrapper) {
- return this._statusControllerService.setStatusUnderApproval(
- this._appStateService.activeProjectId,
- fileStatus.fileId
- );
+ setFileUnderApproval(fileStatus: FileStatusWrapper) {
+ return this._statusControllerService.setStatusUnderApproval(this._appStateService.activeProjectId, fileStatus.fileId);
}
- setApproved(fileStatus: FileStatusWrapper) {
- return this._statusControllerService.setStatusApproved(
- this._appStateService.activeProjectId,
- fileStatus.fileId
- );
+ setFileApproved(fileStatus: FileStatusWrapper) {
+ return this._statusControllerService.setStatusApproved(this._appStateService.activeProjectId, fileStatus.fileId);
}
- setReview(fileStatus: FileStatusWrapper) {
- return this._statusControllerService.setStatusUnderReview(
- this._appStateService.activeProjectId,
- fileStatus.fileId
- );
- }
-
- requestApprovalOrApproveFile(fileStatusWrapper?: FileStatusWrapper) {
- if (!fileStatusWrapper) {
- fileStatusWrapper = this._appStateService.activeFile;
- }
- if (fileStatusWrapper.status === 'UNDER_REVIEW') {
- return this.setUnderApproval(fileStatusWrapper);
- } else {
- return this.setApproved(fileStatusWrapper);
- }
- }
-
- undoApproveOrUnderApproval(fileStatusWrapper?: FileStatusWrapper) {
- if (!fileStatusWrapper) {
- fileStatusWrapper = this._appStateService.activeFile;
- }
- if (fileStatusWrapper.status === 'APPROVED') {
- return this.setUnderApproval(fileStatusWrapper);
- } else {
- return this.setReview(fileStatusWrapper);
- }
+ setFileUnderReview(fileStatus: FileStatusWrapper) {
+ return this._statusControllerService.setStatusUnderReview(this._appStateService.activeProjectId, fileStatus.fileId);
}
}
diff --git a/apps/red-ui/src/app/screens/project-listing-screen/project-listing-screen.component.html b/apps/red-ui/src/app/screens/project-listing-screen/project-listing-screen.component.html
index b1eeb4ef4..9fc24d639 100644
--- a/apps/red-ui/src/app/screens/project-listing-screen/project-listing-screen.component.html
+++ b/apps/red-ui/src/app/screens/project-listing-screen/project-listing-screen.component.html
@@ -32,13 +32,7 @@
[icon]="'red:needs-work'"
>