linked redaction file screen to backend colors and dictionary data

This commit is contained in:
Timo Bejan 2020-10-27 23:38:16 +02:00
parent b4888b49ce
commit f1f3dda4e3
15 changed files with 135 additions and 76 deletions

View File

@ -52,5 +52,11 @@
"secure": false, "secure": false,
"changeOrigin": true, "changeOrigin": true,
"logLevel": "debug" "logLevel": "debug"
},
"/color": {
"target": "https://timo-redaction-dev.iqser.cloud/",
"secure": false,
"changeOrigin": true,
"logLevel": "debug"
} }
} }

View File

@ -1,3 +1,3 @@
<div [ngClass]="type" class="icon"> <div class="icon" [class.hint]="typeValue.hint" [style.background-color]="typeValue.hexColor">
<span>{{ type[0] }}</span> <span>{{ typeValue.type[0] }}</span>
</div> </div>

View File

@ -40,33 +40,10 @@
} }
.hint, .hint,
.comment,
.ignore { .ignore {
border-radius: 50%; border-radius: 50%;
} }
.hint,
.redaction,
.comment {
background-color: $grey-1;
}
.request {
background-color: $blue-1;
}
.ignore { .ignore {
background-color: $grey-5; background-color: $grey-5;
} }
.hint_only {
background-color: $orange-1;
}
.vertebrate {
background-color: $green-1;
}
.names {
background-color: $yellow-2;
}

View File

