From e07599cf276d7896134166c9cc266e826b318b96 Mon Sep 17 00:00:00 2001 From: Timo Bejan Date: Fri, 30 Oct 2020 17:53:14 +0200 Subject: [PATCH] fixed lint --- apps/red-ui/proxy.conf.json | 6 + .../file-preview-screen.component.html | 114 ++++++++---------- .../file-preview-screen.component.ts | 76 ++++++------ .../screens/file/model/annotation.wrapper.ts | 61 +++++----- .../app/screens/file/model/file-data.model.ts | 9 ++ .../file/pdf-viewer/pdf-viewer.component.ts | 39 +----- .../file/service/file-download.service.ts | 22 +++- .../screens/file/service/filters.service.ts | 36 +++--- apps/red-ui/src/app/utils/annotation-utils.ts | 44 +++---- apps/red-ui/src/app/utils/types.d.ts | 7 +- docker/common/nginx/nginx.conf.template | 3 + 11 files changed, 197 insertions(+), 220 deletions(-) create mode 100644 apps/red-ui/src/app/screens/file/model/file-data.model.ts diff --git a/apps/red-ui/proxy.conf.json b/apps/red-ui/proxy.conf.json index 53049ec45..bc4a43128 100644 --- a/apps/red-ui/proxy.conf.json +++ b/apps/red-ui/proxy.conf.json @@ -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, diff --git a/apps/red-ui/src/app/screens/file/file-preview-screen/file-preview-screen.component.html b/apps/red-ui/src/app/screens/file/file-preview-screen/file-preview-screen.component.html index 797b0110b..502e773e0 100644 --- a/apps/red-ui/src/app/screens/file/file-preview-screen/file-preview-screen.component.html +++ b/apps/red-ui/src/app/screens/file/file-preview-screen/file-preview-screen.component.html @@ -74,14 +74,13 @@
@@ -121,65 +120,58 @@ >
-
-
-
- - - - -
- - + + + + + + + + + + + + + + + + + + + + + + + + + + + + - {{ 'file-preview.filter-menu.' + key + '.label' | translate }} - -
-
-
- - - {{ appStateService.getDictionaryLabel(subkey) }} - -
-
-
+ + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/red-ui/src/app/screens/file/file-preview-screen/file-preview-screen.component.ts b/apps/red-ui/src/app/screens/file/file-preview-screen/file-preview-screen.component.ts index b5554d63a..aa9f42429 100644 --- a/apps/red-ui/src/app/screens/file/file-preview-screen/file-preview-screen.component.ts +++ b/apps/red-ui/src/app/screens/file/file-preview-screen/file-preview-screen.component.ts @@ -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; + } } diff --git a/apps/red-ui/src/app/screens/file/model/annotation.wrapper.ts b/apps/red-ui/src/app/screens/file/model/annotation.wrapper.ts index 07174c644..f9da8f02a 100644 --- a/apps/red-ui/src/app/screens/file/model/annotation.wrapper.ts +++ b/apps/red-ui/src/app/screens/file/model/annotation.wrapper.ts @@ -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() + "\""; + // } } diff --git a/apps/red-ui/src/app/screens/file/model/file-data.model.ts b/apps/red-ui/src/app/screens/file/model/file-data.model.ts new file mode 100644 index 000000000..c15915b92 --- /dev/null +++ b/apps/red-ui/src/app/screens/file/model/file-data.model.ts @@ -0,0 +1,9 @@ +import { RedactionLog } from '@redaction/red-ui-http'; + +export class FileDataModel { + constructor( + public annotatedFileData: Blob, + public redactedFileData: Blob, + public redactionLog: RedactionLog + ) {} +} diff --git a/apps/red-ui/src/app/screens/file/pdf-viewer/pdf-viewer.component.ts b/apps/red-ui/src/app/screens/file/pdf-viewer/pdf-viewer.component.ts index e0f6bb6f6..9acb10c61 100644 --- a/apps/red-ui/src/app/screens/file/pdf-viewer/pdf-viewer.component.ts +++ b/apps/red-ui/src/app/screens/file/pdf-viewer/pdf-viewer.component.ts @@ -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(); @Input() fileData: Blob; @Input() fileStatus: FileStatus; @Output() fileReady = new EventEmitter(); - @Output() annotationsAdded = new EventEmitter(); - @Output() annotationSelected = new EventEmitter(); + @Output() annotationSelected = new EventEmitter(); @Output() manualAnnotationRequested = new EventEmitter(); @Output() pageChanged = new EventEmitter(); @Output() keyUp = new EventEmitter(); @@ -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) { diff --git a/apps/red-ui/src/app/screens/file/service/file-download.service.ts b/apps/red-ui/src/app/screens/file/service/file-download.service.ts index e5b923c8b..8a2b51918 100644 --- a/apps/red-ui/src/app/screens/file/service/file-download.service.ts +++ b/apps/red-ui/src/app/screens/file/service/file-download.service.ts @@ -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 { + 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, diff --git a/apps/red-ui/src/app/screens/file/service/filters.service.ts b/apps/red-ui/src/app/screens/file/service/filters.service.ts index 4e15b32da..8b8d303b7 100644 --- a/apps/red-ui/src/app/screens/file/service/filters.service.ts +++ b/apps/red-ui/src/app/screens/file/service/filters.service.ts @@ -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 = new Set(); 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: [] }; } } diff --git a/apps/red-ui/src/app/utils/annotation-utils.ts b/apps/red-ui/src/app/utils/annotation-utils.ts index 0b4ca1ffc..ae675f0ad 100644 --- a/apps/red-ui/src/app/utils/annotation-utils.ts +++ b/apps/red-ui/src/app/utils/annotation-utils.ts @@ -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; - } } diff --git a/apps/red-ui/src/app/utils/types.d.ts b/apps/red-ui/src/app/utils/types.d.ts index 40d6aa603..01afaabba 100644 --- a/apps/red-ui/src/app/utils/types.d.ts +++ b/apps/red-ui/src/app/utils/types.d.ts @@ -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[]; } diff --git a/docker/common/nginx/nginx.conf.template b/docker/common/nginx/nginx.conf.template index a76a14591..4ece652cb 100644 --- a/docker/common/nginx/nginx.conf.template +++ b/docker/common/nginx/nginx.conf.template @@ -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;