added annotation wrapper class to reduce code clutter

This commit is contained in:
Timo Bejan 2020-10-29 14:26:58 +02:00
parent edb0f253f6
commit 01a7dc8abe
9 changed files with 151 additions and 116 deletions

View File

@ -17,6 +17,7 @@ import { AssignOwnerDialogComponent } from './assign-owner-dialog/assign-owner-d
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';
import { AnnotationWrapper } from '../screens/file/model/annotation.wrapper';
const dialogConfig = {
width: '600px',
@ -100,20 +101,17 @@ export class DialogService {
public acceptSuggestionAnnotation(
$event: MouseEvent,
annotation: Annotations.Annotation,
annotation: AnnotationWrapper,
projectId: string,
fileId: string
): MatDialogRef<ConfirmationDialogComponent> {
$event.stopPropagation();
const parts = annotation.Id.split(':');
const annotationId = parts[parts.length - 1];
const ref = this._dialog.open(ConfirmationDialogComponent, dialogConfig);
ref.afterClosed().subscribe((result) => {
if (result) {
this._manualRedactionControllerService
.approveRequest(projectId, fileId, annotationId)
.approveRequest(projectId, fileId, annotation.uuid)
.subscribe(
() => {
this._notificationService.showToastNotification(
@ -143,15 +141,12 @@ export class DialogService {
public suggestRemoveAnnotation(
$event: MouseEvent,
annotation: Annotations.Annotation,
annotation: AnnotationWrapper,
projectId: string,
fileId: string
): MatDialogRef<ConfirmationDialogComponent> {
$event.stopPropagation();
const parts = annotation.Id.split(':');
const annotationId = parts[parts.length - 1];
const ref = this._dialog.open(ConfirmationDialogComponent, {
width: '400px',
maxWidth: '90vw'
@ -160,7 +155,7 @@ export class DialogService {
ref.afterClosed().subscribe((result) => {
if (result) {
this._manualRedactionControllerService
.undo(projectId, fileId, annotationId)
.undo(projectId, fileId, annotation.uuid)
.subscribe(
(ok) => {
this._notificationService.showToastNotification(

View File

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

View File

@ -229,43 +229,41 @@
<div
*ngFor="let annotation of displayedAnnotations[page].annotations"
class="annotation"
attr.annotation-id="{{ annotation.Id }}"
attr.annotation-id="{{ annotation.id }}"
attr.annotation-page="{{ page }}"
[ngClass]="{ active: selectedAnnotation === annotation }"
(click)="selectAnnotation(annotation)"
[ngClass]="{ active: selectedAnnotation?.id === annotation.id }"
(click)="selectAnnotation(annotation.annotation)"
>
<redaction-annotation-icon
[typeValue]="
appStateService.getDictionaryTypeValue(
getDictionary(annotation)
)
appStateService.getDictionaryTypeValueForAnnotation(annotation)
"
></redaction-annotation-icon>
<div class="flex-1">
<div>
<strong>{{ getType(annotation) | translate }}</strong>
<strong>{{ annotation.superType | humanize }}</strong>
</div>
<div>
<div *ngIf="annotation.dictionary">
<strong><span translate="dictionary"></span>: </strong
>{{ getDictionary(annotation) }}
>{{ annotation.dictionary | humanize }}
</div>
<div *ngIf="annotation.getContents()">
<div *ngIf="annotation.content">
<strong><span translate="content"></span>: </strong
>{{ annotation.getContents() }}
>{{ annotation.content }}
</div>
<div *ngFor="let comment of annotation['comments']">
<div *ngFor="let comment of annotation.comments">
<strong><span translate="comment"></span>:</strong>
{{ comment.value }}
{{ comment }}
</div>
</div>
<div class="page-number">
{{ annotation.getPageNumber() }}
{{ annotation.pageNumber }}
</div>
<div
class="annotation-actions"
*ngIf="isManuallyAddedAnnotation(annotation)"
*ngIf="annotation.superType === 'request'"
>
<button
mat-icon-button

View File

@ -24,6 +24,8 @@ 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';
import { hexToRgb } from '../../../utils/functions';
import { AnnotationWrapper } from '../model/annotation.wrapper';
@Component({
selector: 'redaction-file-preview-screen',
@ -43,9 +45,9 @@ export class FilePreviewScreenComponent implements OnInit {
public annotatedFileData: Blob;
public redactedFileData: Blob;
public fileId: string;
public annotations: Annotations.Annotation[] = [];
public displayedAnnotations: { [key: number]: { annotations: Annotations.Annotation[] } } = {};
public selectedAnnotation: Annotations.Annotation;
public annotations: AnnotationWrapper[] = [];
public displayedAnnotations: { [key: number]: { annotations: AnnotationWrapper[] } } = {};
public selectedAnnotation: AnnotationWrapper;
public filters: AnnotationFilters;
public expandedFilters: AnnotationFilters = { hint: false };
public pagesPanelActive = true;
@ -162,7 +164,7 @@ export class FilePreviewScreenComponent implements OnInit {
}
public handleAnnotationSelected(annotation: Annotations.Annotation) {
this.selectedAnnotation = annotation;
this.selectedAnnotation = new AnnotationWrapper(annotation);
this.scrollToSelectedAnnotation();
this._changeDetectorRef.detectChanges();
}
@ -177,7 +179,7 @@ export class FilePreviewScreenComponent implements OnInit {
return;
}
const elements: any[] = this._annotationsElement.nativeElement.querySelectorAll(
`div[annotation-id="${this.selectedAnnotation.Id}"].active`
`div[annotation-id="${this.selectedAnnotation.id}"].active`
);
this._scrollToFirstElement(elements);
}
@ -192,21 +194,18 @@ export class FilePreviewScreenComponent implements OnInit {
this._dialogRef = this._dialogService.openManualRedactionDialog(
$event,
(response: any) => {
const request: ManualRedactionEntry = response.request;
const manualRedactionEntry: ManualRedactionEntry = response.request;
const annotManager = this.activeViewer.annotManager;
const originalQuads = request.quads;
const originalQuads = manualRedactionEntry.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.StrokeColor = this._getColor(manualRedactionEntry);
highlight.setContents(manualRedactionEntry.reason);
highlight.Quads = originalQuads[key];
highlight.Id = this._computeId($event.type, request);
highlight.Id = this._computeId($event.type, manualRedactionEntry);
annotManager.addAnnotation(highlight, true);
annotManager.redrawAnnotation(highlight);
}
@ -233,7 +232,7 @@ export class FilePreviewScreenComponent implements OnInit {
}
private _scrollAnnotations() {
if (this.selectedAnnotation?.getPageNumber() === this.activeViewerPage) {
if (this.selectedAnnotation?.pageNumber === this.activeViewerPage) {
return;
}
this._scrollAnnotationsToPage(this.activeViewerPage);
@ -260,19 +259,7 @@ export class FilePreviewScreenComponent implements OnInit {
}
}
public getType(annotation: Annotations.Annotation): string {
return AnnotationUtils.getType(annotation);
}
public getDictionary(annotation: Annotations.Annotation): string {
const type = AnnotationUtils.getType(annotation);
if (type === 'ignore') {
return 'ignore';
}
return AnnotationUtils.getDictionary(annotation);
}
public acceptSuggestionAnnotation($event: MouseEvent, annotation: Annotations.Annotation) {
public acceptSuggestionAnnotation($event: MouseEvent, annotation: AnnotationWrapper) {
this.ngZone.run(() => {
this._dialogRef = this._dialogService.acceptSuggestionAnnotation(
$event,
@ -283,7 +270,7 @@ export class FilePreviewScreenComponent implements OnInit {
});
}
public suggestRemoveAnnotation($event: MouseEvent, annotation: Annotations.Annotation) {
public suggestRemoveAnnotation($event: MouseEvent, annotation: AnnotationWrapper) {
this.ngZone.run(() => {
this._dialogRef = this._dialogService.suggestRemoveAnnotation(
$event,
@ -337,10 +324,6 @@ export class FilePreviewScreenComponent implements OnInit {
this.expandedFilters[key] = value;
}
public isManuallyAddedAnnotation(annotation: Annotations.Annotation) {
return annotation.Id.indexOf('request:') >= 0;
}
@HostListener('window:keyup', ['$event'])
handleKeyEvent($event: KeyboardEvent) {
const keyArray = ['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown'];
@ -365,27 +348,31 @@ export class FilePreviewScreenComponent implements OnInit {
private _navigateAnnotations($event: KeyboardEvent) {
if (
!this.selectedAnnotation ||
this.activeViewerPage !== this.selectedAnnotation.getPageNumber()
this.activeViewerPage !== this.selectedAnnotation.pageNumber
) {
const pageIdx = this.displayedPages.indexOf(this.activeViewerPage);
if (pageIdx !== -1) {
// Displayed page has annotations
this.selectAnnotation(
this.displayedAnnotations[this.activeViewerPage].annotations[0]
this.displayedAnnotations[this.activeViewerPage].annotations[0].annotation
);
} else {
// Displayed page doesn't have annotations
if ($event.key === 'ArrowDown') {
const nextPage = this._nextPageWithAnnotations();
this.selectAnnotation(this.displayedAnnotations[nextPage].annotations[0]);
this.selectAnnotation(
this.displayedAnnotations[nextPage].annotations[0].annotation
);
} else {
const prevPage = this._prevPageWithAnnotations();
const prevPageAnnotations = this.displayedAnnotations[prevPage].annotations;
this.selectAnnotation(prevPageAnnotations[prevPageAnnotations.length - 1]);
this.selectAnnotation(
prevPageAnnotations[prevPageAnnotations.length - 1].annotation
);
}
}
} else {
const page = this.selectedAnnotation.getPageNumber();
const page = this.selectedAnnotation.pageNumber;
const pageIdx = this.displayedPages.indexOf(page);
const annotationsOnPage = this.displayedAnnotations[page].annotations;
const idx = annotationsOnPage.indexOf(this.selectedAnnotation);
@ -393,24 +380,26 @@ export class FilePreviewScreenComponent implements OnInit {
if ($event.key === 'ArrowDown') {
if (idx + 1 !== annotationsOnPage.length) {
// If not last item in page
this.selectAnnotation(annotationsOnPage[idx + 1]);
this.selectAnnotation(annotationsOnPage[idx + 1].annotation);
} else if (pageIdx + 1 < this.displayedPages.length) {
// If not last page
const nextPageAnnotations = this.displayedAnnotations[
this.displayedPages[pageIdx + 1]
].annotations;
this.selectAnnotation(nextPageAnnotations[0]);
this.selectAnnotation(nextPageAnnotations[0].annotation);
}
} else {
if (idx !== 0) {
// If not first item in page
this.selectAnnotation(annotationsOnPage[idx - 1]);
this.selectAnnotation(annotationsOnPage[idx - 1].annotation);
} else if (pageIdx) {
// If not first page
const prevPageAnnotations = this.displayedAnnotations[
this.displayedPages[pageIdx - 1]
].annotations;
this.selectAnnotation(prevPageAnnotations[prevPageAnnotations.length - 1]);
this.selectAnnotation(
prevPageAnnotations[prevPageAnnotations.length - 1].annotation
);
}
}
}
@ -483,15 +472,9 @@ export class FilePreviewScreenComponent implements OnInit {
}
handleAnnotationsAdded(annotations: Annotations.Annotation[]) {
annotations.forEach((a) => {
a['comments'] = a['Mi']
? a['Mi'].map((m) => {
return { value: m.eC };
})
: [];
});
AnnotationUtils.addAnnotations(this.annotations, annotations);
// replacing array causes UI flicker
this.annotations.splice(0, this.annotations.length);
this.annotations.push(...AnnotationUtils.filterAndConvertAnnotations(annotations));
this.filters = this._filtersService.getFilters(
this.appStateService.dictionaryData,
this.annotations
@ -501,6 +484,15 @@ export class FilePreviewScreenComponent implements OnInit {
}
private _computeId(type: 'HINT' | 'REDACTION', request: ManualRedactionEntry) {
return 'request:' + type.toLowerCase() + ':' + new Date().getTime();
const prefix = this.appStateService.isActiveProjectOwner ? type.toLowerCase() : 'request';
return prefix + ':' + request.type + ':' + new Date().getTime();
}
private _getColor(manualRedactionEntry: ManualRedactionEntry) {
let color = this.appStateService.isActiveProjectOwner
? this.appStateService.getDictionaryColor('request')
: this.appStateService.getDictionaryColor(manualRedactionEntry.type);
const rgbColor = hexToRgb(color);
return new this.activeViewer.Annotations.Color(rgbColor.r, rgbColor.g, rgbColor.b);
}
}

View File

@ -0,0 +1,43 @@
import { Annotations } from '@pdftron/webviewer';
export class AnnotationWrapper {
superType: 'request' | 'redaction' | 'hint' | 'ignore';
dictionary: string;
color: string;
comments: string[] = [];
uuid: string;
constructor(public annotation: Annotations.Annotation) {
this.comments = annotation['Mi'] ? annotation['Mi'].map((m) => m.eC) : [];
const parts = annotation.Id.split(':');
// first part is always the superType
this.superType = parts[0].toLowerCase() as any;
if (this.superType === 'redaction' || this.superType === 'hint') {
this.dictionary = parts[1];
}
if (this.superType === 'request') {
this.dictionary = parts[2] !== 'only_here' ? parts[2] : undefined;
}
this.uuid = parts[parts.length - 1];
}
get x() {
return this.annotation.getX();
}
get y() {
return this.annotation.getY();
}
get id() {
return this.annotation.Id;
}
get content() {
return this.annotation.getContents();
}
get pageNumber() {
return this.annotation.PageNumber;
}
}

View File

@ -1,8 +1,7 @@
import { Injectable } from '@angular/core';
import { AnnotationFilters } from '../../../utils/types';
import { TypeValue } from '@redaction/red-ui-http';
import { Annotations } from '@pdftron/webviewer';
import { AnnotationUtils } from '../../../utils/annotation-utils';
import { AnnotationWrapper } from '../model/annotation.wrapper';
@Injectable({
providedIn: 'root'
@ -19,12 +18,12 @@ export class FiltersService {
public getFilters(
dictionaryData: { [key: string]: TypeValue },
annotations?: Annotations.Annotation[]
annotations?: AnnotationWrapper[]
): AnnotationFilters {
const availableAnnotationTypes: Set<string> = new Set<string>();
annotations?.forEach((a) => {
availableAnnotationTypes.add(AnnotationUtils.getType(a));
availableAnnotationTypes.add(AnnotationUtils.getDictionary(a));
availableAnnotationTypes.add(a.superType);
availableAnnotationTypes.add(a.dictionary);
});
const filtersCopy = JSON.parse(JSON.stringify(this._filters));
for (const key of Object.keys(dictionaryData)) {
@ -39,7 +38,7 @@ export class FiltersService {
}
}
for (let key of Object.keys(filtersCopy)) {
for (const key of Object.keys(filtersCopy)) {
if (!availableAnnotationTypes.has(key)) {
delete filtersCopy[key];
}

View File

@ -17,6 +17,7 @@ import { forkJoin, interval } from 'rxjs';
import { tap } from 'rxjs/operators';
import { download } from '../utils/file-download-utils';
import { humanize } from '../utils/functions';
import { AnnotationWrapper } from '../screens/file/model/annotation.wrapper';
export interface AppState {
projects: ProjectWrapper[];
@ -412,4 +413,13 @@ export class AppStateService {
isManagerAndOwner(user: UserWrapper, project: Project) {
return user.isManager && project.ownerId === user.id;
}
getDictionaryTypeValueForAnnotation(annotation: AnnotationWrapper) {
if (annotation.superType === 'request' || annotation.superType === 'ignore') {
return this._dictionaryData[annotation.superType];
}
if (annotation.superType === 'redaction' || annotation.superType === 'hint') {
return this._dictionaryData[annotation.dictionary];
}
}
}

View File

@ -1,19 +1,20 @@
import { Annotations } from '@pdftron/webviewer';
import { AnnotationFilters } from './types';
import { AnnotationWrapper } from '../screens/file/model/annotation.wrapper';
export class AnnotationUtils {
public static sortAnnotations(annotations: Annotations.Annotation[]): Annotations.Annotation[] {
public static sortAnnotations(annotations: AnnotationWrapper[]): AnnotationWrapper[] {
return annotations.sort((ann1, ann2) => {
if (ann1.getPageNumber() === ann2.getPageNumber()) {
if (ann1.getY() === ann2.getY()) {
if (ann1.getX() === ann2.getY()) {
if (ann1.pageNumber === ann2.pageNumber) {
if (ann1.x === ann2.y) {
if (ann1.x === ann2.y) {
return 0;
}
return ann1.getX() < ann2.getX() ? -1 : 1;
return ann1.x < ann2.x ? -1 : 1;
}
return ann1.getY() < ann2.getY() ? -1 : 1;
return ann1.y < ann2.y ? -1 : 1;
}
return ann1.getPageNumber() < ann2.getPageNumber() ? -1 : 1;
return ann1.pageNumber < ann2.pageNumber ? -1 : 1;
});
}
@ -47,15 +48,15 @@ export class AnnotationUtils {
}
public static parseAnnotations(
annotations: Annotations.Annotation[],
annotations: AnnotationWrapper[],
filters: AnnotationFilters
): { [key: number]: { annotations: Annotations.Annotation[] } } {
): { [key: number]: { annotations: AnnotationWrapper[] } } {
const obj = {};
for (const ann of annotations) {
const pageNumber = ann.getPageNumber();
const type = this.getType(ann);
const dictionary = this.getDictionary(ann);
for (const annotation of annotations) {
const pageNumber = annotation.pageNumber;
const type = annotation.superType;
const dictionary = annotation.dictionary;
if (this.hasActiveFilters(filters)) {
if (!this.hasSubsections(filters[type]) && !filters[type]) {
@ -76,7 +77,7 @@ export class AnnotationUtils {
ignore: 0
};
}
obj[pageNumber].annotations.push(ann);
obj[pageNumber].annotations.push(annotation);
obj[pageNumber][type]++;
}
@ -87,25 +88,13 @@ export class AnnotationUtils {
return obj;
}
public static addAnnotations(
initialAnnotations: Annotations.Annotation[],
addedAnnotations: Annotations.Annotation[]
) {
initialAnnotations.splice(0, initialAnnotations.length);
for (const annotation of addedAnnotations) {
public static filterAndConvertAnnotations(annotations: Annotations.Annotation[]) {
const convertedAnnotations: AnnotationWrapper[] = [];
for (const annotation of annotations) {
if (annotation.Id.indexOf(':') > 0) {
initialAnnotations.push(annotation);
convertedAnnotations.push(new AnnotationWrapper(annotation));
}
}
}
public static getType(annotation: Annotations.Annotation): string {
const parts = annotation.Id.split(':');
return parts.length >= 1 ? parts[0] : 'n/a';
}
public static getDictionary(annotation: Annotations.Annotation): string {
const parts = annotation.Id.split(':');
return parts.length >= 2 ? parts[1] : 'n/a';
return convertedAnnotations;
}
}

View File

@ -12,3 +12,14 @@ export function humanize(str: string) {
}
return frags.join(' ');
}
export function hexToRgb(hex) {
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
return result
? {
r: parseInt(result[1], 16),
g: parseInt(result[2], 16),
b: parseInt(result[3], 16)
}
: null;
}