work in progress permission updates

This commit is contained in:
Timo Bejan 2020-11-09 21:30:06 +02:00
parent 1c5f8fd006
commit 1199fe4542
19 changed files with 316 additions and 367 deletions

View File

@ -1,80 +1,96 @@
<button
(click)="openDeleteFileDialog($event, fileStatus)"
*ngIf="permissionsService.canDeleteFile(fileStatus)"
[matTooltip]="'project-overview.delete.action' | translate"
matTooltipPosition="above"
color="accent"
mat-icon-button
>
<mat-icon svgIcon="red:trash"></mat-icon>
</button>
<div class="file-actions">
<button
(click)="openDeleteFileDialog($event, fileStatus)"
*ngIf="permissionsService.canDeleteFile(fileStatus)"
[matTooltip]="'project-overview.delete.action' | translate"
matTooltipPosition="above"
color="accent"
mat-icon-button
>
<mat-icon svgIcon="red:trash"></mat-icon>
</button>
<div
[matTooltip]="(fileStatus.isApproved ? 'report.action' : 'report.unavailable-single') | translate"
*ngIf="permissionsService.canShowRedactionReportDownloadBtn(fileStatus)"
matTooltipPosition="above"
>
<button (click)="downloadFileRedactionReport($event, fileStatus)" [disabled]="!fileStatus.isApproved" color="accent" mat-icon-button>
<mat-icon svgIcon="red:report"></mat-icon>
</button>
</div>
<div
[matTooltip]="(fileStatus.isApproved ? 'report.action' : 'report.unavailable-single') | translate"
*ngIf="permissionsService.canShowRedactionReportDownloadBtn()"
matTooltipPosition="above"
>
<button (click)="downloadFileRedactionReport($event, fileStatus)" [disabled]="!fileStatus.isApproved" color="accent" mat-icon-button>
<mat-icon svgIcon="red:report"></mat-icon>
<button
(click)="reanalyseFile($event, fileStatus)"
class="warn"
*ngIf="permissionsService.canReanalyseFile(fileStatus) && screen === 'file-preview'"
mat-icon-button
#reanalyseTooltip="matTooltip"
[matTooltip]="'file-preview.reanalyse-notification' | translate"
matTooltipClass="warn"
>
<mat-icon svgIcon="red:refresh"></mat-icon>
</button>
<button
(click)="reanalyseFile($event, fileStatus)"
*ngIf="permissionsService.canReanalyseFile(fileStatus) && screen !== 'file-preview'"
[matTooltip]="'project-overview.reanalyse.action' | translate"
matTooltipPosition="above"
color="accent"
mat-icon-button
>
<mat-icon svgIcon="red:refresh"></mat-icon>
</button>
<button
(click)="assignReviewer($event, fileStatus)"
*ngIf="permissionsService.canAssignReviewer(fileStatus)"
[matTooltip]="'project-overview.assign.action' | translate"
matTooltipPosition="above"
color="accent"
mat-icon-button
>
<mat-icon svgIcon="red:assign"></mat-icon>
</button>
<button
(click)="setFileApproved($event, fileStatus)"
*ngIf="permissionsService.canApprove(fileStatus)"
[matTooltip]="'project-overview.approve' | translate"
matTooltipPosition="above"
color="accent"
mat-icon-button
>
<mat-icon svgIcon="red:check-alt"></mat-icon>
</button>
<button
(click)="setFileUnderApproval($event, fileStatus)"
*ngIf="permissionsService.canSetUnderApproval(fileStatus)"
[matTooltip]="'project-overview.under-approval' | translate"
matTooltipPosition="above"
color="accent"
mat-icon-button
>
<mat-icon svgIcon="red:check-alt"></mat-icon>
</button>
<button
(click)="setFileUnderApproval($event, fileStatus)"
*ngIf="permissionsService.canUndoApproval(fileStatus)"
[matTooltip]="'project-overview.under-approval' | translate"
matTooltipPosition="above"
color="accent"
mat-icon-button
>
<mat-icon svgIcon="red:undo"></mat-icon>
</button>
<button
(click)="setFileUnderReview($event, fileStatus)"
*ngIf="permissionsService.canUndoUnderApproval(fileStatus)"
[matTooltip]="'project-overview.under-review' | translate"
matTooltipPosition="above"
color="accent"
mat-icon-button
>
<mat-icon svgIcon="red:undo"></mat-icon>
</button>
</div>
<button
(click)="assignReviewer($event, fileStatus)"
*ngIf="permissionsService.canAssignReviewer(fileStatus)"
[matTooltip]="'project-overview.assign.action' | translate"
matTooltipPosition="above"
color="accent"
mat-icon-button
>
<mat-icon svgIcon="red:assign"></mat-icon>
</button>
<button
(click)="reanalyseFile($event, fileStatus)"
*ngIf="permissionsService.canReanalyseFile(fileStatus)"
[matTooltip]="'project-overview.reanalyse.action' | translate"
matTooltipPosition="above"
color="accent"
mat-icon-button
>
<mat-icon svgIcon="red:refresh"></mat-icon>
</button>
<button
(click)="setFileApproved($event, fileStatus)"
*ngIf="permissionsService.canApprove(fileStatus)"
[matTooltip]="'project-overview.approve' | translate"
matTooltipPosition="above"
color="accent"
mat-icon-button
>
<mat-icon svgIcon="red:check-alt"></mat-icon>
</button>
<button
(click)="setFileUnderApproval($event, fileStatus)"
*ngIf="permissionsService.canSetUnderApproval(fileStatus)"
[matTooltip]="'project-overview.under-approval' | translate"
matTooltipPosition="above"
color="accent"
mat-icon-button
>
<mat-icon svgIcon="red:check-alt"></mat-icon>
</button>
<button
(click)="setFileUnderApproval($event, fileStatus)"
*ngIf="permissionsService.canUndoApproval(fileStatus)"
[matTooltip]="'project-overview.under-approval' | translate"
matTooltipPosition="above"
color="accent"
mat-icon-button
>
<mat-icon svgIcon="red:undo"></mat-icon>
</button>
<button
(click)="setFileUnderReview($event, fileStatus)"
*ngIf="permissionsService.canUndoUnderApproval(fileStatus)"
[matTooltip]="'project-overview.under-review' | translate"
matTooltipPosition="above"
color="accent"
mat-icon-button
>
<mat-icon svgIcon="red:undo"></mat-icon>
</button>

