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 1cf1d6382..79d076308 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 @@ -4,7 +4,7 @@
{{ 'file-preview.standard' | translate }} - {{ 'file-preview.delta' | translate }} + {{ 'file-preview.delta' | translate }} {{ 'file-preview.redacted' | translate }} 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 6304b06e5..3831f07da 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 @@ -32,7 +32,8 @@ import { download } from '../../../utils/file-download-utils'; import { MatButtonToggleChange } from '@angular/material/button-toggle'; import { ViewMode } from '../model/view-mode'; -const KEY_ARRAY = ['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown', 'f', 'F', 'Escape']; +const COMMAND_KEY_ARRAY = ['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown', 'Escape']; +const ALL_HOTKEY_ARRAY = ['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown', 'Escape', 'F', 'f']; @Component({ selector: 'redaction-file-preview-screen', @@ -101,19 +102,41 @@ export class FilePreviewScreenComponent implements OnInit, OnDestroy { updateViewMode() { const allAnnotations = this._instance.annotManager.getAnnotationsList(); + const redactions = allAnnotations.filter((a) => a.getCustomData('redaction')); - if (this.viewMode === 'STANDARD') { - redactions.forEach((redaction) => { - redaction['StrokeColor'] = redaction.getCustomData('annotationColor'); - }); - this._instance.annotManager.showAnnotations(allAnnotations); - } else { - const other = allAnnotations.filter((a) => !a.getCustomData('redaction')); - redactions.forEach((redaction) => { - redaction['StrokeColor'] = redaction.getCustomData('redactionColor'); - }); - this._instance.annotManager.hideAnnotations(other); + + switch (this.viewMode) { + case 'STANDARD': + const standardEntries = allAnnotations.filter((a) => !a.getCustomData('changeLogRemoved')); + const nonStandardEntries = allAnnotations.filter((a) => a.getCustomData('changeLogRemoved')); + redactions.forEach((redaction) => { + redaction['StrokeColor'] = redaction.getCustomData('annotationColor'); + }); + this._instance.annotManager.showAnnotations(standardEntries); + this._instance.annotManager.hideAnnotations(nonStandardEntries); + break; + case 'DELTA': + const changeLogEntries = allAnnotations.filter((a) => a.getCustomData('changeLog')); + const nonChangeLogEntries = allAnnotations.filter((a) => !a.getCustomData('changeLog')); + redactions.forEach((redaction) => { + redaction['StrokeColor'] = redaction.getCustomData('annotationColor'); + }); + this._instance.annotManager.showAnnotations(changeLogEntries); + this._instance.annotManager.hideAnnotations(nonChangeLogEntries); + break; + case 'REDACTED': + const redactionEntries = allAnnotations.filter((a) => a.getCustomData('redaction')); + const nonRedactionEntries = allAnnotations.filter((a) => !a.getCustomData('redaction')); + redactions.forEach((redaction) => { + redaction['StrokeColor'] = redaction.getCustomData('redactionColor'); + }); + this._instance.annotManager.showAnnotations(redactionEntries); + this._instance.annotManager.hideAnnotations(nonRedactionEntries); + break; } + + this._rebuildFilters(); + this._updateCanPerformActions(); } @@ -137,6 +160,10 @@ export class FilePreviewScreenComponent implements OnInit, OnDestroy { return this.permissionsService.fileRequiresReanalysis(); } + get canNotSwitchToDeltaView() { + return this.fileData?.redactionChangeLog?.redactionLogEntry?.length === 0; + } + ngOnInit(): void { document.documentElement.addEventListener('fullscreenchange', (event) => { if (!document.fullscreenElement) { @@ -202,6 +229,7 @@ export class FilePreviewScreenComponent implements OnInit, OnDestroy { this.annotations = this.fileData.getAnnotations( this.appStateService.dictionaryData[this.appStateService.activeProject.ruleSetId], this.permissionsService.currentUser, + this.viewMode, this.userPreferenceService.areDevFeaturesEnabled ); const annotationFilters = this._annotationProcessingService.getAnnotationFilter(this.annotations); @@ -299,7 +327,7 @@ export class FilePreviewScreenComponent implements OnInit, OnDestroy { @HostListener('window:keyup', ['$event']) handleKeyEvent($event: KeyboardEvent) { - if (!KEY_ARRAY.includes($event.key) || this._dialogRef?.getState() === MatDialogState.OPEN) { + if (!ALL_HOTKEY_ARRAY.includes($event.key) || this._dialogRef?.getState() === MatDialogState.OPEN) { return; } @@ -309,6 +337,10 @@ export class FilePreviewScreenComponent implements OnInit, OnDestroy { } if (['f', 'F'].includes($event.key)) { + // if you type in an input, don't toggle full-screen + if ($event.target instanceof HTMLInputElement) { + return; + } this.toggleFullScreen(); return; } @@ -485,7 +517,7 @@ export class FilePreviewScreenComponent implements OnInit, OnDestroy { } preventKeyDefault($event: KeyboardEvent) { - if (KEY_ARRAY.includes($event.key)) { + if (COMMAND_KEY_ARRAY.includes($event.key)) { $event.preventDefault(); } } @@ -494,7 +526,7 @@ export class FilePreviewScreenComponent implements OnInit, OnDestroy { this._fileDownloadService.loadActiveFileManualAnnotations().subscribe((manualRedactions) => { this.fileData.manualRedactions = manualRedactions; this._rebuildFilters(); - this._annotationDrawService.drawAnnotations(this._instance, this.annotations, this.viewMode); + this._annotationDrawService.drawAnnotations(this._instance, this.annotations); }); } 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 b3881bc5c..f07f4d1a8 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 @@ -37,6 +37,9 @@ export class AnnotationWrapper { textAfter?: string; textBefore?: string; + isChangeLogEntry?: boolean; + changeLogType?: 'ADDED' | 'REMOVED'; + constructor() {} get isUndoableSuperType() { @@ -50,6 +53,10 @@ export class AnnotationWrapper { ); } + get isChangeLogRemoved() { + return this.changeLogType === 'REMOVED'; + } + get descriptor() { return this.isModifyDictionary ? 'dictionary' : 'type'; } @@ -150,6 +157,8 @@ export class AnnotationWrapper { const annotationWrapper = new AnnotationWrapper(); annotationWrapper.annotationId = redactionLogEntry.id; + annotationWrapper.isChangeLogEntry = redactionLogEntry.isChangeLogEntry; + annotationWrapper.changeLogType = redactionLogEntry.changeLogType; annotationWrapper.redaction = redactionLogEntry.redacted; annotationWrapper.hint = redactionLogEntry.hint; annotationWrapper.dictionary = redactionLogEntry.type; 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 index e359cc1dd..24f8f88a7 100644 --- 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 @@ -1,14 +1,25 @@ -import { IdRemoval, ManualRedactionEntry, ManualRedactions, RedactionLog, RedactionLogEntry, TypeValue, ViewedPages } from '@redaction/red-ui-http'; +import { + IdRemoval, + ManualRedactionEntry, + ManualRedactions, + RedactionChangeLog, + RedactionLog, + RedactionLogEntry, + TypeValue, + ViewedPages +} from '@redaction/red-ui-http'; import { FileStatusWrapper } from './file-status.wrapper'; import { UserWrapper } from '../../../user/user.service'; import { AnnotationWrapper } from './annotation.wrapper'; import { RedactionLogEntryWrapper } from './redaction-log-entry.wrapper'; +import { ViewMode } from './view-mode'; export class FileDataModel { constructor( public fileStatus: FileStatusWrapper, public fileData: Blob, public redactionLog: RedactionLog, + public redactionChangeLog: RedactionChangeLog, public manualRedactions: ManualRedactions, public viewedPages?: ViewedPages ) {} @@ -17,17 +28,32 @@ export class FileDataModel { return this.redactionLog.redactionLogEntry; } - getAnnotations(dictionaryData: { [p: string]: TypeValue }, currentUser: UserWrapper, areDevFeaturesEnabled: boolean): AnnotationWrapper[] { + getAnnotations( + dictionaryData: { [p: string]: TypeValue }, + currentUser: UserWrapper, + viewMode: ViewMode, + areDevFeaturesEnabled: boolean + ): AnnotationWrapper[] { const entries: RedactionLogEntryWrapper[] = this._convertData(dictionaryData); let annotations = entries.map((entry) => AnnotationWrapper.fromData(entry)); // filter based on dev-mode annotations = annotations.filter((annotation) => { - if (!areDevFeaturesEnabled) { - return !annotation.isIgnored && !annotation.isFalsePositive; + if (viewMode === 'STANDARD') { + if (annotation.isChangeLogRemoved) { + return false; + } else { + if (!areDevFeaturesEnabled) { + return !annotation.isIgnored && !annotation.isFalsePositive; + } else { + return true; + } + } + } else if (viewMode === 'DELTA') { + return annotation.isChangeLogEntry; } else { - return true; + return annotation.isRedacted; } }); @@ -37,16 +63,34 @@ export class FileDataModel { private _convertData(dictionaryData: { [p: string]: TypeValue }): RedactionLogEntryWrapper[] { let result: RedactionLogEntryWrapper[] = []; + this.redactionChangeLog.redactionLogEntry.forEach((changeLogEntry) => { + if (changeLogEntry.changeType === 'REMOVED') { + const redactionLogEntryWrapper: RedactionLogEntryWrapper = { actionPendingReanalysis: false }; + + Object.assign(redactionLogEntryWrapper, changeLogEntry); + + redactionLogEntryWrapper.comments = this.manualRedactions.comments[redactionLogEntryWrapper.id]; + redactionLogEntryWrapper.isChangeLogEntry = true; + redactionLogEntryWrapper.changeLogType = changeLogEntry.changeType; + redactionLogEntryWrapper.id = 'changed-log-removed-' + redactionLogEntryWrapper.id; + result.push(redactionLogEntryWrapper); + } + }); + this.redactionLog.redactionLogEntry.forEach((redactionLogEntry) => { // false positive entries from the redaction-log need to be skipped if (redactionLogEntry.type === 'false_positive') { return; } + const existingChangeLogEntry = this.redactionChangeLog.redactionLogEntry.find((rle) => rle.id === redactionLogEntry.id); + // copy the redactionLog Entry const redactionLogEntryWrapper: RedactionLogEntryWrapper = { actionPendingReanalysis: false }; Object.assign(redactionLogEntryWrapper, redactionLogEntry); redactionLogEntryWrapper.comments = this.manualRedactions.comments[redactionLogEntryWrapper.id]; + redactionLogEntryWrapper.isChangeLogEntry = !!existingChangeLogEntry; + redactionLogEntryWrapper.changeLogType = 'ADDED'; result.push(redactionLogEntryWrapper); }); diff --git a/apps/red-ui/src/app/screens/file/model/redaction-log-entry.wrapper.ts b/apps/red-ui/src/app/screens/file/model/redaction-log-entry.wrapper.ts index 767fbcb59..6ceb8b46a 100644 --- a/apps/red-ui/src/app/screens/file/model/redaction-log-entry.wrapper.ts +++ b/apps/red-ui/src/app/screens/file/model/redaction-log-entry.wrapper.ts @@ -29,4 +29,7 @@ export interface RedactionLogEntryWrapper { userId?: string; comments?: Comment[]; + + isChangeLogEntry?: boolean; + changeLogType?: 'ADDED' | 'REMOVED'; } 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 3d157d589..24c3b0a67 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 @@ -437,7 +437,7 @@ export class PdfViewerComponent implements OnInit, AfterViewInit, OnChanges { this.instance.hotkeys.off('A'); this.instance.hotkeys.off('C'); this.instance.hotkeys.off('E'); - this.instance.hotkeys.off('F'); + // this.instance.hotkeys.off('F'); this.instance.hotkeys.off('I'); this.instance.hotkeys.off('L'); this.instance.hotkeys.off('N'); diff --git a/apps/red-ui/src/app/screens/file/service/annotation-draw.service.ts b/apps/red-ui/src/app/screens/file/service/annotation-draw.service.ts index 47e209b23..464442597 100644 --- a/apps/red-ui/src/app/screens/file/service/annotation-draw.service.ts +++ b/apps/red-ui/src/app/screens/file/service/annotation-draw.service.ts @@ -5,7 +5,6 @@ import { hexToRgb } from '../../../utils/functions'; import { AppStateService } from '../../../state/app-state.service'; import { AnnotationWrapper } from '../model/annotation.wrapper'; import { UserPreferenceService } from '../../../common/service/user-preference.service'; -import { ViewMode } from '../model/view-mode'; @Injectable({ providedIn: 'root' @@ -17,17 +16,10 @@ export class AnnotationDrawService { private readonly _userPreferenceService: UserPreferenceService ) {} - public drawAnnotations(activeViewer: WebViewerInstance, annotationWrappers: AnnotationWrapper[], viewMode: ViewMode = 'STANDARD') { + public drawAnnotations(activeViewer: WebViewerInstance, annotationWrappers: AnnotationWrapper[]) { const annotations = []; annotationWrappers.forEach((annotation) => { - if (viewMode === 'REDACTED') { - if (annotation.isRedacted) { - const pdfViewerAnnotation = this.computeAnnotation(activeViewer, annotation); - annotations.push(pdfViewerAnnotation); - } - } else { - annotations.push(this.computeAnnotation(activeViewer, annotation)); - } + annotations.push(this.computeAnnotation(activeViewer, annotation)); }); const annotationManager = activeViewer.annotManager; @@ -89,7 +81,13 @@ export class AnnotationDrawService { highlight.Quads = this._rectanglesToQuads(annotationWrapper.positions, activeViewer, pageNumber); highlight.Id = annotationWrapper.id; highlight.ReadOnly = true; + // change log entries are drawn lighter + highlight.Opacity = annotationWrapper.isChangeLogRemoved ? 0.2 : 1; + highlight.Hidden = annotationWrapper.isChangeLogRemoved; + highlight.setCustomData('redaction', annotationWrapper.isRedacted); + highlight.setCustomData('changeLog', annotationWrapper.isChangeLogEntry); + highlight.setCustomData('changeLogRemoved', annotationWrapper.isChangeLogRemoved); highlight.setCustomData('redactionColor', this.getColor(activeViewer, 'ignore', 'ignore')); highlight.setCustomData('annotationColor', this.getColor(activeViewer, annotationWrapper.superType, annotationWrapper.dictionary)); diff --git a/apps/red-ui/src/app/screens/file/service/pdf-viewer-data.service.ts b/apps/red-ui/src/app/screens/file/service/pdf-viewer-data.service.ts index b96cfd4d1..a97e95c0e 100644 --- a/apps/red-ui/src/app/screens/file/service/pdf-viewer-data.service.ts +++ b/apps/red-ui/src/app/screens/file/service/pdf-viewer-data.service.ts @@ -33,13 +33,17 @@ export class PdfViewerDataService { loadActiveFileData(): Observable { const fileObs = this.downloadOriginalFile(this._appStateService.activeFile); const reactionLogObs = this._redactionLogControllerService.getRedactionLog(this._appStateService.activeProjectId, this._appStateService.activeFileId); + const redactionChangeLogObs = this._redactionLogControllerService.getRedactionChangeLog( + this._appStateService.activeProjectId, + this._appStateService.activeFileId + ); const manualRedactionsObs = this._manualRedactionControllerService.getManualRedaction( this._appStateService.activeProjectId, this._appStateService.activeFileId ); const viewedPagesObs = this.getViewedPagesForActiveFile(); - return forkJoin([fileObs, reactionLogObs, manualRedactionsObs, viewedPagesObs]).pipe( + return forkJoin([fileObs, reactionLogObs, redactionChangeLogObs, manualRedactionsObs, viewedPagesObs]).pipe( map((data) => { return new FileDataModel(this._appStateService.activeFile, ...data); }) diff --git a/apps/red-ui/src/app/utils/pdf-coordinates.ts b/apps/red-ui/src/app/utils/pdf-coordinates.ts index e4274eb82..a5c16b1b4 100644 --- a/apps/red-ui/src/app/utils/pdf-coordinates.ts +++ b/apps/red-ui/src/app/utils/pdf-coordinates.ts @@ -48,6 +48,5 @@ export function translateQuads(page: number, rotation: number, quads: any) { default: result = quads; } - console.log(quads, result); return result; } diff --git a/apps/red-ui/src/assets/styles/red-toggle-button.scss b/apps/red-ui/src/assets/styles/red-toggle-button.scss index a76d3e506..07c95ccad 100644 --- a/apps/red-ui/src/assets/styles/red-toggle-button.scss +++ b/apps/red-ui/src/assets/styles/red-toggle-button.scss @@ -18,3 +18,11 @@ border-right: 1px solid $white; } } + +.mat-button-toggle-disabled { + .mat-button-toggle-button { + cursor: not-allowed !important; + } + + outline: none; +}