RED-2279 RED-2280 - Resize Action

This commit is contained in:
Timo Bejan 2021-11-09 22:56:06 +02:00
parent b96ddffb6a
commit b4bdbe4ee4
23 changed files with 520 additions and 135 deletions

View File

@ -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();

View File

@ -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;

View File

@ -45,6 +45,7 @@ export class AnnotationWrapper {
recommendationType: string;
legalBasisValue: string;
legalBasisChangeValue?: string;
resizing?: boolean;
manual?: boolean;

View File

@ -1,109 +1,142 @@
<div *ngIf="canPerformAnnotationActions" [class.always-visible]="alwaysVisible" class="annotation-actions">
<iqser-circle-button
(action)="annotationActionsService.changeLegalBasis($event, annotations, annotationsChanged)"
*ngIf="annotationPermissions.canChangeLegalBasis"
[tooltipPosition]="tooltipPosition"
[tooltip]="'annotation-actions.edit-reason.label' | translate"
[type]="buttonType"
icon="iqser:edit"
></iqser-circle-button>
<!-- Resize Mode for annotation -> only resize accept and deny actions are available-->
<ng-container *ngIf="resizing">
<iqser-circle-button
(action)="acceptResize($event)"
*ngIf="annotationPermissions.canResizeAnnotation && annotations.length === 1 && annotations[0].resizing"
[tooltipPosition]="tooltipPosition"
[tooltip]="'annotation-actions.resize-accept.label' | translate"
[type]="buttonType"
icon="iqser:check"
></iqser-circle-button>
<iqser-circle-button
(action)="annotationActionsService.convertRecommendationToAnnotation($event, annotations, annotationsChanged)"
*ngIf="annotationPermissions.canAcceptRecommendation"
[tooltipPosition]="tooltipPosition"
[tooltip]="'annotation-actions.accept-recommendation.label' | translate"
[type]="buttonType"
icon="iqser:check"
></iqser-circle-button>
<iqser-circle-button
(action)="cancelResize($event)"
*ngIf="annotationPermissions.canResizeAnnotation && annotations.length === 1 && annotations[0].resizing"
[tooltipPosition]="tooltipPosition"
[tooltip]="'annotation-actions.resize-cancel.label' | translate"
[type]="buttonType"
icon="iqser:close"
></iqser-circle-button>
</ng-container>
<iqser-circle-button
(action)="annotationActionsService.acceptSuggestion($event, annotations, annotationsChanged)"
*ngIf="annotationPermissions.canAcceptSuggestion"
[tooltipPosition]="tooltipPosition"
[tooltip]="'annotation-actions.accept-suggestion.label' | translate"
[type]="buttonType"
icon="iqser:check"
></iqser-circle-button>
<!-- Not resizing - standard actions -->
<ng-container *ngIf="!resizing">
<iqser-circle-button
(action)="resize($event)"
*ngIf="annotationPermissions.canResizeAnnotation && annotations.length === 1"
[tooltipPosition]="tooltipPosition"
[tooltip]="'annotation-actions.resize.label' | translate"
[type]="buttonType"
icon="red:resize"
></iqser-circle-button>
<iqser-circle-button
(action)="annotationActionsService.undoDirectAction($event, annotations, annotationsChanged)"
*ngIf="annotationPermissions.canUndo"
[tooltipPosition]="tooltipPosition"
[tooltip]="'annotation-actions.undo' | translate"
[type]="buttonType"
icon="red:undo"
></iqser-circle-button>
<iqser-circle-button
(action)="annotationActionsService.changeLegalBasis($event, annotations, annotationsChanged)"
*ngIf="annotationPermissions.canChangeLegalBasis"
[tooltipPosition]="tooltipPosition"
[tooltip]="'annotation-actions.edit-reason.label' | translate"
[type]="buttonType"
icon="iqser:edit"
></iqser-circle-button>
<iqser-circle-button
(action)="annotationActionsService.rejectSuggestion($event, annotations, annotationsChanged)"
*ngIf="annotationPermissions.canRejectSuggestion"
[tooltipPosition]="tooltipPosition"
[tooltip]="'annotation-actions.reject-suggestion' | translate"
[type]="buttonType"
icon="iqser:close"
></iqser-circle-button>
<iqser-circle-button
(action)="annotationActionsService.convertRecommendationToAnnotation($event, annotations, annotationsChanged)"
*ngIf="annotationPermissions.canAcceptRecommendation"
[tooltipPosition]="tooltipPosition"
[tooltip]="'annotation-actions.accept-recommendation.label' | translate"
[type]="buttonType"
icon="iqser:check"
></iqser-circle-button>
<iqser-circle-button
(action)="annotationActionsService.recategorizeImages($event, annotations, annotationsChanged)"
*ngIf="annotationPermissions.canRecategorizeImage"
[tooltipPosition]="tooltipPosition"
[tooltip]="'annotation-actions.recategorize-image' | translate"
[type]="buttonType"
icon="red:thumb-down"
></iqser-circle-button>
<iqser-circle-button
(action)="annotationActionsService.acceptSuggestion($event, annotations, annotationsChanged)"
*ngIf="annotationPermissions.canAcceptSuggestion"
[tooltipPosition]="tooltipPosition"
[tooltip]="'annotation-actions.accept-suggestion.label' | translate"
[type]="buttonType"
icon="iqser:check"
></iqser-circle-button>
<iqser-circle-button
(action)="annotationActionsService.forceRedaction($event, annotations, annotationsChanged)"
*ngIf="annotationPermissions.canForceRedaction"
[tooltipPosition]="tooltipPosition"
[tooltip]="'annotation-actions.force-redaction.label' | translate"
[type]="buttonType"
icon="red:thumb-up"
></iqser-circle-button>
<iqser-circle-button
(action)="annotationActionsService.undoDirectAction($event, annotations, annotationsChanged)"
*ngIf="annotationPermissions.canUndo"
[tooltipPosition]="tooltipPosition"
[tooltip]="'annotation-actions.undo' | translate"
[type]="buttonType"
icon="red:undo"
></iqser-circle-button>
<iqser-circle-button
(action)="hideAnnotation($event)"
*ngIf="isImage && isVisible"
[tooltipPosition]="tooltipPosition"
[tooltip]="'annotation-actions.hide' | translate"
[type]="buttonType"
icon="red:visibility-off"
></iqser-circle-button>
<iqser-circle-button
(action)="annotationActionsService.rejectSuggestion($event, annotations, annotationsChanged)"
*ngIf="annotationPermissions.canRejectSuggestion"
[tooltipPosition]="tooltipPosition"
[tooltip]="'annotation-actions.reject-suggestion' | translate"
[type]="buttonType"
icon="iqser:close"
></iqser-circle-button>
<iqser-circle-button
(action)="showAnnotation($event)"
*ngIf="isImage && !isVisible"
[tooltipPosition]="tooltipPosition"
[tooltip]="'annotation-actions.show' | translate"
[type]="buttonType"
icon="red:visibility"
></iqser-circle-button>
<iqser-circle-button
(action)="annotationActionsService.recategorizeImages($event, annotations, annotationsChanged)"
*ngIf="annotationPermissions.canRecategorizeImage"
[tooltipPosition]="tooltipPosition"
[tooltip]="'annotation-actions.recategorize-image' | translate"
[type]="buttonType"
icon="red:thumb-down"
></iqser-circle-button>
<iqser-circle-button
(action)="suggestRemoveAnnotations($event, true)"
*ngIf="annotationPermissions.canRemoveOrSuggestToRemoveFromDictionary"
[tooltipPosition]="tooltipPosition"
[tooltip]="'annotation-actions.remove-annotation.remove-from-dict' | translate"
[type]="buttonType"
icon="red:remove-from-dict"
></iqser-circle-button>
<iqser-circle-button
(action)="annotationActionsService.forceRedaction($event, annotations, annotationsChanged)"
*ngIf="annotationPermissions.canForceRedaction"
[tooltipPosition]="tooltipPosition"
[tooltip]="'annotation-actions.force-redaction.label' | translate"
[type]="buttonType"
icon="red:thumb-up"
></iqser-circle-button>
<iqser-circle-button
(action)="markAsFalsePositive($event)"
*ngIf="annotationPermissions.canMarkAsFalsePositive"
[tooltipPosition]="tooltipPosition"
[tooltip]="'annotation-actions.remove-annotation.false-positive' | translate"
[type]="buttonType"
icon="red:thumb-down"
></iqser-circle-button>
<iqser-circle-button
(action)="hideAnnotation($event)"
*ngIf="isImage && isVisible"
[tooltipPosition]="tooltipPosition"
[tooltip]="'annotation-actions.hide' | translate"
[type]="buttonType"
icon="red:visibility-off"
></iqser-circle-button>
<iqser-circle-button
(action)="suggestRemoveAnnotations($event, false)"
*ngIf="annotationPermissions.canRemoveOrSuggestToRemoveOnlyHere"
[tooltipPosition]="tooltipPosition"
[tooltip]="'annotation-actions.remove-annotation.only-here' | translate"
[type]="buttonType"
icon="iqser:trash"
></iqser-circle-button>
<iqser-circle-button
(action)="showAnnotation($event)"
*ngIf="isImage && !isVisible"
[tooltipPosition]="tooltipPosition"
[tooltip]="'annotation-actions.show' | translate"
[type]="buttonType"
icon="red:visibility"
></iqser-circle-button>
<iqser-circle-button
(action)="suggestRemoveAnnotations($event, true)"
*ngIf="annotationPermissions.canRemoveOrSuggestToRemoveFromDictionary"
[tooltipPosition]="tooltipPosition"
[tooltip]="'annotation-actions.remove-annotation.remove-from-dict' | translate"
[type]="buttonType"
icon="red:remove-from-dict"
></iqser-circle-button>
<iqser-circle-button
(action)="markAsFalsePositive($event)"
*ngIf="annotationPermissions.canMarkAsFalsePositive"
[tooltipPosition]="tooltipPosition"
[tooltip]="'annotation-actions.remove-annotation.false-positive' | translate"
[type]="buttonType"
icon="red:thumb-down"
></iqser-circle-button>
<iqser-circle-button
(action)="suggestRemoveAnnotations($event, false)"
*ngIf="annotationPermissions.canRemoveOrSuggestToRemoveOnlyHere"
[tooltipPosition]="tooltipPosition"
[tooltip]="'annotation-actions.remove-annotation.only-here' | translate"
[type]="buttonType"
icon="iqser:trash"
></iqser-circle-button>
</ng-container>
</div>

View File

@ -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);
}
}

