fixed lint

This commit is contained in:
Timo Bejan 2020-10-30 17:53:14 +02:00
parent 59f5a884b9
commit e07599cf27
11 changed files with 197 additions and 220 deletions

View File

@ -17,6 +17,12 @@
"changeOrigin": true,
"logLevel": "debug"
},
"/redactionLog": {
"target": "https://timo-redaction-dev.iqser.cloud/",
"secure": false,
"changeOrigin": true,
"logLevel": "debug"
},
"/user": {
"target": "https://timo-redaction-dev.iqser.cloud/",
"secure": false,

View File

@ -74,14 +74,13 @@
<div class="flex red-content-inner">
<div class="left-container">
<redaction-pdf-viewer
*ngIf="annotatedFileData && redactedFileData"
[fileData]="redactedView ? redactedFileData : annotatedFileData"
*ngIf="fileData"
[fileData]="redactedView ? fileData.redactedFileData : fileData.annotatedFileData"
[fileStatus]="appStateService.activeFile"
(keyUp)="handleKeyEvent($event)"
(pageChanged)="viewerPageChanged($event)"
(manualAnnotationRequested)="openManualRedactionDialog($event)"
(annotationSelected)="handleAnnotationSelected($event)"
(annotationsAdded)="handleAnnotationsAdded($event)"
(viewerReady)="viewerReady($event)"
></redaction-pdf-viewer>
</div>
@ -121,65 +120,58 @@
></div>
</div>
</div>
<div *ngFor="let key of filterKeys()">
<div
class="mat-menu-item flex"
(click)="setExpanded(key, !expandedFilters[key], $event)"
>
<div class="arrow-wrapper" *ngIf="hasSubsections(filters[key])">
<mat-icon
*ngIf="expandedFilters[key]"
svgIcon="red:arrow-down"
color="accent"
>
</mat-icon>
<mat-icon
*ngIf="!expandedFilters[key]"
color="accent"
svgIcon="red:arrow-right"
>
</mat-icon>
</div>
<mat-checkbox
[checked]="isChecked(key)"
[indeterminate]="isIndeterminate(key)"
(click)="$event.stopPropagation()"
(change)="
setAllFilters(
filters[key],
$event.checked,
hasSubsections(filters[key]) ? null : key
)
"
color="primary"
>
<redaction-annotation-icon
[typeValue]="appStateService.getDictionaryTypeValue(key)"
></redaction-annotation-icon>
<!-- <div *ngFor="let key of filterKeys()">-->
<!-- <div-->
<!-- class="mat-menu-item flex"-->
<!-- (click)="setExpanded(key, !expandedFilters[key], $event)"-->
<!-- >-->
<!-- <div class="arrow-wrapper" *ngIf="hasSubsections(filters[key])">-->
<!-- <mat-icon-->
<!-- *ngIf="expandedFilters[key]"-->
<!-- svgIcon="red:arrow-down"-->
<!-- color="accent"-->
<!-- >-->
<!-- </mat-icon>-->
<!-- <mat-icon-->
<!-- *ngIf="!expandedFilters[key]"-->
<!-- color="accent"-->
<!-- svgIcon="red:arrow-right"-->
<!-- >-->
<!-- </mat-icon>-->
<!-- </div>-->
<!-- <mat-checkbox-->
<!-- [checked]="isChecked(key)"-->
<!-- [indeterminate]="isIndeterminate(key)"-->
<!-- (click)="filterClicked($event,key)"-->
<!-- color="primary"-->
<!-- >-->
<!-- <redaction-annotation-icon-->
<!-- [typeValue]="appStateService.getDictionaryTypeValue(key)"-->
<!-- ></redaction-annotation-icon>-->
{{ 'file-preview.filter-menu.' + key + '.label' | translate }}
</mat-checkbox>
</div>
<div *ngIf="hasSubsections(filters[key]) && expandedFilters[key]">
<div
*ngFor="let subkey of filterKeys(key)"
class="padding-left mat-menu-item"
(click)="$event.stopPropagation()"
>
<mat-checkbox
[(ngModel)]="filters[key][subkey]"
color="primary"
>
<redaction-annotation-icon
[typeValue]="
appStateService.getDictionaryTypeValue(subkey)
"
></redaction-annotation-icon>
{{ appStateService.getDictionaryLabel(subkey) }}
</mat-checkbox>
</div>
</div>
</div>
<!-- {{ 'file-preview.filter-menu.' + key + '.label' | translate }}-->
<!-- </mat-checkbox>-->
<!-- </div>-->
<!-- <div *ngIf="hasSubsections(filters[key]) && expandedFilters[key]">-->
<!-- <div-->
<!-- *ngFor="let subkey of filterKeys(key)"-->
<!-- class="padding-left mat-menu-item"-->
<!-- >-->
<!-- <mat-checkbox-->
<!-- [checked]="filters[key][subkey]"-->
<!-- (click)="filterClicked($event,key,subkey)"-->
<!-- color="primary"-->
<!-- >-->
<!-- <redaction-annotation-icon-->
<!-- [typeValue]="-->
<!-- appStateService.getDictionaryTypeValue(subkey)-->
<!-- "-->
<!-- ></redaction-annotation-icon>-->
<!-- {{ appStateService.getDictionaryLabel(subkey) }}-->
<!-- </mat-checkbox>-->
<!-- </div>-->
<!-- </div>-->
<!-- </div>-->
</mat-menu>
</div>
</div>

View File

@ -16,7 +16,6 @@ import { AnnotationUtils } from '../../../utils/annotation-utils';
import { UserService } from '../../../user/user.service';
import { debounce } from '../../../utils/debounce';
import scrollIntoView from 'scroll-into-view-if-needed';
import { AnnotationFilters } from '../../../utils/types';
import { FiltersService } from '../service/filters.service';
import { FileDownloadService } from '../service/file-download.service';
import { saveAs } from 'file-saver';
@ -28,6 +27,8 @@ import { hexToRgb } from '../../../utils/functions';
import { AnnotationWrapper } from '../model/annotation.wrapper';
import { ManualAnnotationService } from '../service/manual-annotation.service';
import { ManualAnnotationResponse } from '../model/manual-annotation-response';
import { FileDataModel } from '../model/file-data.model';
import { AnnotationFilter } from '../../../utils/types';
@Component({
selector: 'redaction-file-preview-screen',
@ -44,14 +45,12 @@ export class FilePreviewScreenComponent implements OnInit {
@ViewChild('annotations') private _annotationsElement: ElementRef;
@ViewChild('quickNavigation') private _quickNavigationElement: ElementRef;
public annotatedFileData: Blob;
public redactedFileData: Blob;
public fileData: FileDataModel;
public fileId: string;
public annotations: AnnotationWrapper[] = [];
public displayedAnnotations: { [key: number]: { annotations: AnnotationWrapper[] } } = {};
public selectedAnnotation: AnnotationWrapper;
public filters: AnnotationFilters;
public expandedFilters: AnnotationFilters = { hint: false };
public filters: AnnotationFilter[];
public pagesPanelActive = true;
public viewReady = false;
@ -97,7 +96,7 @@ export class FilePreviewScreenComponent implements OnInit {
public ngOnInit(): void {
this.filters = this._filtersService.getFilters(this.appStateService.dictionaryData);
this._reloadFiles();
this._loadFileData();
this.appStateService.fileStatusChanged.subscribe((fileStatus) => {
if (fileStatus.fileId === this.fileId) {
// no more automatic reloads
@ -106,17 +105,20 @@ export class FilePreviewScreenComponent implements OnInit {
});
}
private _reloadFiles() {
this._fileDownloadService
.loadFile('ANNOTATED', this.fileId, (data) => {
this.annotatedFileData = data;
})
.subscribe(() => {});
this._fileDownloadService
.loadFile('REDACTED', this.fileId, (data) => {
this.redactedFileData = data;
})
.subscribe(() => {});
private _loadFileData() {
this._fileDownloadService.loadFileData(this.fileId).subscribe((fileDataModel) => {
this.fileData = fileDataModel;
this.annotations = fileDataModel.redactionLog.redactionLogEntry.map(
(rde) => new AnnotationWrapper(rde, null)
);
this.filters = this._filtersService.getFilters(
this.appStateService.dictionaryData,
this.annotations
);
this.applyFilters();
this._changeDetectorRef.detectChanges();
console.log(this.annotations);
});
}
public openFileDetailsDialog($event: MouseEvent) {
@ -166,8 +168,8 @@ export class FilePreviewScreenComponent implements OnInit {
return Object.keys(this.displayedAnnotations).map((key) => Number(key));
}
public handleAnnotationSelected(annotation: AnnotationWrapper) {
this.selectedAnnotation = annotation;
public handleAnnotationSelected(annotationId: string) {
this.selectedAnnotation = this.annotations.find((a) => a.id === annotationId);
this.scrollToSelectedAnnotation();
this._changeDetectorRef.detectChanges();
}
@ -272,7 +274,8 @@ export class FilePreviewScreenComponent implements OnInit {
public rejectSuggestion($event: MouseEvent, annotation: AnnotationWrapper) {
this.ngZone.run(() => {
this._dialogRef = this._dialogService.rejectSuggestion($event, annotation, () => {
this.activeViewer.annotManager.deleteAnnotation(annotation.annotation, false, true);
// TODO DELETE ANNOTATIOn
//this.activeViewer.annotManager.deleteAnnotation(annotation, false, true);
});
});
}
@ -306,16 +309,17 @@ export class FilePreviewScreenComponent implements OnInit {
}
public get hasActiveFilters(): boolean {
return AnnotationUtils.hasActiveFilters(this.filters);
// return AnnotationUtils.hasActiveFilters(this.filters);
return true;
}
public hasSubsections(filter: AnnotationFilters | boolean) {
return AnnotationUtils.hasSubsections(filter);
public hasSubsections(filter: AnnotationFilter[]) {
// return AnnotationUtils.hasSubsections(filter);
}
public setExpanded(key: string, value: boolean, $event: MouseEvent) {
$event.stopPropagation();
this.expandedFilters[key] = value;
//this.expandedFilters[key] = value;
this._changeDetectorRef.detectChanges();
}
@ -460,18 +464,6 @@ export class FilePreviewScreenComponent implements OnInit {
this.viewReady = true;
}
handleAnnotationsAdded(annotations: AnnotationWrapper[]) {
// replacing array causes UI flicker
this.annotations.splice(0, this.annotations.length);
this.annotations.push(...annotations);
this.filters = this._filtersService.getFilters(
this.appStateService.dictionaryData,
this.annotations
);
this.applyFilters();
this._changeDetectorRef.detectChanges();
}
private _computeId(response: ManualAnnotationResponse) {
// if owner or not set the request prefix in the id
const prefix = this.appStateService.isActiveProjectOwner ? '' : 'request:add:';
@ -498,4 +490,16 @@ export class FilePreviewScreenComponent implements OnInit {
const rgbColor = hexToRgb(color);
return new this.activeViewer.Annotations.Color(rgbColor.r, rgbColor.g, rgbColor.b);
}
filterClicked($event: MouseEvent, key: string, subkey?: string) {
$event.preventDefault();
$event.stopPropagation();
if (subkey) {
this.filters[key][subkey] = !this.filters[key][subkey];
} else {
//this.setAllFilters(this.filters[key],)
this.filters[key] = !this.filters[key];
}
return false;
}
}

View File

@ -1,5 +1,9 @@
import { Annotations } from '@pdftron/webviewer';
import { ManualRedactionEntry } from '@redaction/red-ui-http';
import {
ManualRedactionEntry,
ManualRedactions,
Point,
RedactionLogEntry
} from '@redaction/red-ui-http';
export class AnnotationWrapper {
superType: 'request' | 'redaction' | 'hint' | 'ignore';
@ -8,25 +12,22 @@ export class AnnotationWrapper {
comments: string[] = [];
uuid: string;
manualRedactionEntry: ManualRedactionEntry;
firstTopLeftPoint: Point;
id: string;
content: string;
pageNumber;
constructor(
public annotation: Annotations.Annotation,
manualRedactionEntries?: ManualRedactionEntry[]
) {
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];
this.manualRedactionEntry = manualRedactionEntries
? manualRedactionEntries.find((e) => e.id === this.uuid)
: undefined;
constructor(public redactionLogEntry: RedactionLogEntry, manualRedactions?: ManualRedactions) {
this.superType = redactionLogEntry.redacted
? 'redaction'
: redactionLogEntry.hint
? 'hint'
: 'ignore';
this.dictionary = redactionLogEntry.type;
this.firstTopLeftPoint = redactionLogEntry.positions[0]?.topLeft;
this.pageNumber = redactionLogEntry.positions[0]?.page;
this.id = redactionLogEntry.id;
this.content = redactionLogEntry.reason;
}
get manualRedactionOwner() {
@ -34,22 +35,16 @@ export class AnnotationWrapper {
}
get x() {
return this.annotation.getX();
return this.firstTopLeftPoint.x;
}
get y() {
return this.annotation.getY();
return this.firstTopLeftPoint.y;
}
get id() {
return this.annotation.Id;
}
get content() {
return this.annotation.getContents();
}
get pageNumber() {
return this.annotation.PageNumber;
}
// private String createAnnotationContent(Entity entity) {
//
// return "\nRule " + entity.getMatchedRule() + " matched\n\n" + entity.getRedactionReason() + "\n\nLegal basis:" + entity
// .getLegalBasis() + "\n\nIn section: \"" + entity.getHeadline() + "\"";
// }
}

View File

@ -0,0 +1,9 @@
import { RedactionLog } from '@redaction/red-ui-http';
export class FileDataModel {
constructor(
public annotatedFileData: Blob,
public redactedFileData: Blob,
public redactionLog: RedactionLog
) {}
}

View File

@ -46,14 +46,12 @@ export interface ViewerState {
})
export class PdfViewerComponent implements OnInit, AfterViewInit, OnChanges {
private _viewerState: ViewerState = null; // no initial state
private _annotationEventDebouncer = new Subject<Annotations.Annotation[]>();
@Input() fileData: Blob;
@Input() fileStatus: FileStatus;
@Output() fileReady = new EventEmitter();
@Output() annotationsAdded = new EventEmitter<AnnotationWrapper[]>();
@Output() annotationSelected = new EventEmitter<AnnotationWrapper>();
@Output() annotationSelected = new EventEmitter<string>();
@Output() manualAnnotationRequested = new EventEmitter<ManualRedactionEntryWrapper>();
@Output() pageChanged = new EventEmitter<number>();
@Output() keyUp = new EventEmitter<KeyboardEvent>();
@ -64,7 +62,6 @@ export class PdfViewerComponent implements OnInit, AfterViewInit, OnChanges {
@ViewChild('viewer', { static: true }) viewer: ElementRef;
instance: WebViewerInstance;
private _manualAnnotations: ManualRedactions;
constructor(
private readonly _appStateService: AppStateService,
@ -77,26 +74,6 @@ export class PdfViewerComponent implements OnInit, AfterViewInit, OnChanges {
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(
AnnotationUtils.filterAndConvertAnnotations(
this.instance.annotManager.getAnnotationsList(),
this._manualAnnotationService.manualEntries
)
);
// nasty double-emit fix, the annotationList is not updated when the event is fired
setTimeout(
() =>
this.annotationsAdded.emit(
AnnotationUtils.filterAndConvertAnnotations(
this.instance.annotManager.getAnnotationsList(),
this._manualAnnotationService.manualEntries
)
),
200
);
});
this._manualAnnotationService.loadManualAnnotationsForActiveFile().subscribe(() => {});
}
@ -124,15 +101,11 @@ export class PdfViewerComponent implements OnInit, AfterViewInit, OnChanges {
this._disableElements();
this._configureTextPopup();
this._configureHeader();
instance.annotManager.on('annotationChanged', (annotations, action) => {
this._annotationEventDebouncer.next(annotations);
});
instance.annotManager.on('annotationSelected', (annotationList, action) => {
if (action === 'deselected') {
this.annotationSelected.emit(null);
} else {
this.annotationSelected.emit(new AnnotationWrapper(annotationList[0]));
this.annotationSelected.emit(annotationList[0].Id);
}
});
@ -273,10 +246,10 @@ export class PdfViewerComponent implements OnInit, AfterViewInit, OnChanges {
public selectAnnotation(annotation: AnnotationWrapper) {
this.instance.annotManager.deselectAllAnnotations();
if (annotation?.annotation) {
this.instance.annotManager.selectAnnotation(annotation.annotation);
this.navigateToPage(annotation.pageNumber);
}
const annotationFromViewer = this.instance.annotManager.getAnnotationById(annotation.id);
console.log(annotationFromViewer);
this.instance.annotManager.selectAnnotation(annotationFromViewer);
this.navigateToPage(annotation.pageNumber);
}
public navigateToPage(pageNumber: number) {

View File

@ -1,14 +1,28 @@
import { Injectable } from '@angular/core';
import { Observable, of } from 'rxjs';
import { tap } from 'rxjs/operators';
import { FileUploadControllerService } from '@redaction/red-ui-http';
import { forkJoin, Observable, of } from 'rxjs';
import { map, tap } from 'rxjs/operators';
import { FileUploadControllerService, RedactionLogControllerService } from '@redaction/red-ui-http';
import { FileType } from '../model/file-type';
import { FileDataModel } from '../model/file-data.model';
@Injectable({
providedIn: 'root'
})
export class FileDownloadService {
constructor(private readonly _fileUploadControllerService: FileUploadControllerService) {}
constructor(
private readonly _fileUploadControllerService: FileUploadControllerService,
private readonly _redactionLogControllerService: RedactionLogControllerService
) {}
public loadFileData(fileId: string): Observable<FileDataModel> {
const annotatedObs = this.loadFile('ANNOTATED', fileId);
const redactedObs = this.loadFile('REDACTED', fileId);
const reactionLogObs = this._redactionLogControllerService.getRedactionLog(fileId);
return forkJoin([annotatedObs, redactedObs, reactionLogObs]).pipe(
map((data) => new FileDataModel(...data))
);
}
loadFile(
fileType: FileType | string,

View File

@ -1,5 +1,5 @@
import { Injectable } from '@angular/core';
import { AnnotationFilters } from '../../../utils/types';
import { AnnotationFilter } from '../../../utils/types';
import { TypeValue } from '@redaction/red-ui-http';
import { AnnotationWrapper } from '../model/annotation.wrapper';
@ -9,41 +9,33 @@ import { AnnotationWrapper } from '../model/annotation.wrapper';
export class FiltersService {
constructor() {}
private _filters: AnnotationFilters = {
hint: {},
redaction: {},
request: false,
ignore: false
};
public getFilters(
dictionaryData: { [key: string]: TypeValue },
annotations?: AnnotationWrapper[]
): AnnotationFilters {
): AnnotationFilter[] {
const availableAnnotationTypes: Set<string> = new Set<string>();
annotations?.forEach((a) => {
availableAnnotationTypes.add(a.superType);
availableAnnotationTypes.add(a.dictionary);
});
const filtersCopy = JSON.parse(JSON.stringify(this._filters));
const filters: AnnotationFilter[] = [];
for (const key of Object.keys(dictionaryData)) {
if (availableAnnotationTypes.has(key)) {
const typeValue = dictionaryData[key];
if (typeValue.hint === true) {
filtersCopy.hint[key] = typeValue.defaultFilter;
}
if (typeValue.hint === false) {
filtersCopy.redaction[key] = typeValue.defaultFilter;
}
const filter: AnnotationFilter = this._addOrGetGroup(
filters,
typeValue.hint ? 'hint' : 'redaction'
);
filter.filters.push({
key: key
});
}
}
for (const key of Object.keys(filtersCopy)) {
if (!availableAnnotationTypes.has(key)) {
delete filtersCopy[key];
}
}
return filters;
}
return filtersCopy;
private _addOrGetGroup(filters: AnnotationFilter[], name: string) {
return { key: name, filters: [] };
}
}

View File

@ -1,56 +1,53 @@
import { Annotations } from '@pdftron/webviewer';
import { AnnotationFilters } from './types';
import { AnnotationWrapper } from '../screens/file/model/annotation.wrapper';
import { ManualRedactionEntry } from '@redaction/red-ui-http';
import { AnnotationFilter } from './types';
export class AnnotationUtils {
public static sortAnnotations(annotations: AnnotationWrapper[]): AnnotationWrapper[] {
return annotations.sort((ann1, ann2) => {
if (ann1.pageNumber === ann2.pageNumber) {
if (ann1.x === ann2.y) {
if (ann1.x === ann2.y) {
return 0;
}
return ann1.x < ann2.x ? -1 : 1;
if (ann1.y === ann2.y) {
return ann1.x < ann2.x ? 1 : -1;
} else {
return ann1.y < ann2.y ? 1 : -1;
}
return ann1.y < ann2.y ? -1 : 1;
}
return ann1.pageNumber < ann2.pageNumber ? -1 : 1;
});
}
public static hasSubsections(filter: AnnotationFilters | boolean) {
public static hasSubsections(filter: AnnotationFilter | boolean) {
return filter instanceof Object;
}
public static checkedSubkeys(filter: AnnotationFilters | boolean) {
public static checkedSubkeys(filter: AnnotationFilter | boolean) {
return Object.keys(filter).filter((subkey) => this.isChecked(filter[subkey])).length;
}
// Only some of the sub-items are selected
public static isIndeterminate(filter: AnnotationFilters | boolean): boolean {
public static isIndeterminate(filter: AnnotationFilter | boolean): boolean {
return this.hasSubsections(filter)
? AnnotationUtils.checkedSubkeys(filter) > 0 && !this.isChecked(filter)
: false;
}
// All sub-items are selected
public static isChecked(filter: AnnotationFilters | boolean): boolean {
public static isChecked(filter: AnnotationFilter | boolean): boolean {
return this.hasSubsections(filter)
? AnnotationUtils.checkedSubkeys(filter) === Object.keys(filter).length
: (filter as boolean);
}
public static hasActiveFilters(filter: AnnotationFilters): boolean {
public static hasActiveFilters(filter: AnnotationFilter[]): boolean {
const activeFilters = Object.keys(filter).filter((key) => {
return this.isChecked(filter[key]) || this.isIndeterminate(filter[key]);
});
return activeFilters.length > 0;
// return activeFilters.length > 0;
return false;
}
public static parseAnnotations(
annotations: AnnotationWrapper[],
filters: AnnotationFilters
filters: AnnotationFilter[]
): { [key: number]: { annotations: AnnotationWrapper[] } } {
const obj = {};
@ -86,19 +83,8 @@ export class AnnotationUtils {
obj[page].annotations = this.sortAnnotations(obj[page].annotations);
});
console.log(obj);
return obj;
}
public static filterAndConvertAnnotations(
annotations: Annotations.Annotation[],
manualRedactions: ManualRedactionEntry[]
) {
const convertedAnnotations: AnnotationWrapper[] = [];
for (const annotation of annotations) {
if (annotation.Id.indexOf(':') > 0) {
convertedAnnotations.push(new AnnotationWrapper(annotation, manualRedactions));
}
}
return convertedAnnotations;
}
}

View File

@ -8,6 +8,9 @@ export class SortingOption {
column: string;
}
export class AnnotationFilters {
[key: string]: boolean | {};
export interface AnnotationFilter {
key: string;
checked?: boolean;
indeterminate?: boolean;
filters?: AnnotationFilter[];
}

View File

@ -44,6 +44,9 @@ server {
location /status {
proxy_pass $API_URL;
}
location /redactionLog {
proxy_pass $API_URL;
}
client_max_body_size 0;
gzip_min_length 1000;
gzip on;