RED-2279 RED-2280 - Resize Action
This commit is contained in:
parent
b96ddffb6a
commit
b4bdbe4ee4
@ -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();
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -45,6 +45,7 @@ export class AnnotationWrapper {
|
||||
recommendationType: string;
|
||||
legalBasisValue: string;
|
||||
legalBasisChangeValue?: string;
|
||||
resizing?: boolean;
|
||||
|
||||
manual?: boolean;
|
||||
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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('-');
|
||||
|
||||
@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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>
|
||||
@ -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,
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -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,
|
||||
|
||||
@ -11,4 +11,6 @@ export type AnnotationActionMode =
|
||||
| 'suggest'
|
||||
| 'undo'
|
||||
| 'force-redaction'
|
||||
| 'request-force-redaction';
|
||||
| 'request-force-redaction'
|
||||
| 'resize'
|
||||
| 'request-resize';
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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[] {
|
||||
|
||||
@ -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,
|
||||
},
|
||||
|
||||
@ -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';
|
||||
|
||||
@ -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)),
|
||||
};
|
||||
};
|
||||
@ -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();
|
||||
}
|
||||
|
||||
@ -52,6 +52,7 @@ export class IconsModule {
|
||||
'reason',
|
||||
'remove-from-dict',
|
||||
'report',
|
||||
'resize',
|
||||
'rule',
|
||||
'secret',
|
||||
'status',
|
||||
|
||||
@ -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",
|
||||
|
||||
27
apps/red-ui/src/assets/icons/general/resize.svg
Normal file
27
apps/red-ui/src/assets/icons/general/resize.svg
Normal 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 |
@ -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';
|
||||
|
||||
8
libs/red-domain/src/lib/redaction-log/resize.request.ts
Normal file
8
libs/red-domain/src/lib/redaction-log/resize.request.ts
Normal file
@ -0,0 +1,8 @@
|
||||
import { IRectangle } from '../geometry';
|
||||
|
||||
export interface IResizeRequest {
|
||||
annotationId: string;
|
||||
comment: string;
|
||||
positions: IRectangle[];
|
||||
value: string;
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user