View File

@ -40,8 +40,9 @@ export class PageExclusionComponent implements OnChanges {
}, []);
}
async excludePagesRange(value: string): Promise<void> {
async excludePagesRange(inputValue: string): Promise<void> {
this._loadingService.start();
const value = inputValue.replace(/[^0-9-,]/g, '');
try {
const pageRanges = value.split(',').map(range => {
const splitted = range.split('-');

View File

@ -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));
}
}

View File

@ -0,0 +1,21 @@
<section class="dialog">
<form (submit)="save()" [formGroup]="resizeForm">
<div class="dialog-header heading-l" translate="resize-annotation-dialog.header"></div>
<div class="dialog-content">
<div [class.required]="!isDocumentAdmin" class="iqser-input-group w-300">
<label translate="resize-annotation-dialog.content.comment"></label>
<textarea formControlName="comment" iqserHasScrollbar name="comment" rows="4" type="text"></textarea>
</div>
</div>
<div class="dialog-actions">
<button [disabled]="!resizeForm.valid" color="primary" mat-flat-button type="submit">
{{ 'resize-annotation-dialog.actions.save' | translate }}
</button>
<div class="all-caps-label cancel" mat-dialog-close translate="resize-annotation-dialog.actions.cancel"></div>
</div>
</form>
<iqser-circle-button class="dialog-close" icon="iqser:close" mat-dialog-close></iqser-circle-button>
</section>

View File

@ -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<ResizeAnnotationDialogComponent>,
) {}
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,
});
}
}

