work in progress direct-draw annotations

This commit is contained in:
Timo Bejan 2020-10-28 22:56:58 +02:00
parent 64cbfbeca4
commit 8236467579
14 changed files with 224 additions and 66 deletions

View File

@ -60,6 +60,7 @@ import { MatDatepickerModule } from '@angular/material/datepicker';
import { MatNativeDateModule } from '@angular/material/core'; import { MatNativeDateModule } from '@angular/material/core';
import { MatInputModule } from '@angular/material/input'; import { MatInputModule } from '@angular/material/input';
import { ProjectMemberGuard } from './auth/project-member-guard.service'; import { ProjectMemberGuard } from './auth/project-member-guard.service';
import { HumanizePipe } from './utils/humanize.pipe';
export function HttpLoaderFactory(httpClient: HttpClient) { export function HttpLoaderFactory(httpClient: HttpClient) {
return new TranslateHttpLoader(httpClient, '/assets/i18n/', '.json'); return new TranslateHttpLoader(httpClient, '/assets/i18n/', '.json');
@ -84,7 +85,8 @@ export function HttpLoaderFactory(httpClient: HttpClient) {
SimpleDoughnutChartComponent, SimpleDoughnutChartComponent,
ManualRedactionDialogComponent, ManualRedactionDialogComponent,
AnnotationIconComponent, AnnotationIconComponent,
AuthErrorComponent AuthErrorComponent,
HumanizePipe
], ],
imports: [ imports: [
BrowserModule, BrowserModule,

View File

@ -16,6 +16,7 @@ import { AddEditProjectDialogComponent } from './add-edit-project-dialog/add-edi
import { AssignOwnerDialogComponent } from './assign-owner-dialog/assign-owner-dialog.component'; import { AssignOwnerDialogComponent } from './assign-owner-dialog/assign-owner-dialog.component';
import { ManualRedactionDialogComponent } from './manual-redaction-dialog/manual-redaction-dialog.component'; import { ManualRedactionDialogComponent } from './manual-redaction-dialog/manual-redaction-dialog.component';
import { Annotations } from '@pdftron/webviewer'; import { Annotations } from '@pdftron/webviewer';
import { ManualRedactionEntryWrapper } from '../screens/file/model/manual-redaction-entry.wrapper';
const dialogConfig = { const dialogConfig = {
width: '600px', width: '600px',
@ -79,7 +80,7 @@ export class DialogService {
} }
public openManualRedactionDialog( public openManualRedactionDialog(
$event: ManualRedactionEntry, $event: ManualRedactionEntryWrapper,
cb?: Function cb?: Function
): MatDialogRef<ManualRedactionDialogComponent> { ): MatDialogRef<ManualRedactionDialogComponent> {
const ref = this._dialog.open(ManualRedactionDialogComponent, { const ref = this._dialog.open(ManualRedactionDialogComponent, {
@ -89,6 +90,7 @@ export class DialogService {
}); });
ref.afterClosed().subscribe((result) => { ref.afterClosed().subscribe((result) => {
console.log(result);
if (cb) { if (cb) {
cb(result); cb(result);
} }

View File

@ -1,36 +1,39 @@
<section class="dialog"> <section class="dialog">
<form (submit)="handleAddRedaction()" [formGroup]="redactionForm"> <form (submit)="handleAddRedaction()" [formGroup]="redactionForm">
<div class="dialog-header heading-l" translate="manual-redaction.dialog.header.label"></div> <div class="dialog-header heading-l" [translate]="title"></div>
<div class="dialog-content"> <div class="dialog-content">
<div class="red-input-group"> <div class="red-input-group">
<label translate="manual-redaction.dialog.content.text.label"></label> <label translate="manual-redaction.dialog.content.text.label"></label>
</div> </div>
{{ addRedactionRequest.value }} {{ manualRedactionEntryWrapper.manualRedactionEntry.value }}
<div class="red-input-group"> <div class="red-input-group">
<label translate="manual-redaction.dialog.content.reason.label"></label> <label translate="manual-redaction.dialog.content.reason.label"></label>
<textarea formControlName="reason" name="reason" type="text" rows="2"></textarea> <input formControlName="reason" name="reason" type="text" rows="2" />
</div> </div>
<div class="red-input-group"> <div class="red-input-group">
<label translate="manual-redaction.dialog.content.comment.label"></label> <label translate="manual-redaction.dialog.content.comment.label"></label>
<textarea formControlName="comment" name="comment" type="text" rows="2"></textarea> <textarea formControlName="comment" name="comment" type="text" rows="4"></textarea>
</div> </div>
<div class="red-input-group"> <div class="red-input-group">
<label translate="manual-redaction.dialog.content.dictionary.label"></label> <label translate="manual-redaction.dialog.content.dictionary.label"></label>
<mat-select formControlName="dictionary"> <mat-select formControlName="dictionary">
<mat-option <mat-option
*ngFor="let dictionary of dictionaries | async" *ngFor="let dictionary of dictionaryOptions"
[value]="dictionary.type" [value]="dictionary.type"
> >
{{ dictionary.type }} {{ dictionary.label }}
</mat-option> </mat-option>
</mat-select> </mat-select>
</div> </div>
<div class="red-input-group"> <div
class="red-input-group"
[class.hidden]="this.manualRedactionEntryWrapper.type === 'HINT'"
>
<mat-checkbox color="primary" formControlName="addToDictionary">{{ <mat-checkbox color="primary" formControlName="addToDictionary">{{
'manual-redaction.dialog.content.dictionary.add.label' | translate 'manual-redaction.dialog.content.dictionary.add.label' | translate
}}</mat-checkbox> }}</mat-checkbox>

View File

@ -10,9 +10,8 @@ import {
} 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 { map } from 'rxjs/operators';
import { Observable } from 'rxjs';
import { UserService } from '../../user/user.service'; import { UserService } from '../../user/user.service';
import { ManualRedactionEntryWrapper } from '../../screens/file/model/manual-redaction-entry.wrapper';
@Component({ @Component({
selector: 'redaction-manual-redaction-dialog', selector: 'redaction-manual-redaction-dialog',
@ -20,8 +19,8 @@ import { UserService } from '../../user/user.service';
styleUrls: ['./manual-redaction-dialog.component.scss'] styleUrls: ['./manual-redaction-dialog.component.scss']
}) })
export class ManualRedactionDialogComponent implements OnInit { export class ManualRedactionDialogComponent implements OnInit {
dictionaryOptions: TypeValue[] = [];
redactionForm: FormGroup; redactionForm: FormGroup;
dictionaries: Observable<Array<TypeValue>>;
isDocumentAdmin: boolean; isDocumentAdmin: boolean;
constructor( constructor(
@ -33,7 +32,7 @@ export class ManualRedactionDialogComponent implements OnInit {
private readonly _manualRedactionControllerService: ManualRedactionControllerService, private readonly _manualRedactionControllerService: ManualRedactionControllerService,
private readonly _dictionaryControllerService: DictionaryControllerService, private readonly _dictionaryControllerService: DictionaryControllerService,
public dialogRef: MatDialogRef<ManualRedactionDialogComponent>, public dialogRef: MatDialogRef<ManualRedactionDialogComponent>,
@Inject(MAT_DIALOG_DATA) public addRedactionRequest: AddRedactionRequest @Inject(MAT_DIALOG_DATA) public manualRedactionEntryWrapper: ManualRedactionEntryWrapper
) {} ) {}
async ngOnInit() { async ngOnInit() {
@ -47,9 +46,18 @@ export class ManualRedactionDialogComponent implements OnInit {
dictionary: [null, Validators.required], dictionary: [null, Validators.required],
comment: commentField comment: commentField
}); });
this.dictionaries = this._dictionaryControllerService
.getAllTypes() for (let key of Object.keys(this._appStateService.dictionaryData)) {
.pipe(map((r) => r.types)); const dictionaryData = this._appStateService.dictionaryData[key];
if (!dictionaryData.virtual) {
if (dictionaryData.hint && this.manualRedactionEntryWrapper.type === 'HINT') {
this.dictionaryOptions.push(dictionaryData);
}
if (!dictionaryData.hint && this.manualRedactionEntryWrapper.type === 'REDACTION') {
this.dictionaryOptions.push(dictionaryData);
}
}
}
} }
handleAddRedaction() { handleAddRedaction() {
@ -61,7 +69,7 @@ export class ManualRedactionDialogComponent implements OnInit {
} }
suggestManualRedaction() { suggestManualRedaction() {
const mre = Object.assign({}, this.addRedactionRequest); const mre = Object.assign({}, this.manualRedactionEntryWrapper.manualRedactionEntry);
this._enhanceManualRedaction(mre); this._enhanceManualRedaction(mre);
this._manualRedactionControllerService this._manualRedactionControllerService
.requestAddRedaction( .requestAddRedaction(
@ -71,7 +79,6 @@ export class ManualRedactionDialogComponent implements OnInit {
) )
.subscribe( .subscribe(
(ok) => { (ok) => {
this._appStateService.reanalyseActiveFile();
this._notificationService.showToastNotification( this._notificationService.showToastNotification(
this._translateService.instant( this._translateService.instant(
'manual-redaction.dialog.add-redaction.success.label' 'manual-redaction.dialog.add-redaction.success.label'
@ -79,7 +86,7 @@ export class ManualRedactionDialogComponent implements OnInit {
null, null,
NotificationType.SUCCESS NotificationType.SUCCESS
); );
this.dialogRef.close(); this.dialogRef.close({ mode: 'suggestion', request: mre });
}, },
(err) => { (err) => {
this._notificationService.showToastNotification( this._notificationService.showToastNotification(
@ -95,7 +102,7 @@ export class ManualRedactionDialogComponent implements OnInit {
} }
addManualRedaction() { addManualRedaction() {
const mre = Object.assign({}, this.addRedactionRequest); const mre = Object.assign({}, this.manualRedactionEntryWrapper.manualRedactionEntry);
this._enhanceManualRedaction(mre); this._enhanceManualRedaction(mre);
this._manualRedactionControllerService this._manualRedactionControllerService
.addRedaction( .addRedaction(
@ -105,7 +112,6 @@ export class ManualRedactionDialogComponent implements OnInit {
) )
.subscribe( .subscribe(
(ok) => { (ok) => {
this._appStateService.reanalyseActiveFile();
this._notificationService.showToastNotification( this._notificationService.showToastNotification(
this._translateService.instant( this._translateService.instant(
'manual-redaction.dialog.add-redaction.success.label' 'manual-redaction.dialog.add-redaction.success.label'
@ -113,7 +119,7 @@ export class ManualRedactionDialogComponent implements OnInit {
null, null,
NotificationType.SUCCESS NotificationType.SUCCESS
); );
this.dialogRef.close(); this.dialogRef.close({ mode: 'redaction', request: mre });
}, },
(err) => { (err) => {
this._notificationService.showToastNotification( this._notificationService.showToastNotification(
@ -128,9 +134,28 @@ export class ManualRedactionDialogComponent implements OnInit {
); );
} }
get title() {
if (this.isDocumentAdmin) {
if (this.manualRedactionEntryWrapper.type === 'HINT') {
return 'manual-redaction.dialog.header.hint.label';
} else {
return 'manual-redaction.dialog.header.redaction.label';
}
} else {
if (this.manualRedactionEntryWrapper.type === 'HINT') {
return 'manual-redaction.dialog.header.request-hint.label';
} else {
return 'manual-redaction.dialog.header.request-redaction.label';
}
}
}
private _enhanceManualRedaction(addRedactionRequest: AddRedactionRequest) { private _enhanceManualRedaction(addRedactionRequest: AddRedactionRequest) {
addRedactionRequest.type = this.redactionForm.get('dictionary').value; addRedactionRequest.type = this.redactionForm.get('dictionary').value;
addRedactionRequest.addToDictionary = this.redactionForm.get('addToDictionary').value; addRedactionRequest.addToDictionary =
this.manualRedactionEntryWrapper.type === 'HINT'
? true
: this.redactionForm.get('addToDictionary').value;
addRedactionRequest.reason = this.redactionForm.get('reason').value; addRedactionRequest.reason = this.redactionForm.get('reason').value;
const commentValue = this.redactionForm.get('comment').value; const commentValue = this.redactionForm.get('comment').value;
addRedactionRequest.comment = commentValue ? { text: commentValue } : null; addRedactionRequest.comment = commentValue ? { text: commentValue } : null;

View File

@ -23,6 +23,7 @@ import { saveAs } from 'file-saver';
import { FileType } from '../model/file-type'; import { FileType } from '../model/file-type';
import { DialogService } from '../../../dialogs/dialog.service'; import { DialogService } from '../../../dialogs/dialog.service';
import { MatDialogRef, MatDialogState } from '@angular/material/dialog'; import { MatDialogRef, MatDialogState } from '@angular/material/dialog';
import { ManualRedactionEntryWrapper } from '../model/manual-redaction-entry.wrapper';
@Component({ @Component({
selector: 'redaction-file-preview-screen', selector: 'redaction-file-preview-screen',
@ -94,7 +95,8 @@ export class FilePreviewScreenComponent implements OnInit {
this._reloadFiles(); this._reloadFiles();
this.appStateService.fileStatusChanged.subscribe((fileStatus) => { this.appStateService.fileStatusChanged.subscribe((fileStatus) => {
if (fileStatus.fileId === this.fileId) { if (fileStatus.fileId === this.fileId) {
this._reloadFiles(); // no more automatic reloads
// this._reloadFiles();
} }
}); });
} }
@ -185,23 +187,29 @@ export class FilePreviewScreenComponent implements OnInit {
this._scrollAnnotationsToPage(pageNumber, 'always'); this._scrollAnnotationsToPage(pageNumber, 'always');
} }
public openManualRedactionDialog($event: ManualRedactionEntry) { public openManualRedactionDialog($event: ManualRedactionEntryWrapper) {
this.ngZone.run(() => { this.ngZone.run(() => {
this._dialogRef = this._dialogService.openManualRedactionDialog( this._dialogRef = this._dialogService.openManualRedactionDialog(
$event, $event,
(annotation) => { (response: any) => {
// const annotManager = this.activeViewer.annotManager; const request: ManualRedactionEntry = response.request;
// const rectangleAnnot = new this.activeViewer.Annotations.RectangleAnnotation();
// rectangleAnnot.PageNumber = 1; const annotManager = this.activeViewer.annotManager;
// // values are in page coordinates with (0, 0) in the top left const originalQuads = request.quads;
// rectangleAnnot.X = 100; for (const key of Object.keys(originalQuads)) {
// rectangleAnnot.Y = 150; const pageNumber = parseInt(key, 10);
// rectangleAnnot.Width = 200; const highlight = new this.activeViewer.Annotations.TextHighlightAnnotation();
// rectangleAnnot.Height = 50; highlight.PageNumber = pageNumber;
// rectangleAnnot.Author = annotManager.getCurrentUser(); highlight.StrokeColor = new this.activeViewer.Annotations.Color(
// 255,
// annotManager.addAnnotation(rectangleAnnot,true); 255,
// annotManager.redrawAnnotation(rectangleAnnot); 0
);
highlight.Quads = originalQuads[key];
highlight.Id = this._computeId($event.type, request);
annotManager.addAnnotation(highlight, true);
annotManager.redrawAnnotation(highlight);
}
} }
); );
}); });
@ -487,4 +495,8 @@ export class FilePreviewScreenComponent implements OnInit {
this.applyFilters(); this.applyFilters();
this._changeDetectorRef.detectChanges(); this._changeDetectorRef.detectChanges();
} }
private _computeId(type: 'HINT' | 'REDACTION', request: ManualRedactionEntry) {
return 'request:' + type.toLowerCase() + ':' + new Date().getTime();
}
} }

View File

@ -0,0 +1,10 @@
import { ManualRedactionEntry } from '@redaction/red-ui-http';
export class ManualRedactionEntryWrapper {
public mode: 'REQUEST' | 'ACTUAL';
constructor(
public readonly manualRedactionEntry: ManualRedactionEntry,
public readonly type: 'HINT' | 'REDACTION'
) {}
}

View File

@ -4,6 +4,7 @@ import {
ElementRef, ElementRef,
EventEmitter, EventEmitter,
Input, Input,
NgZone,
OnChanges, OnChanges,
OnInit, OnInit,
Output, Output,
@ -17,6 +18,7 @@ import { TranslateService } from '@ngx-translate/core';
import { FileDownloadService } from '../service/file-download.service'; import { FileDownloadService } from '../service/file-download.service';
import { Subject } from 'rxjs'; import { Subject } from 'rxjs';
import { throttleTime } from 'rxjs/operators'; import { throttleTime } from 'rxjs/operators';
import { ManualRedactionEntryWrapper } from '../model/manual-redaction-entry.wrapper';
export interface ViewerState { export interface ViewerState {
displayMode?: any; displayMode?: any;
@ -43,7 +45,7 @@ export class PdfViewerComponent implements OnInit, AfterViewInit, OnChanges {
@Output() fileReady = new EventEmitter(); @Output() fileReady = new EventEmitter();
@Output() annotationsAdded = new EventEmitter<Annotations.Annotation[]>(); @Output() annotationsAdded = new EventEmitter<Annotations.Annotation[]>();
@Output() annotationSelected = new EventEmitter<Annotations.Annotation>(); @Output() annotationSelected = new EventEmitter<Annotations.Annotation>();
@Output() manualAnnotationRequested = new EventEmitter<ManualRedactionEntry>(); @Output() manualAnnotationRequested = new EventEmitter<ManualRedactionEntryWrapper>();
@Output() pageChanged = new EventEmitter<number>(); @Output() pageChanged = new EventEmitter<number>();
@Output() keyUp = new EventEmitter<KeyboardEvent>(); @Output() keyUp = new EventEmitter<KeyboardEvent>();
@ -57,17 +59,21 @@ export class PdfViewerComponent implements OnInit, AfterViewInit, OnChanges {
constructor( constructor(
private readonly _translateService: TranslateService, private readonly _translateService: TranslateService,
private readonly _fileDownloadService: FileDownloadService, private readonly _fileDownloadService: FileDownloadService,
private readonly _appConfigService: AppConfigService private readonly _appConfigService: AppConfigService,
private readonly _ngZone: NgZone
) {} ) {}
ngOnInit() { ngOnInit() {
this._restoreViewerState = this._restoreViewerState.bind(this); this._restoreViewerState = this._restoreViewerState.bind(this);
// always publish all existing annotations this way everything gets drawn always // always publish all existing annotations this way everything gets drawn always
this._annotationEventDebouncer this._annotationEventDebouncer.pipe(throttleTime(300)).subscribe((value) => {
.pipe(throttleTime(300)) this.annotationsAdded.emit(this.instance.annotManager.getAnnotationsList());
.subscribe((value) => // nasty double-emit fix, the annotationList is not updated when the event is fired
this.annotationsAdded.emit(this.instance.annotManager.getAnnotationsList()) setTimeout(
() => this.annotationsAdded.emit(this.instance.annotManager.getAnnotationsList()),
200
); );
});
} }
ngOnChanges(changes: SimpleChanges): void { ngOnChanges(changes: SimpleChanges): void {
@ -95,6 +101,7 @@ export class PdfViewerComponent implements OnInit, AfterViewInit, OnChanges {
this._configureTextPopup(); this._configureTextPopup();
this._configureHeader(); this._configureHeader();
instance.annotManager.on('annotationChanged', (annotations, action) => { instance.annotManager.on('annotationChanged', (annotations, action) => {
console.log(action, annotations);
if (action === 'add') { if (action === 'add') {
this._annotationEventDebouncer.next(annotations); this._annotationEventDebouncer.next(annotations);
} }
@ -109,7 +116,7 @@ export class PdfViewerComponent implements OnInit, AfterViewInit, OnChanges {
}); });
instance.docViewer.on('pageComplete', (p) => { instance.docViewer.on('pageComplete', (p) => {
this.pageChanged.emit(p); this._ngZone.run(() => this.pageChanged.emit(p));
}); });
instance.docViewer.on('documentLoaded', this._restoreViewerState); instance.docViewer.on('documentLoaded', this._restoreViewerState);
@ -153,6 +160,37 @@ export class PdfViewerComponent implements OnInit, AfterViewInit, OnChanges {
} }
private _configureTextPopup() { private _configureTextPopup() {
this.instance.textPopup.add(<any>{
type: 'actionButton',
img: '/assets/icons/general/search-viewer.svg',
title: this._translateService.instant('pdf-viewer.text-popup.actions.search.label'),
onClick: () => {
const text = this.instance.docViewer.getSelectedText();
const searchOptions = {
caseSensitive: true, // match case
wholeWord: true, // match whole words only
wildcard: false, // allow using '*' as a wildcard value
regex: false, // string is treated as a regular expression
searchUp: false, // search from the end of the document upwards
ambientString: true // return ambient string as part of the result
};
this.instance.openElements(['searchPanel']);
setTimeout(() => {
this.instance.searchTextFull(text, searchOptions);
}, 250);
}
});
this.instance.textPopup.add(<any>{
type: 'actionButton',
img: '/assets/icons/general/add-hint.svg',
title: this._translateService.instant(
'pdf-viewer.text-popup.actions.suggestion-hint.label'
),
onClick: () => {
const mre = this._getManualRedactionEntry();
this.manualAnnotationRequested.emit(new ManualRedactionEntryWrapper(mre, 'HINT'));
}
});
this.instance.textPopup.add(<any>{ this.instance.textPopup.add(<any>{
type: 'actionButton', type: 'actionButton',
img: '/assets/icons/general/add-redaction.svg', img: '/assets/icons/general/add-redaction.svg',
@ -160,6 +198,15 @@ export class PdfViewerComponent implements OnInit, AfterViewInit, OnChanges {
'pdf-viewer.text-popup.actions.suggestion-redaction.label' 'pdf-viewer.text-popup.actions.suggestion-redaction.label'
), ),
onClick: () => { onClick: () => {
const mre = this._getManualRedactionEntry();
this.manualAnnotationRequested.emit(
new ManualRedactionEntryWrapper(mre, 'REDACTION')
);
}
});
}
private _getManualRedactionEntry(): ManualRedactionEntry {
const selectedQuads = this.instance.docViewer.getSelectedTextQuads(); const selectedQuads = this.instance.docViewer.getSelectedTextQuads();
const text = this.instance.docViewer.getSelectedText(); const text = this.instance.docViewer.getSelectedText();
const entry: ManualRedactionEntry = { positions: [] }; const entry: ManualRedactionEntry = { positions: [] };
@ -168,10 +215,9 @@ export class PdfViewerComponent implements OnInit, AfterViewInit, OnChanges {
entry.positions.push(this.toPosition(parseInt(key, 10), quad)); entry.positions.push(this.toPosition(parseInt(key, 10), quad));
} }
} }
entry.quads = selectedQuads;
entry.value = text; entry.value = text;
this.manualAnnotationRequested.emit(entry); return entry;
}
});
} }
private toPosition(page: number, selectedQuad: any): Rectangle { private toPosition(page: number, selectedQuad: any): Rectangle {

View File

@ -103,7 +103,7 @@ export class AppStateService {
} }
getDictionaryLabel(type: string) { getDictionaryLabel(type: string) {
return this._dictionaryData[type]['label']; return this._dictionaryData[type].label;
} }
get isActiveFileDocumentReviewer() { get isActiveFileDocumentReviewer() {
@ -362,30 +362,42 @@ export class AppStateService {
tap((colors) => { tap((colors) => {
this._dictionaryData['request'] = { this._dictionaryData['request'] = {
hexColor: colors.requestAdd, hexColor: colors.requestAdd,
type: 'request' type: 'request',
virtual: true
}; };
this._dictionaryData['ignore'] = { this._dictionaryData['ignore'] = {
hexColor: colors.notRedacted, hexColor: colors.notRedacted,
type: 'ignore' type: 'ignore',
virtual: true
}; };
this._dictionaryData['default'] = { this._dictionaryData['default'] = {
hexColor: colors.defaultColor, hexColor: colors.defaultColor,
type: 'default' type: 'default',
virtual: true
};
this._dictionaryData['add'] = {
hexColor: colors.requestAdd,
type: 'add',
virtual: true
}; };
this._dictionaryData['add'] = { hexColor: colors.requestAdd, type: 'add' };
this._dictionaryData['remove'] = { this._dictionaryData['remove'] = {
hexColor: colors.requestRemove, hexColor: colors.requestRemove,
type: 'remove' type: 'remove',
virtual: true
}; };
}) })
); );
const result = await forkJoin([typeObs, colorsObs]).toPromise(); const result = await forkJoin([typeObs, colorsObs]).toPromise();
this._dictionaryData['hint'] = { hexColor: '#283241', type: 'hint' }; this._dictionaryData['hint'] = { hexColor: '#283241', type: 'hint', virtual: true };
this._dictionaryData['redaction'] = { hexColor: '#283241', type: 'redaction' }; this._dictionaryData['redaction'] = {
hexColor: '#283241',
type: 'redaction',
virtual: true
};
for (const key of Object.keys(this._dictionaryData)) { for (const key of Object.keys(this._dictionaryData)) {
this._dictionaryData[key]['label'] = humanize(key); this._dictionaryData[key].label = humanize(key);
} }
} else { } else {
return this._dictionaryData; return this._dictionaryData;

View File

@ -0,0 +1,11 @@
import { Pipe, PipeTransform } from '@angular/core';
import { humanize } from './functions';
@Pipe({
name: 'humanize'
})
export class HumanizePipe implements PipeTransform {
transform(item: string): any {
return humanize(item);
}
}

View File

@ -35,7 +35,18 @@
}, },
"dialog": { "dialog": {
"header": { "header": {
"label": "Add Manual Redaction" "hint": {
"label": "Add Hint"
},
"redaction": {
"label": "Add Redaction"
},
"request-hint": {
"label": "Request Hint"
},
"request-redaction": {
"label": "Request Redaction"
}
}, },
"add-redaction": { "add-redaction": {
"success": { "success": {
@ -47,7 +58,7 @@
}, },
"actions": { "actions": {
"save": { "save": {
"label": "Save Manual Redaction" "label": "Save"
} }
}, },
"content": { "content": {
@ -92,6 +103,12 @@
"actions": { "actions": {
"suggestion-redaction": { "suggestion-redaction": {
"label": "Suggest Redaction" "label": "Suggest Redaction"
},
"suggestion-hint": {
"label": "Suggest Hint"
},
"search": {
"label": "Search for selection"
} }
} }
} }

View File

@ -0,0 +1,4 @@
<svg height="512pt" viewBox="-82 0 512 512.00163" width="512pt" xmlns="http://www.w3.org/2000/svg">
<path fill="rgb(136,142,149)"
d="m329.089844 94.824219c-1.921875-3.75-6.515625-5.230469-10.265625-3.3125-3.75 1.921875-5.234375 6.519531-3.3125 10.269531 11.523437 22.5 17.367187 46.820312 17.367187 72.285156 0 34.808594-11.054687 67.855469-31.96875 95.5625-8.675781 11.496094-13.453125 25.953125-13.453125 40.707032v.972656c0 20.777344-16.902343 37.679687-37.675781 37.679687h-68.085938v-96.597656c27.527344-3.738281 48.820313-27.382813 48.820313-55.917969 0-4.214844-3.417969-7.628906-7.628906-7.628906-4.214844 0-7.628907 3.414062-7.628907 7.628906 0 22.710938-18.476562 41.191406-41.191406 41.191406s-41.191406-18.480468-41.191406-41.191406c0-4.214844-3.414062-7.628906-7.628906-7.628906-4.210938 0-7.628906 3.414062-7.628906 7.628906 0 28.535156 21.292968 52.179688 48.820312 55.917969v96.597656h-68.085938c-20.773437 0-37.675781-16.902343-37.675781-37.679687v-.984375c0-14.839844-4.816406-29.128907-13.929687-41.324219-20.273438-27.144531-31.160156-59.441406-31.484375-93.394531-.402344-42.5 15.941406-82.65625 46.015625-113.074219 30.066406-30.40625 70.015625-47.195312 112.492187-47.273438h.300781c49.964844 0 96.007813 22.804688 126.355469 62.601563 2.558594 3.347656 7.34375 3.992187 10.691407 1.4375 3.351562-2.554687 3.996093-7.339844 1.441406-10.6875-15.933594-20.898437-36.722656-38.191406-60.117188-50.007813-24.539062-12.394531-51.058594-18.6718745-78.699218-18.601562-46.574219.0859375-90.367188 18.484375-123.3125 51.800781-32.957032 33.332031-50.863282 77.351563-50.4218755 123.949219.3554685 37.21875 12.2890625 72.621094 34.5195315 102.378906 7.125 9.539063 10.890625 20.671875 10.890625 32.195313v.984375c0 16.269531 7.382812 30.839844 18.964843 40.558594-11.171874 5.074218-18.964843 16.328124-18.964843 29.375 0 14.832031 10.070312 27.351562 23.726562 31.101562-3.539062 5.175781-5.609375 11.425781-5.609375 18.152344 0 17.785156 14.46875 32.253906 32.253906 32.253906h21.652344c3.742188 27.769531 27.578125 49.25 56.355469 49.25s52.613281-21.484375 56.351563-49.25h21.652343c17.785157 0 32.253907-14.46875 32.253907-32.253906 0-6.726563-2.070313-12.976563-5.605469-18.152344 13.65625-3.75 23.722656-16.269531 23.722656-31.101562 0-12.910157-7.628906-24.066407-18.609375-29.214844 11.691406-9.71875 19.152344-24.359375 19.152344-40.714844v-.972656c0-11.460938 3.683594-22.652344 10.371094-31.519532 22.925781-30.375 35.046874-66.601562 35.046874-104.75 0-27.910156-6.40625-54.570312-19.042968-79.246093zm-155.292969 401.921875c-20.347656 0-37.316406-14.679688-40.910156-33.996094h81.820312c-3.59375 19.316406-20.566406 33.996094-40.910156 33.996094zm96.121094-132.5c9.375 0 17 7.625 17 16.996094 0 9.375-7.625 17-17 17h-132.792969c-4.210938 0-7.628906 3.414062-7.628906 7.625 0 4.214843 3.417968 7.628906 7.628906 7.628906h114.675781c9.375 0 17 7.625 17 17 0 9.371094-7.625 16.996094-17 16.996094h-156.011719c-9.371093 0-16.996093-7.625-16.996093-16.996094 0-9.375 7.625-17 16.996093-17h10.824219c4.210938 0 7.625-3.414063 7.625-7.628906 0-4.210938-3.414062-7.625-7.625-7.625h-28.941406c-9.371094 0-16.996094-7.628907-16.996094-17 0-9.375 7.625-16.996094 16.996094-16.996094zm0 0"/>
</svg>

After

Width:  |  Height:  |  Size: 3.1 KiB

View File

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="iso-8859-1"?>
<!-- Generator: Adobe Illustrator 19.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 512.005 512.005" style="enable-background:new 0 0 512.005 512.005;" xml:space="preserve">
<g fill="rgb(136,142,149)">
<path d="M505.749,475.587l-145.6-145.6c28.203-34.837,45.184-79.104,45.184-127.317c0-111.744-90.923-202.667-202.667-202.667
S0,90.925,0,202.669s90.923,202.667,202.667,202.667c48.213,0,92.48-16.981,127.317-45.184l145.6,145.6
c4.16,4.16,9.621,6.251,15.083,6.251s10.923-2.091,15.083-6.251C514.091,497.411,514.091,483.928,505.749,475.587z
M202.667,362.669c-88.235,0-160-71.765-160-160s71.765-160,160-160s160,71.765,160,160S290.901,362.669,202.667,362.669z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 875 B

View File

@ -22,6 +22,7 @@ export interface ManualRedactionEntry {
type?: string; type?: string;
user?: string; user?: string;
value?: string; value?: string;
[key: string]: any;
} }
export namespace ManualRedactionEntry { export namespace ManualRedactionEntry {

View File

@ -30,4 +30,6 @@ export interface TypeValue {
* The nonnull entry type. * The nonnull entry type.
*/ */
type?: string; type?: string;
[key: string]: any;
} }