work in progress direct-draw annotations
This commit is contained in:
parent
64cbfbeca4
commit
8236467579
@ -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,
|
||||||
|
|||||||
@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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>
|
||||||
|
|||||||
@ -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;
|
||||||
|
|||||||
@ -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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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'
|
||||||
|
) {}
|
||||||
|
}
|
||||||
@ -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 {
|
||||||
|
|||||||
@ -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;
|
||||||
|
|||||||
11
apps/red-ui/src/app/utils/humanize.pipe.ts
Normal file
11
apps/red-ui/src/app/utils/humanize.pipe.ts
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
4
apps/red-ui/src/assets/icons/general/add-hint.svg
Normal file
4
apps/red-ui/src/assets/icons/general/add-hint.svg
Normal 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 |
11
apps/red-ui/src/assets/icons/general/search-viewer.svg
Normal file
11
apps/red-ui/src/assets/icons/general/search-viewer.svg
Normal 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 |
@ -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 {
|
||||||
|
|||||||
@ -30,4 +30,6 @@ export interface TypeValue {
|
|||||||
* The nonnull entry type.
|
* The nonnull entry type.
|
||||||
*/
|
*/
|
||||||
type?: string;
|
type?: string;
|
||||||
|
|
||||||
|
[key: string]: any;
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user