View File

@ -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,

View File

@ -11,4 +11,6 @@ export type AnnotationActionMode =
| 'suggest'
| 'undo'
| 'force-redaction'
| 'request-force-redaction';
| 'request-force-redaction'
| 'resize'
| 'request-resize';

View File

@ -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<AnnotationWrapper>) {
@ -123,6 +128,7 @@ export class AnnotationActionsService {
}
getViewerAvailableActions(
viewer: WebViewerInstance,
annotations: AnnotationWrapper[],
annotationsChanged: EventEmitter<AnnotationWrapper>,
): Record<string, unknown>[] {
@ -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 = (<Core.Annotations.TextHighlightAnnotation>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<AnnotationWrapper>,
) {
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<AnnotationWrapper>,
) {
$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);
}
}

View File

@ -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[] {

View File

@ -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<DialogType> {
removeAnnotations: {
component: RemoveAnnotationsDialogComponent,
},
resizeAnnotation: {
component: ResizeAnnotationDialogComponent,
},
forceRedaction: {
component: ForceRedactionDialogComponent,
},

View File

@ -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<IManualAddResponse>
suggest: 'requestAddRedaction',
'force-redaction': 'forceRedaction',
'request-force-redaction': 'requestForceRedaction',
resize: 'resize',
'request-resize': 'requestResize',
};
}
@ -162,6 +165,14 @@ export class ManualAnnotationService extends GenericService<IManualAddResponse>
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<IManualAddResponse>
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';