@ -1,4 +1,5 @@
import { Component, Input, OnInit } from '@angular/core'; import { Component, Input, OnInit } from '@angular/core';
import { TypeValue } from '@redaction/red-ui-http';
@Component({ @Component({
selector: 'redaction-annotation-icon', selector: 'redaction-annotation-icon',
@ -6,7 +7,7 @@ import { Component, Input, OnInit } from '@angular/core';
styleUrls: ['./annotation-icon.component.scss'] styleUrls: ['./annotation-icon.component.scss']
}) })
export class AnnotationIconComponent implements OnInit { export class AnnotationIconComponent implements OnInit {
@Input() public type: 'hint' | 'redaction' | 'suggestion' | 'ignore' | 'comment' | 'request'; @Input() typeValue: TypeValue;
constructor() {} constructor() {}

View File

@ -38,9 +38,8 @@ export class ManualRedactionDialogComponent implements OnInit {
async ngOnInit() { async ngOnInit() {
this.isDocumentAdmin = this.isDocumentAdmin =
(this._appStateService.isActiveProjectOwner || this._appStateService.isActiveProjectOwner && this._userService.user.isManager;
this._appStateService.isActiveFileDocumentReviewer) && this.isDocumentAdmin = false;
this._userService.user.isManager;
const commentField = this.isDocumentAdmin ? [null] : [null, Validators.required]; const commentField = this.isDocumentAdmin ? [null] : [null, Validators.required];
this.redactionForm = this._formBuilder.group({ this.redactionForm = this._formBuilder.group({
addToDictionary: [false], addToDictionary: [false],

View File

@ -145,8 +145,9 @@
color="primary" color="primary"
> >
<redaction-annotation-icon <redaction-annotation-icon
[type]="key" [typeValue]="appStateService.getDictionaryTypeValue(key)"
></redaction-annotation-icon> ></redaction-annotation-icon>
{{ 'file-preview.filter-menu.' + key + '.label' | translate }} {{ 'file-preview.filter-menu.' + key + '.label' | translate }}
</mat-checkbox> </mat-checkbox>
</div> </div>
@ -162,15 +163,11 @@
color="primary" color="primary"
> >
<redaction-annotation-icon <redaction-annotation-icon
[type]="key + ' ' + subkey" [typeValue]="
appStateService.getDictionaryTypeValue(subkey)
"
></redaction-annotation-icon> ></redaction-annotation-icon>
{{ {{ appStateService.getDictionaryLabel(subkey) }}
'file-preview.filter-menu.' +
key +
'.' +
subkey +
'.label' | translate
}}
</mat-checkbox> </mat-checkbox>
</div> </div>
</div> </div>
@ -223,7 +220,11 @@
(click)="selectAnnotation(annotation)" (click)="selectAnnotation(annotation)"
> >
<redaction-annotation-icon <redaction-annotation-icon
[type]="getType(annotation) + ' ' + getDictionary(annotation)" [typeValue]="
appStateService.getDictionaryTypeValue(
getDictionary(annotation)
)
"
></redaction-annotation-icon> ></redaction-annotation-icon>
<div class="flex-1"> <div class="flex-1">
<div> <div>

View File

@ -8,7 +8,12 @@ import {
ViewChild ViewChild
} from '@angular/core'; } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router'; import { ActivatedRoute, Router } from '@angular/router';
import { ManualRedactionEntry, ReanalysisControllerService } from '@redaction/red-ui-http'; import {
DictionaryControllerService,
ManualRedactionEntry,
ReanalysisControllerService,
TypeValue
} from '@redaction/red-ui-http';
import { AppStateService } from '../../../state/app-state.service'; import { AppStateService } from '../../../state/app-state.service';
import { Annotations, WebViewerInstance } from '@pdftron/webviewer'; import { Annotations, WebViewerInstance } from '@pdftron/webviewer';
import { PdfViewerComponent } from '../pdf-viewer/pdf-viewer.component'; import { PdfViewerComponent } from '../pdf-viewer/pdf-viewer.component';
@ -56,6 +61,7 @@ export class FilePreviewScreenComponent implements OnInit {
private readonly _activatedRoute: ActivatedRoute, private readonly _activatedRoute: ActivatedRoute,
private readonly _dialogService: DialogService, private readonly _dialogService: DialogService,
private readonly _router: Router, private readonly _router: Router,
private readonly _dictionaryControllerService: DictionaryControllerService,
private readonly _userService: UserService, private readonly _userService: UserService,
private readonly _fileDownloadService: FileDownloadService, private readonly _fileDownloadService: FileDownloadService,
private readonly _reanalysisControllerService: ReanalysisControllerService, private readonly _reanalysisControllerService: ReanalysisControllerService,
@ -67,7 +73,6 @@ export class FilePreviewScreenComponent implements OnInit {
this.fileId = params.fileId; this.fileId = params.fileId;
this.appStateService.activateFile(this.projectId, this.fileId); this.appStateService.activateFile(this.projectId, this.fileId);
}); });
this.filters = _filtersService.filters;
} }
public get user() { public get user() {
@ -91,7 +96,7 @@ export class FilePreviewScreenComponent implements OnInit {
} }
public ngOnInit(): void { public ngOnInit(): void {
// PDFTRON cache fix this.filters = this._filtersService.getFilters(this.appStateService.dictionaryData);
this._reloadFiles(); this._reloadFiles();
this.appStateService.fileStatusChanged.subscribe((fileStatus) => { this.appStateService.fileStatusChanged.subscribe((fileStatus) => {
if (fileStatus.fileId === this.fileId) { if (fileStatus.fileId === this.fileId) {
@ -273,7 +278,7 @@ export class FilePreviewScreenComponent implements OnInit {
}); });
} }
public setAllFilters(filter: AnnotationFilters, value: boolean, rootKey?: string) { public setAllFilters(filter: string, value: boolean, rootKey?: string) {
if (rootKey) { if (rootKey) {
this.filters[rootKey] = value; this.filters[rootKey] = value;
} else { } else {

View File

@ -1,5 +1,6 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { AnnotationFilters } from '../../../utils/types'; import { AnnotationFilters } from '../../../utils/types';
import { TypeValue } from '@redaction/red-ui-http';
@Injectable({ @Injectable({
providedIn: 'root' providedIn: 'root'
@ -8,18 +9,23 @@ export class FiltersService {
constructor() {} constructor() {}
private _filters: AnnotationFilters = { private _filters: AnnotationFilters = {
hint: { hint: {},
hint_only: false, redaction: {},
vertebrate: false, request: false,
names: false
},
redaction: false,
comment: false,
suggestion: false,
ignore: false ignore: false
}; };
public get filters(): AnnotationFilters { public getFilters(dictionaryData: { [key: string]: TypeValue }): AnnotationFilters {
return JSON.parse(JSON.stringify(this._filters)); const filtersCopy = JSON.parse(JSON.stringify(this._filters));
for (let key of Object.keys(dictionaryData)) {
const typeValue = dictionaryData[key];
if (typeValue.hint === true) {
filtersCopy.hint[key] = false;
}
if (typeValue.hint === false) {
filtersCopy.redaction[key] = false;
}
}
return filtersCopy;
} }
} }

View File

@ -15,6 +15,7 @@ export class AppStateGuard implements CanActivate {
async canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Promise<boolean> { async canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Promise<boolean> {
await this._userService.loadAllUsersIfNecessary(); await this._userService.loadAllUsersIfNecessary();
await this._appStateService.loadAllProjectsIfNecessary(); await this._appStateService.loadAllProjectsIfNecessary();
await this._appStateService.loadDictionaryDataIfNecessary();
return true; return true;
} }

View File

@ -1,19 +1,22 @@
import { EventEmitter, Injectable } from '@angular/core'; import { EventEmitter, Injectable } from '@angular/core';
import { import {
DictionaryControllerService,
FileStatus, FileStatus,
FileUploadControllerService, FileUploadControllerService,
Project, Project,
ProjectControllerService, ProjectControllerService,
ReanalysisControllerService, ReanalysisControllerService,
StatusControllerService StatusControllerService,
TypeValue
} from '@redaction/red-ui-http'; } from '@redaction/red-ui-http';
import { NotificationService, NotificationType } from '../notification/notification.service'; import { NotificationService, NotificationType } from '../notification/notification.service';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
import { Router } from '@angular/router'; import { Router } from '@angular/router';
import { UserService } from '../user/user.service'; import { UserService } from '../user/user.service';
import { interval } from 'rxjs'; import { forkJoin, interval } from 'rxjs';
import { tap } from 'rxjs/operators'; import { tap } from 'rxjs/operators';
import { download } from '../utils/file-download-utils'; import { download } from '../utils/file-download-utils';
import { humanize } from '../utils/functions';
export interface AppState { export interface AppState {
projects: ProjectWrapper[]; projects: ProjectWrapper[];
@ -37,6 +40,7 @@ export class ProjectWrapper {
}) })
export class AppStateService { export class AppStateService {
private _appState: AppState; private _appState: AppState;
private _dictionaryData: { [key: string]: TypeValue } = null;
public fileStatusChanged = new EventEmitter<FileStatus>(); public fileStatusChanged = new EventEmitter<FileStatus>();
@ -48,6 +52,7 @@ export class AppStateService {
private readonly _notificationService: NotificationService, private readonly _notificationService: NotificationService,
private readonly _reanalysisControllerService: ReanalysisControllerService, private readonly _reanalysisControllerService: ReanalysisControllerService,
private readonly _translateService: TranslateService, private readonly _translateService: TranslateService,
private readonly _dictionaryControllerService: DictionaryControllerService,
private readonly _statusControllerService: StatusControllerService private readonly _statusControllerService: StatusControllerService
) { ) {
this._appState = { this._appState = {
@ -90,6 +95,19 @@ export class AppStateService {
); );
} }
get dictionaryData() {
return this._dictionaryData;
}
getDictionaryColor(type: string) {
const color = this._dictionaryData[type].hexColor;
return color ? color : this._dictionaryData['default'].hexColor;
}
getDictionaryLabel(type: string) {
return this._dictionaryData[type]['label'];
}
get isActiveFileDocumentReviewer() { get isActiveFileDocumentReviewer() {
return this._appState.activeFile?.currentReviewer === this._userService.userId; return this._appState.activeFile?.currentReviewer === this._userService.userId;
} }
@ -331,4 +349,53 @@ export class AppStateService {
download(data, 'redaction-report-' + file.filename + '.docx'); download(data, 'redaction-report-' + file.filename + '.docx');
}); });
} }
async loadDictionaryDataIfNecessary() {
if (!this._dictionaryData) {
this._dictionaryData = {};
const typeObs = this._dictionaryControllerService.getAllTypes().pipe(
tap((typesResponse) => {
for (let type of typesResponse.types) {
this._dictionaryData[type.type] = type;
}
})
);
const colorsObs = this._dictionaryControllerService.getColors().pipe(
tap((colors) => {
this._dictionaryData['request'] = {
hexColor: colors.requestAdd,
type: 'request'
};
this._dictionaryData['ignore'] = {
hexColor: colors.notRedacted,
type: 'ignore'
};
this._dictionaryData['default'] = {
hexColor: colors.defaultColor,
type: 'default'
};
this._dictionaryData['add'] = { hexColor: colors.requestAdd, type: 'add' };
this._dictionaryData['remove'] = {
hexColor: colors.requestRemove,
type: 'remove'
};
})
);
const result = await forkJoin([typeObs, colorsObs]).toPromise();
this._dictionaryData['hint'] = { hexColor: '#283241', type: 'hint' };
this._dictionaryData['redaction'] = { hexColor: '#283241', type: 'redaction' };
for (let key of Object.keys(this._dictionaryData)) {
this._dictionaryData[key]['label'] = humanize(key);
}
} else {
return this._dictionaryData;
}
}
getDictionaryTypeValue(key: string) {
const data = this._dictionaryData[key];
return data ? data : this._dictionaryData['default'];
}
} }

View File

@ -72,8 +72,7 @@ export class AnnotationUtils {
annotations: [], annotations: [],
hint: 0, hint: 0,
redaction: 0, redaction: 0,
comment: 0, request: 0,
suggestion: 0,
ignore: 0 ignore: 0
}; };
} }

View File

@ -4,3 +4,11 @@ export function groupBy(xs: any[], key: string) {
return rv; return rv;
}, {}); }, {});
} }
export function humanize(str: string) {
let frags = str.split(/[ \-_]+/);
for (let i = 0; i < frags.length; i++) {
frags[i] = frags[i].charAt(0).toUpperCase() + frags[i].slice(1);
}
return frags.join(' ');
}

View File

@ -2,8 +2,6 @@ import { FileStatus } from '@redaction/red-ui-http';
export type Color = FileStatus.StatusEnum | ProjectStatus.StatusEnum; export type Color = FileStatus.StatusEnum | ProjectStatus.StatusEnum;
export type AnnotationType = 'hint' | 'redaction' | 'suggestion' | 'comment' | 'ignore';
export class SortingOption { export class SortingOption {
label: string; label: string;
order: string; order: string;
@ -11,5 +9,5 @@ export class SortingOption {
} }
export class AnnotationFilters { export class AnnotationFilters {
[key: AnnotationType]: boolean; [key: string]: boolean | {};
} }

View File

@ -442,26 +442,14 @@
"filter-types": { "filter-types": {
"label": "Filter types" "label": "Filter types"
}, },
"hint": {
"label": "Hint annotation",
"hint_only": {
"label": "Hint only"
},
"vertebrate": {
"label": "Vertebrate"
},
"names": {
"label": "Names"
}
},
"redaction": { "redaction": {
"label": "Redaction" "label": "Redaction"
}, },
"comment": { "hint": {
"label": "Comment annotation" "label": "Hint"
}, },
"suggestion": { "request": {
"label": "Suggested redaction" "label": "Redaction Request"
}, },
"ignore": { "ignore": {
"label": "Ignored redaction" "label": "Ignored redaction"

View File

@ -17,6 +17,9 @@ server {
location /project { location /project {
proxy_pass $API_URL; proxy_pass $API_URL;
} }
location /color {
proxy_pass $API_URL;
}
location /user { location /user {
proxy_pass $API_URL; proxy_pass $API_URL;
} }