View File

@ -0,0 +1,3 @@
.file-actions {
display: flex;
}

View File

@ -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<string>();
@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);
});
}

View File

@ -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,

View File

@ -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();
}
}

View File

@ -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<ManualAnnotationDialogComponent>,
@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) {

View File

@ -12,14 +12,14 @@
</div>
<div class="flex-1 filename page-title">
<span *ngIf="appStateService.fileNotUpToDateWithDictionary()" class="pill" translate="project-overview.new-rule.label"></span>
<span *ngIf="permissionsService.fileRequiresReanalysis()" class="pill" translate="project-overview.new-rule.label"></span>
<span *ngIf="!permissionsService.canPerformAnnotationActions()" class="pill" translate="readonly-pill"></span>&nbsp;<span>{{
appStateService.activeFile.filename
}}</span>
</div>
<div class="flex-1 actions-container">
<redaction-file-actions [fileStatus]="fileData.fileStatus" (actionPerformed)="fileActionPerformed()"></redaction-file-actions>
<redaction-file-actions (actionPerformed)="fileActionPerformed($event)" *ngIf="viewReady"></redaction-file-actions>
<button [routerLink]="['/ui/projects/' + appStateService.activeProjectId]" mat-icon-button>
<mat-icon svgIcon="red:close"></mat-icon>
</button>

View File

@ -20,6 +20,7 @@ redaction-pdf-viewer {
display: flex;
justify-content: center;
align-items: center;
gap: 4px;
}
.right-fixed-container {

View File

@ -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();
}

View File

@ -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() {

View File

@ -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'])

View File

@ -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(<any>{
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);

View File

@ -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<FileDataModel> {
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<any> {
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<any> {
let fileObs$: Observable<any>;
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$;

View File

@ -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 {

View File

@ -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();
});
}

View File

@ -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();
});
}

View File

@ -122,11 +122,7 @@
<div [class.disabled]="fileStatus.isPending || fileStatus.isProcessing" [class.error]="fileStatus.isError" class="table-item-title">
{{ fileStatus.filename }}
</div>
<span
*ngIf="appStateService.fileNotUpToDateWithDictionary(fileStatus)"
class="pill"
translate="project-overview.new-rule.label"
></span>
<span *ngIf="permissionsService.fileRequiresReanalysis(fileStatus)" class="pill" translate="project-overview.new-rule.label"></span>
</div>
</div>
@ -163,7 +159,11 @@
></redaction-status-bar>
<div class="action-buttons">
<redaction-file-actions [fileStatus]="fileStatus" (actionPerformed)="calculateData()"></redaction-file-actions>
<redaction-file-actions
[fileStatus]="fileStatus"
(actionPerformed)="calculateData()"
*ngIf="!fileStatus.isProcessing"
></redaction-file-actions>
<redaction-status-bar
class="mr-8"
*ngIf="fileStatus.isWorkable"

View File

@ -1,6 +1,5 @@
import { ChangeDetectorRef, Component, OnDestroy, OnInit, ViewChild } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { FileUploadControllerService, ReanalysisControllerService, StatusControllerService } from '@redaction/red-ui-http';
import { NotificationService, NotificationType } from '../../notification/notification.service';
import { AppStateService } from '../../state/app-state.service';
import { FileDropOverlayService } from '../../upload/file-drop/service/file-drop-overlay.service';
@ -63,7 +62,7 @@ export class ProjectOverviewScreenComponent implements OnInit, OnDestroy {
this.appStateService.activateProject(params.projectId);
});
this.appStateService.fileStatusChanged.subscribe(() => {
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')} <span class="pill">${this._translateService.instant(
'project-overview.new-rule.label'
)}</span>`,
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')} <span class="pill">${this._translateService.instant(
'project-overview.new-rule.label'
)}</span>`,
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() {

View File

@ -39,7 +39,7 @@ export interface AppState {
export class AppStateService {
private _appState: AppState;
private _dictionaryData: { [key: string]: TypeValue } = null;
public fileStatusChanged = new EventEmitter<FileStatusWrapper>();
public fileChanged = new EventEmitter<FileStatusWrapper>();
public fileReanalysed = new EventEmitter<FileStatusWrapper>();
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;