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 { MatInputModule } from '@angular/material/input';
import { ProjectMemberGuard } from './auth/project-member-guard.service';
import { HumanizePipe } from './utils/humanize.pipe';
export function HttpLoaderFactory(httpClient: HttpClient) {
return new TranslateHttpLoader(httpClient, '/assets/i18n/', '.json');
@ -84,7 +85,8 @@ export function HttpLoaderFactory(httpClient: HttpClient) {
SimpleDoughnutChartComponent,
ManualRedactionDialogComponent,
AnnotationIconComponent,
AuthErrorComponent
AuthErrorComponent,
HumanizePipe
],
imports: [
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 { ManualRedactionDialogComponent } from './manual-redaction-dialog/manual-redaction-dialog.component';
import { Annotations } from '@pdftron/webviewer';
import { ManualRedactionEntryWrapper } from '../screens/file/model/manual-redaction-entry.wrapper';
const dialogConfig = {
width: '600px',
@ -79,7 +80,7 @@ export class DialogService {
}
public openManualRedactionDialog(
$event: ManualRedactionEntry,
$event: ManualRedactionEntryWrapper,
cb?: Function
): MatDialogRef<ManualRedactionDialogComponent> {
const ref = this._dialog.open(ManualRedactionDialogComponent, {
@ -89,6 +90,7 @@ export class DialogService {
});
ref.afterClosed().subscribe((result) => {
console.log(result);
if (cb) {
cb(result);
}

View File

@ -1,36 +1,39 @@
<section class="dialog">
<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="red-input-group">
<label translate="manual-redaction.dialog.content.text.label"></label>
</div>
{{ addRedactionRequest.value }}
{{ manualRedactionEntryWrapper.manualRedactionEntry.value }}
<div class="red-input-group">
<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 class="red-input-group">
<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 class="red-input-group">
<label translate="manual-redaction.dialog.content.dictionary.label"></label>
<mat-select formControlName="dictionary">
<mat-option
*ngFor="let dictionary of dictionaries | async"
*ngFor="let dictionary of dictionaryOptions"
[value]="dictionary.type"
>
{{ dictionary.type }}
{{ dictionary.label }}
</mat-option>
</mat-select>
</div>
<div class="red-input-group">
<div
class="red-input-group"
[class.hidden]="this.manualRedactionEntryWrapper.type === 'HINT'"
>
<mat-checkbox color="primary" formControlName="addToDictionary">{{
'manual-redaction.dialog.content.dictionary.add.label' | translate
}}</mat-checkbox>

View File

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

View File

@ -23,6 +23,7 @@ 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';
@Component({
selector: 'redaction-file-preview-screen',
@ -94,7 +95,8 @@ export class FilePreviewScreenComponent implements OnInit {
this._reloadFiles();
this.appStateService.fileStatusChanged.subscribe((fileStatus) => {
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');
}
public openManualRedactionDialog($event: ManualRedactionEntry) {
public openManualRedactionDialog($event: ManualRedactionEntryWrapper) {
this.ngZone.run(() => {
this._dialogRef = this._dialogService.openManualRedactionDialog(
$event,
(annotation) => {
// const annotManager = this.activeViewer.annotManager;
// const rectangleAnnot = new this.activeViewer.Annotations.RectangleAnnotation();
// rectangleAnnot.PageNumber = 1;
// // values are in page coordinates with (0, 0) in the top left
// rectangleAnnot.X = 100;
// rectangleAnnot.Y = 150;
// rectangleAnnot.Width = 200;
// rectangleAnnot.Height = 50;
// rectangleAnnot.Author = annotManager.getCurrentUser();
//
// annotManager.addAnnotation(rectangleAnnot,true);
// annotManager.redrawAnnotation(rectangleAnnot);
(response: any) => {
const request: ManualRedactionEntry = response.request;
const annotManager = this.activeViewer.annotManager;
const originalQuads = request.quads;
for (const key of Object.keys(originalQuads)) {
const pageNumber = parseInt(key, 10);
const highlight = new this.activeViewer.Annotations.TextHighlightAnnotation();
highlight.PageNumber = pageNumber;
highlight.StrokeColor = new this.activeViewer.Annotations.Color(
255,
255,
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._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,
EventEmitter,
Input,
NgZone,
OnChanges,
OnInit,
Output,
@ -17,6 +18,7 @@ import { TranslateService } from '@ngx-translate/core';
import { FileDownloadService } from '../service/file-download.service';
import { Subject } from 'rxjs';
import { throttleTime } from 'rxjs/operators';
import { ManualRedactionEntryWrapper } from '../model/manual-redaction-entry.wrapper';
export interface ViewerState {
displayMode?: any;
@ -43,7 +45,7 @@ export class PdfViewerComponent implements OnInit, AfterViewInit, OnChanges {
@Output() fileReady = new EventEmitter();
@Output() annotationsAdded = 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() keyUp = new EventEmitter<KeyboardEvent>();
@ -57,17 +59,21 @@ export class PdfViewerComponent implements OnInit, AfterViewInit, OnChanges {
constructor(
private readonly _translateService: TranslateService,
private readonly _fileDownloadService: FileDownloadService,
private readonly _appConfigService: AppConfigService
private readonly _appConfigService: AppConfigService,
private readonly _ngZone: NgZone
) {}
ngOnInit() {
this._restoreViewerState = this._restoreViewerState.bind(this);
// always publish all existing annotations this way everything gets drawn always
this._annotationEventDebouncer
.pipe(throttleTime(300))
.subscribe((value) =>
this.annotationsAdded.emit(this.instance.annotManager.getAnnotationsList())
this._annotationEventDebouncer.pipe(throttleTime(300)).subscribe((value) => {
this.annotationsAdded.emit(this.instance.annotManager.getAnnotationsList());
// nasty double-emit fix, the annotationList is not updated when the event is fired
setTimeout(
() => this.annotationsAdded.emit(this.instance.annotManager.getAnnotationsList()),
200
);
});
}
ngOnChanges(changes: SimpleChanges): void {
@ -95,6 +101,7 @@ export class PdfViewerComponent implements OnInit, AfterViewInit, OnChanges {
this._configureTextPopup();
this._configureHeader();
instance.annotManager.on('annotationChanged', (annotations, action) => {
console.log(action, annotations);
if (action === 'add') {
this._annotationEventDebouncer.next(annotations);
}
@ -109,7 +116,7 @@ export class PdfViewerComponent implements OnInit, AfterViewInit, OnChanges {
});
instance.docViewer.on('pageComplete', (p) => {
this.pageChanged.emit(p);
this._ngZone.run(() => this.pageChanged.emit(p));
});
instance.docViewer.on('documentLoaded', this._restoreViewerState);
@ -153,6 +160,37 @@ export class PdfViewerComponent implements OnInit, AfterViewInit, OnChanges {
}
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>{
type: 'actionButton',
img: '/assets/icons/general/add-redaction.svg',
@ -160,20 +198,28 @@ export class PdfViewerComponent implements OnInit, AfterViewInit, OnChanges {
'pdf-viewer.text-popup.actions.suggestion-redaction.label'
),
onClick: () => {
const selectedQuads = this.instance.docViewer.getSelectedTextQuads();
const text = this.instance.docViewer.getSelectedText();
const entry: ManualRedactionEntry = { positions: [] };
for (const key of Object.keys(selectedQuads)) {
for (const quad of selectedQuads[key]) {
entry.positions.push(this.toPosition(parseInt(key, 10), quad));
}
}
entry.value = text;
this.manualAnnotationRequested.emit(entry);
const mre = this._getManualRedactionEntry();
this.manualAnnotationRequested.emit(
new ManualRedactionEntryWrapper(mre, 'REDACTION')
);
}
});
}
private _getManualRedactionEntry(): ManualRedactionEntry {
const selectedQuads = this.instance.docViewer.getSelectedTextQuads();
const text = this.instance.docViewer.getSelectedText();
const entry: ManualRedactionEntry = { positions: [] };
for (const key of Object.keys(selectedQuads)) {
for (const quad of selectedQuads[key]) {
entry.positions.push(this.toPosition(parseInt(key, 10), quad));
}
}
entry.quads = selectedQuads;
entry.value = text;
return entry;
}
private toPosition(page: number, selectedQuad: any): Rectangle {
const pageHeight = this.instance.docViewer.getPageHeight(page);
const height = selectedQuad.y2 - selectedQuad.y4;

View File

@ -103,7 +103,7 @@ export class AppStateService {
}
getDictionaryLabel(type: string) {
return this._dictionaryData[type]['label'];
return this._dictionaryData[type].label;
}
get isActiveFileDocumentReviewer() {
@ -362,30 +362,42 @@ export class AppStateService {
tap((colors) => {
this._dictionaryData['request'] = {
hexColor: colors.requestAdd,
type: 'request'
type: 'request',
virtual: true
};
this._dictionaryData['ignore'] = {
hexColor: colors.notRedacted,
type: 'ignore'
type: 'ignore',
virtual: true
};
this._dictionaryData['default'] = {
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'] = {
hexColor: colors.requestRemove,
type: 'remove'
type: 'remove',
virtual: true
};
})
);
const result = await forkJoin([typeObs, colorsObs]).toPromise();
this._dictionaryData['hint'] = { hexColor: '#283241', type: 'hint' };
this._dictionaryData['redaction'] = { hexColor: '#283241', type: 'redaction' };
this._dictionaryData['hint'] = { hexColor: '#283241', type: 'hint', virtual: true };
this._dictionaryData['redaction'] = {
hexColor: '#283241',
type: 'redaction',
virtual: true
};
for (const key of Object.keys(this._dictionaryData)) {
this._dictionaryData[key]['label'] = humanize(key);
this._dictionaryData[key].label = humanize(key);
}
} else {
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": {
"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": {
"success": {
@ -47,7 +58,7 @@
},
"actions": {
"save": {
"label": "Save Manual Redaction"
"label": "Save"
}
},
"content": {
@ -92,6 +103,12 @@
"actions": {
"suggestion-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;
user?: string;
value?: string;
[key: string]: any;
}
export namespace ManualRedactionEntry {

View File

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