From b4bdbe4ee4045a860f8b0c47d04895ed7e49e2f3 Mon Sep 17 00:00:00 2001 From: Timo Bejan Date: Tue, 9 Nov 2021 22:56:06 +0200 Subject: [PATCH] RED-2279 RED-2280 - Resize Action --- apps/red-ui/src/app/i18n/language.service.ts | 1 - .../app/models/file/annotation.permissions.ts | 3 + .../src/app/models/file/annotation.wrapper.ts | 1 + .../annotation-actions.component.html | 225 ++++++++++-------- .../annotation-actions.component.ts | 16 ++ .../page-exclusion.component.ts | 3 +- .../pdf-viewer/pdf-viewer.component.ts | 8 +- .../resize-annotation-dialog.component.html | 21 ++ .../resize-annotation-dialog.component.scss | 0 .../resize-annotation-dialog.component.ts | 36 +++ .../app/modules/dossier/dossiers.module.ts | 2 + .../models/annotation-action-mode.model.ts | 4 +- .../services/annotation-actions.service.ts | 165 ++++++++++++- .../services/annotation-draw.service.ts | 52 ++-- .../services/dossiers-dialog.service.ts | 5 + .../services/manual-annotation.service.ts | 23 ++ .../dossier/utils/pdf-calculation.utils.ts | 18 ++ .../modules/dossier/utils/pdf-viewer.utils.ts | 16 +- .../src/app/modules/icons/icons.module.ts | 1 + apps/red-ui/src/assets/i18n/en.json | 19 ++ .../src/assets/icons/general/resize.svg | 27 +++ .../red-domain/src/lib/redaction-log/index.ts | 1 + .../src/lib/redaction-log/resize.request.ts | 8 + 23 files changed, 520 insertions(+), 135 deletions(-) create mode 100644 apps/red-ui/src/app/modules/dossier/dialogs/resize-annotation-dialog/resize-annotation-dialog.component.html create mode 100644 apps/red-ui/src/app/modules/dossier/dialogs/resize-annotation-dialog/resize-annotation-dialog.component.scss create mode 100644 apps/red-ui/src/app/modules/dossier/dialogs/resize-annotation-dialog/resize-annotation-dialog.component.ts create mode 100644 apps/red-ui/src/app/modules/dossier/utils/pdf-calculation.utils.ts create mode 100644 apps/red-ui/src/assets/icons/general/resize.svg create mode 100644 libs/red-domain/src/lib/redaction-log/resize.request.ts diff --git a/apps/red-ui/src/app/i18n/language.service.ts b/apps/red-ui/src/app/i18n/language.service.ts index 906ac365b..4f4331ae7 100644 --- a/apps/red-ui/src/app/i18n/language.service.ts +++ b/apps/red-ui/src/app/i18n/language.service.ts @@ -25,7 +25,6 @@ export class LanguageService { } else { defaultLang = 'en'; } - console.log(defaultLang); document.documentElement.lang = defaultLang; this._translateService.setDefaultLang(defaultLang); this._translateService.use(defaultLang).toPromise().then(); diff --git a/apps/red-ui/src/app/models/file/annotation.permissions.ts b/apps/red-ui/src/app/models/file/annotation.permissions.ts index 62b5645c5..41695bc5b 100644 --- a/apps/red-ui/src/app/models/file/annotation.permissions.ts +++ b/apps/red-ui/src/app/models/file/annotation.permissions.ts @@ -12,6 +12,7 @@ export class AnnotationPermissions { canRejectSuggestion = true; canForceRedaction = true; canChangeLegalBasis = true; + canResizeAnnotation = true; canRecategorizeImage = true; static forUser(isApprover: boolean, user: User, annotations: AnnotationWrapper | AnnotationWrapper[]) { @@ -41,6 +42,8 @@ export class AnnotationPermissions { permissions.canRecategorizeImage = annotation.isImage; + permissions.canResizeAnnotation = annotation.isRedacted || annotation.isImage; + summedPermissions._merge(permissions); } return summedPermissions; diff --git a/apps/red-ui/src/app/models/file/annotation.wrapper.ts b/apps/red-ui/src/app/models/file/annotation.wrapper.ts index ff1c95d17..81603ade5 100644 --- a/apps/red-ui/src/app/models/file/annotation.wrapper.ts +++ b/apps/red-ui/src/app/models/file/annotation.wrapper.ts @@ -45,6 +45,7 @@ export class AnnotationWrapper { recommendationType: string; legalBasisValue: string; legalBasisChangeValue?: string; + resizing?: boolean; manual?: boolean; diff --git a/apps/red-ui/src/app/modules/dossier/components/annotation-actions/annotation-actions.component.html b/apps/red-ui/src/app/modules/dossier/components/annotation-actions/annotation-actions.component.html index e7ca4524f..49da6275b 100644 --- a/apps/red-ui/src/app/modules/dossier/components/annotation-actions/annotation-actions.component.html +++ b/apps/red-ui/src/app/modules/dossier/components/annotation-actions/annotation-actions.component.html @@ -1,109 +1,142 @@
- + + + - + + - + + + - + - + - + - + - + - + - + - + - + + + + + + + +
diff --git a/apps/red-ui/src/app/modules/dossier/components/annotation-actions/annotation-actions.component.ts b/apps/red-ui/src/app/modules/dossier/components/annotation-actions/annotation-actions.component.ts index 2b86746fd..e0aa2c6ec 100644 --- a/apps/red-ui/src/app/modules/dossier/components/annotation-actions/annotation-actions.component.ts +++ b/apps/red-ui/src/app/modules/dossier/components/annotation-actions/annotation-actions.component.ts @@ -63,6 +63,10 @@ export class AnnotationActionsComponent implements OnInit { return this.annotations?.reduce((accumulator, annotation) => annotation.isImage && accumulator, true); } + get resizing() { + return this.annotations?.length === 1 && this.annotations?.[0].resizing; + } + ngOnInit(): void { this._setPermissions(); } @@ -97,4 +101,16 @@ export class AnnotationActionsComponent implements OnInit { this.annotations, ); } + + resize($event: MouseEvent) { + this.annotationActionsService.resize($event, this.viewer, this.annotations[0]); + } + + acceptResize($event: MouseEvent) { + this.annotationActionsService.acceptResize($event, this.viewer, this.annotations[0], this.annotationsChanged); + } + + cancelResize($event: MouseEvent) { + this.annotationActionsService.cancelResize($event, this.viewer, this.annotations[0], this.annotationsChanged); + } } diff --git a/apps/red-ui/src/app/modules/dossier/components/page-exclusion/page-exclusion.component.ts b/apps/red-ui/src/app/modules/dossier/components/page-exclusion/page-exclusion.component.ts index 14c4a721f..122980e03 100644 --- a/apps/red-ui/src/app/modules/dossier/components/page-exclusion/page-exclusion.component.ts +++ b/apps/red-ui/src/app/modules/dossier/components/page-exclusion/page-exclusion.component.ts @@ -40,8 +40,9 @@ export class PageExclusionComponent implements OnChanges { }, []); } - async excludePagesRange(value: string): Promise { + async excludePagesRange(inputValue: string): Promise { this._loadingService.start(); + const value = inputValue.replace(/[^0-9-,]/g, ''); try { const pageRanges = value.split(',').map(range => { const splitted = range.split('-'); diff --git a/apps/red-ui/src/app/modules/dossier/components/pdf-viewer/pdf-viewer.component.ts b/apps/red-ui/src/app/modules/dossier/components/pdf-viewer/pdf-viewer.component.ts index e460b7e02..da430c61e 100644 --- a/apps/red-ui/src/app/modules/dossier/components/pdf-viewer/pdf-viewer.component.ts +++ b/apps/red-ui/src/app/modules/dossier/components/pdf-viewer/pdf-viewer.component.ts @@ -36,6 +36,7 @@ import { ActivatedRoute } from '@angular/router'; import Tools = Core.Tools; import TextTool = Tools.TextTool; import Annotation = Core.Annotations.Annotation; +import { toPosition } from '../../utils/pdf-calculation.utils'; const ALLOWED_KEYBOARD_SHORTCUTS = ['+', '-', 'p', 'r', 'Escape'] as const; const dataElements = { @@ -253,6 +254,8 @@ export class PdfViewerComponent implements OnInit, OnChanges { // this will auto select rectangle after drawing if (annotations.length === 1 && annotations[0].ToolName === 'AnnotationCreateRectangle') { this.annotationManager.selectAnnotations(annotations); + annotations[0].setRotationControlEnabled(false); + console.log(annotations[0]); } }); @@ -456,7 +459,7 @@ export class PdfViewerComponent implements OnInit, OnChanges { } this.instance.UI.annotationPopup.add( - this._annotationActionsService.getViewerAvailableActions(annotationWrappers, this.annotationsChanged), + this._annotationActionsService.getViewerAvailableActions(this.instance, annotationWrappers, this.annotationsChanged), ); } @@ -598,7 +601,8 @@ export class PdfViewerComponent implements OnInit, OnChanges { for (const key of Object.keys(quads)) { for (const quad of quads[key]) { const page = parseInt(key, 10); - entry.positions.push(this.utils.toPosition(page, convertQuads ? this.utils.translateQuads(page, quad) : quad)); + const pageHeight = this.documentViewer.getPageHeight(page); + entry.positions.push(toPosition(page, pageHeight, convertQuads ? this.utils.translateQuads(page, quad) : quad)); } } diff --git a/apps/red-ui/src/app/modules/dossier/dialogs/resize-annotation-dialog/resize-annotation-dialog.component.html b/apps/red-ui/src/app/modules/dossier/dialogs/resize-annotation-dialog/resize-annotation-dialog.component.html new file mode 100644 index 000000000..72a505f70 --- /dev/null +++ b/apps/red-ui/src/app/modules/dossier/dialogs/resize-annotation-dialog/resize-annotation-dialog.component.html @@ -0,0 +1,21 @@ +
+
+
+ +
+
+ + +
+
+ +
+ +
+
+
+ + +
diff --git a/apps/red-ui/src/app/modules/dossier/dialogs/resize-annotation-dialog/resize-annotation-dialog.component.scss b/apps/red-ui/src/app/modules/dossier/dialogs/resize-annotation-dialog/resize-annotation-dialog.component.scss new file mode 100644 index 000000000..e69de29bb diff --git a/apps/red-ui/src/app/modules/dossier/dialogs/resize-annotation-dialog/resize-annotation-dialog.component.ts b/apps/red-ui/src/app/modules/dossier/dialogs/resize-annotation-dialog/resize-annotation-dialog.component.ts new file mode 100644 index 000000000..f344de1cd --- /dev/null +++ b/apps/red-ui/src/app/modules/dossier/dialogs/resize-annotation-dialog/resize-annotation-dialog.component.ts @@ -0,0 +1,36 @@ +import { Component, OnInit } from '@angular/core'; +import { TranslateService } from '@ngx-translate/core'; +import { MatDialogRef } from '@angular/material/dialog'; +import { FormBuilder, FormGroup, Validators } from '@angular/forms'; +import { PermissionsService } from '@services/permissions.service'; + +@Component({ + selector: 'redaction-resize-annotation-dialog', + templateUrl: './resize-annotation-dialog.component.html', + styleUrls: ['./resize-annotation-dialog.component.scss'], +}) +export class ResizeAnnotationDialogComponent implements OnInit { + resizeForm: FormGroup; + isDocumentAdmin: boolean; + + constructor( + private readonly _translateService: TranslateService, + private readonly _permissionsService: PermissionsService, + private readonly _formBuilder: FormBuilder, + public dialogRef: MatDialogRef, + ) {} + + async ngOnInit() { + this.isDocumentAdmin = this._permissionsService.isApprover(); + + this.resizeForm = this._formBuilder.group({ + comment: this.isDocumentAdmin ? [null] : [null, Validators.required], + }); + } + + save() { + this.dialogRef.close({ + comment: this.resizeForm.get('comment').value, + }); + } +} diff --git a/apps/red-ui/src/app/modules/dossier/dossiers.module.ts b/apps/red-ui/src/app/modules/dossier/dossiers.module.ts index 4a1007320..507ab21b6 100644 --- a/apps/red-ui/src/app/modules/dossier/dossiers.module.ts +++ b/apps/red-ui/src/app/modules/dossier/dossiers.module.ts @@ -40,6 +40,7 @@ import { AnnotationSourceComponent } from './components/file-workload/components import { OverlayModule } from '@angular/cdk/overlay'; import { SharedDossiersModule } from './shared/shared-dossiers.module'; import { PlatformSearchService } from './shared/services/platform-search.service'; +import { ResizeAnnotationDialogComponent } from './dialogs/resize-annotation-dialog/resize-annotation-dialog.component'; const screens = [FilePreviewScreenComponent, SearchScreenComponent]; @@ -49,6 +50,7 @@ const dialogs = [ ManualAnnotationDialogComponent, ForceRedactionDialogComponent, RemoveAnnotationsDialogComponent, + ResizeAnnotationDialogComponent, DocumentInfoDialogComponent, AssignReviewerApproverDialogComponent, ChangeLegalBasisDialogComponent, diff --git a/apps/red-ui/src/app/modules/dossier/models/annotation-action-mode.model.ts b/apps/red-ui/src/app/modules/dossier/models/annotation-action-mode.model.ts index eabbda0a9..83f3965a0 100644 --- a/apps/red-ui/src/app/modules/dossier/models/annotation-action-mode.model.ts +++ b/apps/red-ui/src/app/modules/dossier/models/annotation-action-mode.model.ts @@ -11,4 +11,6 @@ export type AnnotationActionMode = | 'suggest' | 'undo' | 'force-redaction' - | 'request-force-redaction'; + | 'request-force-redaction' + | 'resize' + | 'request-resize'; diff --git a/apps/red-ui/src/app/modules/dossier/services/annotation-actions.service.ts b/apps/red-ui/src/app/modules/dossier/services/annotation-actions.service.ts index c2836a9f7..d257e5cc6 100644 --- a/apps/red-ui/src/app/modules/dossier/services/annotation-actions.service.ts +++ b/apps/red-ui/src/app/modules/dossier/services/annotation-actions.service.ts @@ -9,8 +9,11 @@ import { AnnotationPermissions } from '@models/file/annotation.permissions'; import { DossiersDialogService } from './dossiers-dialog.service'; import { BASE_HREF } from '../../../tokens'; import { UserService } from '@services/user.service'; -import { Core } from '@pdftron/webviewer'; -import { IAddRedactionRequest, ILegalBasisChangeRequest } from '@red/domain'; +import { Core, WebViewerInstance } from '@pdftron/webviewer'; +import { IAddRedactionRequest, ILegalBasisChangeRequest, IRectangle, IResizeRequest } from '@red/domain'; +import { AppStateService } from '../../../state/app-state.service'; +import { toPosition } from '../utils/pdf-calculation.utils'; +import { AnnotationDrawService } from './annotation-draw.service'; import Annotation = Core.Annotations.Annotation; @Injectable() @@ -18,11 +21,13 @@ export class AnnotationActionsService { constructor( @Inject(BASE_HREF) private readonly _baseHref: string, private readonly _ngZone: NgZone, + private readonly _appStateService: AppStateService, private readonly _userService: UserService, private readonly _permissionsService: PermissionsService, private readonly _manualAnnotationService: ManualAnnotationService, private readonly _translateService: TranslateService, private readonly _dialogService: DossiersDialogService, + private readonly _annotationDrawService: AnnotationDrawService, ) {} acceptSuggestion($event: MouseEvent, annotations: AnnotationWrapper[], annotationsChanged: EventEmitter) { @@ -123,6 +128,7 @@ export class AnnotationActionsService { } getViewerAvailableActions( + viewer: WebViewerInstance, annotations: AnnotationWrapper[], annotationsChanged: EventEmitter, ): Record[] { @@ -133,6 +139,49 @@ export class AnnotationActionsService { permissions: AnnotationPermissions.forUser(this._permissionsService.isApprover(), this._userService.currentUser, annotation), })); + // you can only resize one annotation at a time + const canResize = + annotationPermissions.length === 1 && + annotationPermissions.reduce((acc, next) => acc && next.permissions.canResizeAnnotation, true); + if (canResize) { + const firstAnnotation = annotations[0]; + // if we already entered resize-mode previously + if (firstAnnotation.resizing) { + availableActions.push({ + type: 'actionButton', + img: this._convertPath('/assets/icons/general/check.svg'), + title: this._translateService.instant('annotation-actions.resize-accept.label'), + onClick: () => { + this._ngZone.run(() => { + this.acceptResize(null, viewer, firstAnnotation, annotationsChanged); + }); + }, + }); + availableActions.push({ + type: 'actionButton', + img: this._convertPath('/assets/icons/general/close.svg'), + title: this._translateService.instant('annotation-actions.resize-cancel.label'), + onClick: () => { + this._ngZone.run(() => { + this.cancelResize(null, viewer, firstAnnotation, annotationsChanged); + }); + }, + }); + return availableActions; + } + + availableActions.push({ + type: 'actionButton', + img: this._convertPath('/assets/icons/general/resize.svg'), + title: this._translateService.instant('annotation-actions.resize.label'), + onClick: () => { + this._ngZone.run(() => { + this.resize(null, viewer, annotations[0]); + }); + }, + }); + } + const canChangeLegalBasis = annotationPermissions.reduce((acc, next) => acc && next.permissions.canChangeLegalBasis, true); if (canChangeLegalBasis) { availableActions.push({ @@ -337,4 +386,116 @@ export class AnnotationActionsService { private _convertPath(path: string): string { return this._baseHref + path; } + + resize($event: MouseEvent, viewer: WebViewerInstance, annotationWrapper: AnnotationWrapper) { + $event?.stopPropagation(); + + annotationWrapper.resizing = true; + + const annotationManager = viewer.Core.annotationManager; + const viewerAnnotation = annotationManager.getAnnotationById(annotationWrapper.id); + viewerAnnotation.ReadOnly = false; + viewerAnnotation.setRotationControlEnabled(false); + annotationManager.redrawAnnotation(viewerAnnotation); + annotationManager.selectAnnotation(viewerAnnotation); + + // console.log(viewerAnnotation); + } + + private async _extractTextAndPositions(viewer: WebViewerInstance, annotationId: string) { + const viewerAnnotation = viewer.Core.annotationManager.getAnnotationById(annotationId); + + const document = await viewer.Core.documentViewer.getDocument().getPDFDoc(); + const page = await document.getPage(viewerAnnotation.getPageNumber()); + let quads = (viewerAnnotation).Quads; + if (!quads) { + quads = [this._annotationDrawService.annotationToQuads(viewerAnnotation, viewer)]; + const rect = toPosition(viewerAnnotation.getPageNumber(), await page.getPageHeight(), quads[0]); + return { + positions: [rect], + text: null, + }; + } + + const words = []; + const rectangles: IRectangle[] = []; + for (const quad of quads) { + const rect = toPosition(viewerAnnotation.getPageNumber(), await page.getPageHeight(), quad); + rectangles.push(rect); + const pdfNetRect = new viewer.Core.PDFNet.Rect( + rect.topLeft.x, + rect.topLeft.y, + rect.topLeft.x + rect.width, + rect.topLeft.y + rect.height, + ); + const quadWords = await this._extractTextFromRect(viewer, page, pdfNetRect); + words.push(...quadWords); + } + + return { + text: words.join(' '), + positions: rectangles, + }; + } + + private async _extractTextFromRect(viewer: WebViewerInstance, page: Core.PDFNet.Page, rect: Core.PDFNet.Rect) { + const txt = await viewer.Core.PDFNet.TextExtractor.create(); + txt.begin(page, rect); // Read the page. + + const words = []; + // Extract words one by one. + let line = await txt.getFirstLine(); + for (; await line.isValid(); line = await line.getNextLine()) { + for (let word = await line.getFirstWord(); await word.isValid(); word = await word.getNextWord()) { + words.push(await word.getString()); + } + } + return words; + } + + acceptResize( + $event: MouseEvent, + viewer: WebViewerInstance, + annotationWrapper: AnnotationWrapper, + annotationsChanged: EventEmitter, + ) { + this._dialogService.openDialog('resizeAnnotation', $event, null, async (result: { comment: string }) => { + const textAndPositions = await this._extractTextAndPositions(viewer, annotationWrapper.id); + const text = + annotationWrapper.value === 'Rectangle' ? 'Rectangle' : annotationWrapper.isImage ? 'Image' : textAndPositions.text; + + const resizeRequest: IResizeRequest = { + annotationId: annotationWrapper.id, + comment: result.comment, + positions: textAndPositions.positions, + value: text, + }; + + console.log(resizeRequest); + + this._processObsAndEmit( + this._manualAnnotationService.resizeOrSuggestToResize(annotationWrapper, resizeRequest), + annotationWrapper, + annotationsChanged, + ); + }); + } + + cancelResize( + $event: MouseEvent, + viewer: WebViewerInstance, + annotationWrapper: AnnotationWrapper, + annotationsChanged: EventEmitter, + ) { + $event?.stopPropagation(); + + annotationWrapper.resizing = false; + + const annotationManager = viewer.Core.annotationManager; + const viewerAnnotation = annotationManager.getAnnotationById(annotationWrapper.id); + viewerAnnotation.ReadOnly = false; + annotationManager.redrawAnnotation(viewerAnnotation); + annotationManager.deselectAllAnnotations(); + annotationsChanged.emit(annotationWrapper); + } } diff --git a/apps/red-ui/src/app/modules/dossier/services/annotation-draw.service.ts b/apps/red-ui/src/app/modules/dossier/services/annotation-draw.service.ts index f87dda6db..836b73c67 100644 --- a/apps/red-ui/src/app/modules/dossier/services/annotation-draw.service.ts +++ b/apps/red-ui/src/app/modules/dossier/services/annotation-draw.service.ts @@ -141,32 +141,50 @@ export class AnnotationDrawService { compareMode = false, ) { const pageNumber = compareMode ? annotationWrapper.pageNumber * 2 - 1 : annotationWrapper.pageNumber; - const highlight = new activeViewer.Core.Annotations.TextHighlightAnnotation(); - highlight.PageNumber = pageNumber; - highlight.StrokeColor = this.getColor(activeViewer, annotationWrapper.superType, annotationWrapper.type); - highlight.setContents(annotationWrapper.content); - highlight.Quads = this._rectanglesToQuads(annotationWrapper.positions, activeViewer, pageNumber); - highlight.Id = annotationWrapper.id; - highlight.ReadOnly = true; + + let annotation; + if (annotationWrapper.value === 'Rectangle' || annotationWrapper.isImage) { + annotation = new activeViewer.Core.Annotations.RectangleAnnotation(); + const pageHeight = activeViewer.Core.documentViewer.getPageHeight(pageNumber); + const firstPosition = annotationWrapper.positions[0]; + annotation.X = firstPosition.topLeft.x; + annotation.Y = pageHeight - (firstPosition.topLeft.y + firstPosition.height); + annotation.Width = firstPosition.width; + annotation.FillColor = this.getColor(activeViewer, annotationWrapper.superType, annotationWrapper.type); + annotation.Opacity = annotationWrapper.isChangeLogRemoved ? 0.2 : 0.6; + annotation.Height = firstPosition.height; + annotation.Intensity = 100; + } else { + annotation = new activeViewer.Core.Annotations.TextHighlightAnnotation(); + annotation.Quads = this._rectanglesToQuads(annotationWrapper.positions, activeViewer, pageNumber); + annotation.Opacity = annotationWrapper.isChangeLogRemoved ? 0.2 : 1; + } + + annotation.setContents(annotationWrapper.content); + + annotation.PageNumber = pageNumber; + annotation.StrokeColor = this.getColor(activeViewer, annotationWrapper.superType, annotationWrapper.type); + annotation.Id = annotationWrapper.id; + annotation.ReadOnly = true; // change log entries are drawn lighter - highlight.Opacity = annotationWrapper.isChangeLogRemoved ? 0.2 : 1; - highlight.Hidden = + + annotation.Hidden = annotationWrapper.isChangeLogRemoved || (hideSkipped && annotationWrapper.isSkipped) || annotationWrapper.isOCR || annotationWrapper.hidden; - highlight.setCustomData('redacto-manager', 'true'); - highlight.setCustomData('redaction', String(annotationWrapper.isRedacted)); - highlight.setCustomData('skipped', String(annotationWrapper.isSkipped)); - highlight.setCustomData('changeLog', String(annotationWrapper.isChangeLogEntry)); - highlight.setCustomData('changeLogRemoved', String(annotationWrapper.isChangeLogRemoved)); - highlight.setCustomData('redactionColor', String(this.getColor(activeViewer, 'redaction', 'redaction'))); - highlight.setCustomData( + annotation.setCustomData('redacto-manager', 'true'); + annotation.setCustomData('redaction', String(annotationWrapper.isRedacted)); + annotation.setCustomData('skipped', String(annotationWrapper.isSkipped)); + annotation.setCustomData('changeLog', String(annotationWrapper.isChangeLogEntry)); + annotation.setCustomData('changeLogRemoved', String(annotationWrapper.isChangeLogRemoved)); + annotation.setCustomData('redactionColor', String(this.getColor(activeViewer, 'redaction', 'redaction'))); + annotation.setCustomData( 'annotationColor', String(this.getColor(activeViewer, annotationWrapper.superType, annotationWrapper.type)), ); - return highlight; + return annotation; } private _rectanglesToQuads(positions: IRectangle[], activeViewer: WebViewerInstance, pageNumber: number): any[] { diff --git a/apps/red-ui/src/app/modules/dossier/services/dossiers-dialog.service.ts b/apps/red-ui/src/app/modules/dossier/services/dossiers-dialog.service.ts index 331349a4c..4e14a62ba 100644 --- a/apps/red-ui/src/app/modules/dossier/services/dossiers-dialog.service.ts +++ b/apps/red-ui/src/app/modules/dossier/services/dossiers-dialog.service.ts @@ -10,6 +10,7 @@ import { AssignReviewerApproverDialogComponent } from '../dialogs/assign-reviewe import { ChangeLegalBasisDialogComponent } from '../dialogs/change-legal-basis-dialog/change-legal-basis-dialog.component'; import { RecategorizeImageDialogComponent } from '../dialogs/recategorize-image-dialog/recategorize-image-dialog.component'; import { ConfirmationDialogComponent, DialogConfig, DialogService, largeDialogConfig } from '@iqser/common-ui'; +import { ResizeAnnotationDialogComponent } from '../dialogs/resize-annotation-dialog/resize-annotation-dialog.component'; type DialogType = | 'confirm' @@ -20,6 +21,7 @@ type DialogType = | 'recategorizeImage' | 'changeLegalBasis' | 'removeAnnotations' + | 'resizeAnnotation' | 'forceRedaction' | 'manualAnnotation'; @@ -53,6 +55,9 @@ export class DossiersDialogService extends DialogService { removeAnnotations: { component: RemoveAnnotationsDialogComponent, }, + resizeAnnotation: { + component: ResizeAnnotationDialogComponent, + }, forceRedaction: { component: ForceRedactionDialogComponent, }, diff --git a/apps/red-ui/src/app/modules/dossier/services/manual-annotation.service.ts b/apps/red-ui/src/app/modules/dossier/services/manual-annotation.service.ts index 991478045..91bdd9a49 100644 --- a/apps/red-ui/src/app/modules/dossier/services/manual-annotation.service.ts +++ b/apps/red-ui/src/app/modules/dossier/services/manual-annotation.service.ts @@ -7,6 +7,7 @@ import { ILegalBasisChangeRequest, IManualAddResponse, IRemoveRedactionRequest, + IResizeRequest, } from '@red/domain'; import { AnnotationWrapper } from '@models/file/annotation.wrapper'; import { CONFLICT_ERROR_CODE, ErrorMessageService, GenericService, RequiredParam, Toaster, Validate } from '@iqser/common-ui'; @@ -51,6 +52,8 @@ export class ManualAnnotationService extends GenericService suggest: 'requestAddRedaction', 'force-redaction': 'forceRedaction', 'request-force-redaction': 'requestForceRedaction', + resize: 'resize', + 'request-resize': 'requestResize', }; } @@ -162,6 +165,14 @@ export class ManualAnnotationService extends GenericService return this._makeRequest(mode, annotationWrapper.id, null, annotationWrapper.isModifyDictionary); } + // this wraps + // /manualRedaction/redaction/resize/ + // /manualRedaction/request/resize/ + resizeOrSuggestToResize(annotationWrapper: AnnotationWrapper, resizeRequest: IResizeRequest) { + const mode: AnnotationActionMode = this._permissionsService.isApprover() ? 'resize' : 'request-resize'; + return this._makeRequest(mode, resizeRequest); + } + // this wraps // /manualRedaction/redaction/remove/ // /manualRedaction/request/remove/ @@ -322,6 +333,18 @@ export class ManualAnnotationService extends GenericService return this._post(body, url); } + @Validate() + resize(@RequiredParam() body: IResizeRequest, @RequiredParam() dossierId: string, @RequiredParam() fileId: string) { + const url = `${this._defaultModelPath}/redaction/resize/${dossierId}/${fileId}`; + return this._post(body, url); + } + + @Validate() + requestResize(@RequiredParam() body: IResizeRequest, @RequiredParam() dossierId: string, @RequiredParam() fileId: string) { + const url = `${this._defaultModelPath}/request/resize/${dossierId}/${fileId}`; + return this._post(body, url); + } + private _getMessage(mode: AnnotationActionMode, modifyDictionary?: boolean, error = false, isConflict = false) { const type = modifyDictionary ? 'dictionary' : 'manual-redaction'; const resultType = error ? (isConflict ? 'conflictError' : 'error') : 'success'; diff --git a/apps/red-ui/src/app/modules/dossier/utils/pdf-calculation.utils.ts b/apps/red-ui/src/app/modules/dossier/utils/pdf-calculation.utils.ts new file mode 100644 index 000000000..7d53d0a12 --- /dev/null +++ b/apps/red-ui/src/app/modules/dossier/utils/pdf-calculation.utils.ts @@ -0,0 +1,18 @@ +import { IRectangle } from '@red/domain'; + +export const toPosition = ( + page: number, + pageHeight: number, + selectedQuad: { x1: number; x2: number; x3: number; x4: number; y4: number; y2: number }, +): IRectangle => { + const height = selectedQuad.y2 - selectedQuad.y4; + return { + page: page, + topLeft: { + x: Math.min(selectedQuad.x3, selectedQuad.x4, selectedQuad.x2, selectedQuad.x1), + y: pageHeight - (selectedQuad.y4 + height), + }, + height: height, + width: Math.max(4, Math.abs(selectedQuad.x3 - selectedQuad.x4), Math.abs(selectedQuad.x3 - selectedQuad.x1)), + }; +}; diff --git a/apps/red-ui/src/app/modules/dossier/utils/pdf-viewer.utils.ts b/apps/red-ui/src/app/modules/dossier/utils/pdf-viewer.utils.ts index 224d58bf2..1146630af 100644 --- a/apps/red-ui/src/app/modules/dossier/utils/pdf-viewer.utils.ts +++ b/apps/red-ui/src/app/modules/dossier/utils/pdf-viewer.utils.ts @@ -1,4 +1,4 @@ -import { IRectangle, ViewMode } from '@red/domain'; +import { ViewMode } from '@red/domain'; import { translateQuads } from '@utils/pdf-coordinates'; import { AnnotationWrapper } from '@models/file/annotation.wrapper'; import { Core, WebViewerInstance } from '@pdftron/webviewer'; @@ -120,20 +120,6 @@ export class PdfViewerUtils { return translateQuads(page, rotation, quads); } - toPosition(page: number, selectedQuad: { x1: number; x2: number; x3: number; x4: number; y4: number; y2: number }): IRectangle { - const pageHeight = this._documentViewer.getPageHeight(page); - const height = selectedQuad.y2 - selectedQuad.y4; - return { - page: page, - topLeft: { - x: Math.min(selectedQuad.x3, selectedQuad.x4, selectedQuad.x2, selectedQuad.x1), - y: pageHeight - (selectedQuad.y4 + height), - }, - height: height, - width: Math.max(4, Math.abs(selectedQuad.x3 - selectedQuad.x4), Math.abs(selectedQuad.x3 - selectedQuad.x1)), - }; - } - deselectAllAnnotations() { this._annotationManager.deselectAllAnnotations(); } diff --git a/apps/red-ui/src/app/modules/icons/icons.module.ts b/apps/red-ui/src/app/modules/icons/icons.module.ts index 4b98ecc4e..94a522966 100644 --- a/apps/red-ui/src/app/modules/icons/icons.module.ts +++ b/apps/red-ui/src/app/modules/icons/icons.module.ts @@ -52,6 +52,7 @@ export class IconsModule { 'reason', 'remove-from-dict', 'report', + 'resize', 'rule', 'secret', 'status', diff --git a/apps/red-ui/src/assets/i18n/en.json b/apps/red-ui/src/assets/i18n/en.json index b86ac2971..d5758f534 100644 --- a/apps/red-ui/src/assets/i18n/en.json +++ b/apps/red-ui/src/assets/i18n/en.json @@ -138,6 +138,15 @@ }, "annotation": "Annotation", "annotation-actions": { + "resize": { + "label": "Resize" + }, + "resize-accept": { + "label": "Save Resize" + }, + "resize-cancel": { + "label": "Abort Resize" + }, "accept-recommendation": { "label": "Accept Recommendation" }, @@ -341,6 +350,16 @@ "logout": "Logout" }, "by": "by", + "resize-annotation-dialog": { + "actions": { + "cancel": "Cancel", + "save": "Save Changes" + }, + "content": { + "comment": "Comment" + }, + "header": "Resize Redaction" + }, "change-legal-basis-dialog": { "actions": { "cancel": "Cancel", diff --git a/apps/red-ui/src/assets/icons/general/resize.svg b/apps/red-ui/src/assets/icons/general/resize.svg new file mode 100644 index 000000000..6f425dfc7 --- /dev/null +++ b/apps/red-ui/src/assets/icons/general/resize.svg @@ -0,0 +1,27 @@ + + + status + + + + + + + + + + + + + + + + + + diff --git a/libs/red-domain/src/lib/redaction-log/index.ts b/libs/red-domain/src/lib/redaction-log/index.ts index eef5f3805..e2abbc277 100644 --- a/libs/red-domain/src/lib/redaction-log/index.ts +++ b/libs/red-domain/src/lib/redaction-log/index.ts @@ -9,3 +9,4 @@ export * from './remove-redaction.request'; export * from './manual-add.response'; export * from './approve-request'; export * from './image-recategorization.request'; +export * from './resize.request'; diff --git a/libs/red-domain/src/lib/redaction-log/resize.request.ts b/libs/red-domain/src/lib/redaction-log/resize.request.ts new file mode 100644 index 000000000..faf9331f8 --- /dev/null +++ b/libs/red-domain/src/lib/redaction-log/resize.request.ts @@ -0,0 +1,8 @@ +import { IRectangle } from '../geometry'; + +export interface IResizeRequest { + annotationId: string; + comment: string; + positions: IRectangle[]; + value: string; +}