View File

@ -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)),
};
};

View File

@ -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();
}

View File

@ -52,6 +52,7 @@ export class IconsModule {
'reason',
'remove-from-dict',
'report',
'resize',
'rule',
'secret',
'status',

View File

@ -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",

View File

@ -0,0 +1,27 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="14px" height="14px" viewBox="0 0 14 14" version="1.1" xmlns="http://www.w3.org/2000/svg">
<title>status</title>
<g id="Styleguide" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="Styleguide-Actions" transform="translate(-979.000000, -630.000000)" fill="currentColor">
<g id="reference" transform="translate(969.000000, 620.000000)">
<g id="status" transform="translate(10.000000, 10.000000)">
<path
d="M1.4,9.8 L1.4,12.6 L4.2,12.6 L4.2,14 L0,14 L0,9.8 L1.4,9.8 Z M14,9.8 L14,14 L9.8,14 L9.8,12.6 L12.6,12.6 L12.6,9.8 L14,9.8 Z M4.2,0 L4.2,1.4 L1.4,1.4 L1.4,4.2 L0,4.2 L0,0 L4.2,0 Z M14,0 L14,4.2 L12.6,4.2 L12.6,1.4 L9.8,1.4 L9.8,0 L14,0 Z"
id="OCR" fill-rule="nonzero"></path>
<path d="M4.2,0 L0,0 L0,4.2 L4.2,4.2 L4.2,0 Z M2.8,1.4 L2.8,2.8 L1.4,2.8 L1.4,1.4 L2.8,1.4 Z" id="Path"
fill-rule="nonzero"></path>
<path d="M4.2,9.8 L0,9.8 L0,14 L4.2,14 L4.2,9.8 Z M2.8,11.2 L2.8,12.6 L1.4,12.6 L1.4,11.2 L2.8,11.2 Z" id="Path"
fill-rule="nonzero"></path>
<path d="M14,0 L9.8,0 L9.8,4.2 L14,4.2 L14,0 Z M12.6,1.4 L12.6,2.8 L11.2,2.8 L11.2,1.4 L12.6,1.4 Z" id="Path"
fill-rule="nonzero"></path>
<path d="M14,9.8 L9.8,9.8 L9.8,14 L14,14 L14,9.8 Z M12.6,11.2 L12.6,12.6 L11.2,12.6 L11.2,11.2 L12.6,11.2 Z" id="Path"
fill-rule="nonzero"></path>
<rect id="Rectangle" x="4.2" y="1.4" width="5.6" height="1.4"></rect>
<rect id="Rectangle" x="4.2" y="11.2" width="5.6" height="1.4"></rect>
<rect id="Rectangle" x="11.2" y="4.2" width="1.4" height="5.6"></rect>
<rect id="Rectangle" x="1.4" y="4.2" width="1.4" height="5.6"></rect>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

@ -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';

View File

@ -0,0 +1,8 @@
import { IRectangle } from '../geometry';
export interface IResizeRequest {
annotationId: string;
comment: string;
positions: IRectangle[];
value: string;
}