Merge branch 'RED-10034' into 'master'
RED-10034: Removed unnecessary draw call? Closes RED-10034 See merge request redactmanager/red-ui!593
This commit is contained in:
commit
fbc7dfce60
@ -1,161 +1,165 @@
|
||||
<div
|
||||
*ngIf="canPerformAnnotationActions && annotationPermissions"
|
||||
[class.always-visible]="alwaysVisible || (helpModeService.isHelpModeActive$ | async)"
|
||||
class="annotation-actions"
|
||||
>
|
||||
<!-- Resize Mode for annotation -> only resize accept and deny actions are available-->
|
||||
<ng-container *ngIf="resizing && annotationPermissions.canResizeAnnotation">
|
||||
<iqser-circle-button
|
||||
(action)="acceptResize()"
|
||||
[buttonId]="annotations.length === 1 ? 'annotation-' + annotations[0].id + '-accept_resize' : 'annotations-accept_resize'"
|
||||
[class.disabled]="!resized"
|
||||
[tooltipPosition]="tooltipPosition"
|
||||
[tooltip]="resized ? ('annotation-actions.resize-accept.label' | translate) : ''"
|
||||
[type]="buttonType"
|
||||
icon="iqser:check"
|
||||
></iqser-circle-button>
|
||||
@if (canPerformAnnotationActions && annotationPermissions()) {
|
||||
<div [class.always-visible]="alwaysVisible || (helpModeService.isHelpModeActive$ | async)" class="annotation-actions">
|
||||
<!-- Resize Mode for annotation -> only resize accept and deny actions are available-->
|
||||
<ng-container *ngIf="resizing() && annotationPermissions().canResizeAnnotation">
|
||||
<iqser-circle-button
|
||||
(action)="acceptResize()"
|
||||
[buttonId]="
|
||||
annotations().length === 1 ? 'annotation-' + annotations()[0].id + '-accept_resize' : 'annotations-accept_resize'
|
||||
"
|
||||
[class.disabled]="!resized"
|
||||
[tooltipPosition]="tooltipPosition"
|
||||
[tooltip]="resized ? ('annotation-actions.resize-accept.label' | translate) : ''"
|
||||
[type]="buttonType"
|
||||
icon="iqser:check"
|
||||
></iqser-circle-button>
|
||||
|
||||
<iqser-circle-button
|
||||
(action)="cancelResize()"
|
||||
[buttonId]="annotations.length === 1 ? 'annotation-' + annotations[0].id + '-cancel_resize' : 'annotations-cancel_resize'"
|
||||
[tooltipPosition]="tooltipPosition"
|
||||
[tooltip]="'annotation-actions.resize-cancel.label' | translate"
|
||||
[type]="buttonType"
|
||||
icon="iqser:close"
|
||||
></iqser-circle-button>
|
||||
</ng-container>
|
||||
<iqser-circle-button
|
||||
(action)="cancelResize()"
|
||||
[buttonId]="
|
||||
annotations().length === 1 ? 'annotation-' + annotations()[0].id + '-cancel_resize' : 'annotations-cancel_resize'
|
||||
"
|
||||
[tooltipPosition]="tooltipPosition"
|
||||
[tooltip]="'annotation-actions.resize-cancel.label' | translate"
|
||||
[type]="buttonType"
|
||||
icon="iqser:close"
|
||||
></iqser-circle-button>
|
||||
</ng-container>
|
||||
|
||||
<!-- Not resizing - standard actions -->
|
||||
<ng-container *ngIf="!resizing">
|
||||
<iqser-circle-button
|
||||
(action)="resize()"
|
||||
*ngIf="canResize"
|
||||
[attr.help-mode-key]="helpModeKey('resize')"
|
||||
[buttonId]="annotations.length === 1 ? 'annotation-' + annotations[0].id + '-resize' : 'annotations-resize'"
|
||||
[tooltipPosition]="tooltipPosition"
|
||||
[tooltip]="'annotation-actions.resize.label' | translate"
|
||||
[type]="buttonType"
|
||||
icon="iqser:resize"
|
||||
></iqser-circle-button>
|
||||
<!-- Not resizing - standard actions -->
|
||||
<ng-container *ngIf="!resizing()">
|
||||
<iqser-circle-button
|
||||
(action)="resize()"
|
||||
*ngIf="canResize()"
|
||||
[attr.help-mode-key]="helpModeKey('resize')"
|
||||
[buttonId]="annotations().length === 1 ? 'annotation-' + annotations()[0].id + '-resize' : 'annotations-resize'"
|
||||
[tooltipPosition]="tooltipPosition"
|
||||
[tooltip]="'annotation-actions.resize.label' | translate"
|
||||
[type]="buttonType"
|
||||
icon="iqser:resize"
|
||||
></iqser-circle-button>
|
||||
|
||||
<iqser-circle-button
|
||||
(action)="annotationActionsService.editRedaction(annotations)"
|
||||
*ngIf="canEdit"
|
||||
[attr.help-mode-key]="helpModeKey('edit')"
|
||||
[buttonId]="annotations.length === 1 ? 'annotation-' + annotations[0].id + '-edit' : 'annotations-edit'"
|
||||
[tooltipPosition]="tooltipPosition"
|
||||
[tooltip]="'annotation-actions.edit-redaction.label' | translate"
|
||||
[type]="buttonType"
|
||||
icon="iqser:edit"
|
||||
></iqser-circle-button>
|
||||
<iqser-circle-button
|
||||
(action)="annotationActionsService.editRedaction(annotations())"
|
||||
*ngIf="canEdit()"
|
||||
[attr.help-mode-key]="helpModeKey('edit')"
|
||||
[buttonId]="annotations().length === 1 ? 'annotation-' + annotations()[0].id + '-edit' : 'annotations-edit'"
|
||||
[tooltipPosition]="tooltipPosition"
|
||||
[tooltip]="'annotation-actions.edit-redaction.label' | translate"
|
||||
[type]="buttonType"
|
||||
icon="iqser:edit"
|
||||
></iqser-circle-button>
|
||||
|
||||
<iqser-circle-button
|
||||
(action)="acceptRecommendation()"
|
||||
*ngIf="canAcceptRecommendation"
|
||||
[attr.help-mode-key]="helpModeKey('accept')"
|
||||
[buttonId]="
|
||||
annotations.length === 1
|
||||
? 'annotation-' + annotations[0].id + '-accept_recommendation'
|
||||
: 'annotations-accept_recommendation'
|
||||
"
|
||||
[tooltipPosition]="tooltipPosition"
|
||||
[tooltip]="'annotation-actions.accept-recommendation.label' | translate"
|
||||
[type]="buttonType"
|
||||
icon="iqser:check"
|
||||
></iqser-circle-button>
|
||||
<iqser-circle-button
|
||||
(action)="acceptRecommendation()"
|
||||
*ngIf="canAcceptRecommendation()"
|
||||
[attr.help-mode-key]="helpModeKey('accept')"
|
||||
[buttonId]="
|
||||
annotations().length === 1
|
||||
? 'annotation-' + annotations()[0].id + '-accept_recommendation'
|
||||
: 'annotations-accept_recommendation'
|
||||
"
|
||||
[tooltipPosition]="tooltipPosition"
|
||||
[tooltip]="'annotation-actions.accept-recommendation.label' | translate"
|
||||
[type]="buttonType"
|
||||
icon="iqser:check"
|
||||
></iqser-circle-button>
|
||||
|
||||
<iqser-circle-button
|
||||
(action)="annotationActionsService.convertHighlights(annotations)"
|
||||
*ngIf="viewModeService.isEarmarks()"
|
||||
[tooltipPosition]="tooltipPosition"
|
||||
[tooltip]="'annotation-actions.convert-highlights.label' | translate"
|
||||
[type]="buttonType"
|
||||
icon="red:convert"
|
||||
></iqser-circle-button>
|
||||
<iqser-circle-button
|
||||
(action)="annotationActionsService.convertHighlights(annotations())"
|
||||
*ngIf="viewModeService.isEarmarks()"
|
||||
[tooltipPosition]="tooltipPosition"
|
||||
[tooltip]="'annotation-actions.convert-highlights.label' | translate"
|
||||
[type]="buttonType"
|
||||
icon="red:convert"
|
||||
></iqser-circle-button>
|
||||
|
||||
<iqser-circle-button
|
||||
(action)="annotationActionsService.removeHighlights(annotations)"
|
||||
*ngIf="viewModeService.isEarmarks()"
|
||||
[tooltipPosition]="tooltipPosition"
|
||||
[tooltip]="'annotation-actions.remove-highlights.label' | translate"
|
||||
[type]="buttonType"
|
||||
icon="iqser:trash"
|
||||
></iqser-circle-button>
|
||||
<iqser-circle-button
|
||||
(action)="annotationActionsService.removeHighlights(annotations())"
|
||||
*ngIf="viewModeService.isEarmarks()"
|
||||
[tooltipPosition]="tooltipPosition"
|
||||
[tooltip]="'annotation-actions.remove-highlights.label' | translate"
|
||||
[type]="buttonType"
|
||||
icon="iqser:trash"
|
||||
></iqser-circle-button>
|
||||
|
||||
<iqser-circle-button
|
||||
(action)="annotationActionsService.undoDirectAction(annotations)"
|
||||
*allow="roles.redactions.deleteManual; if: annotationPermissions.canUndo"
|
||||
[tooltipPosition]="tooltipPosition"
|
||||
[tooltip]="'annotation-actions.undo' | translate"
|
||||
[type]="buttonType"
|
||||
icon="red:undo"
|
||||
></iqser-circle-button>
|
||||
<iqser-circle-button
|
||||
(action)="annotationActionsService.undoDirectAction(annotations())"
|
||||
*allow="roles.redactions.deleteManual; if: annotationPermissions().canUndo"
|
||||
[tooltipPosition]="tooltipPosition"
|
||||
[tooltip]="'annotation-actions.undo' | translate"
|
||||
[type]="buttonType"
|
||||
icon="red:undo"
|
||||
></iqser-circle-button>
|
||||
|
||||
<iqser-circle-button
|
||||
(action)="annotationReferencesService.show(annotations[0])"
|
||||
*ngIf="multiSelectService.inactive() && annotations[0].reference.length"
|
||||
[tooltipPosition]="tooltipPosition"
|
||||
[tooltip]="'annotation-actions.see-references.label' | translate"
|
||||
[type]="buttonType"
|
||||
icon="red:reference"
|
||||
></iqser-circle-button>
|
||||
<iqser-circle-button
|
||||
(action)="annotationReferencesService.show(annotations()[0])"
|
||||
*ngIf="multiSelectService.inactive() && annotations()[0].reference.length"
|
||||
[tooltipPosition]="tooltipPosition"
|
||||
[tooltip]="'annotation-actions.see-references.label' | translate"
|
||||
[type]="buttonType"
|
||||
icon="red:reference"
|
||||
></iqser-circle-button>
|
||||
|
||||
<iqser-circle-button
|
||||
(action)="annotationActionsService.forceAnnotation(annotations)"
|
||||
*ngIf="canForceRedaction"
|
||||
[attr.help-mode-key]="isImageHint ? helpModeKey('redact') : helpModeKey('force')"
|
||||
[buttonId]="annotations.length === 1 ? 'annotation-' + annotations[0].id + '-force_redaction' : 'annotations-force_redaction'"
|
||||
[icon]="isImageHint ? 'red:pdftron-action-add-redaction' : 'iqser:thumb-up'"
|
||||
[tooltipPosition]="tooltipPosition"
|
||||
[tooltip]="
|
||||
isImageHint
|
||||
? ('annotation-actions.force-redaction.label-image-hint' | translate)
|
||||
: ('annotation-actions.force-redaction.label' | translate)
|
||||
"
|
||||
[type]="buttonType"
|
||||
></iqser-circle-button>
|
||||
<iqser-circle-button
|
||||
(action)="annotationActionsService.forceAnnotation(annotations())"
|
||||
*ngIf="canForceRedaction()"
|
||||
[attr.help-mode-key]="isImageHint() ? helpModeKey('redact') : helpModeKey('force')"
|
||||
[buttonId]="
|
||||
annotations().length === 1 ? 'annotation-' + annotations()[0].id + '-force_redaction' : 'annotations-force_redaction'
|
||||
"
|
||||
[icon]="isImageHint() ? 'red:pdftron-action-add-redaction' : 'iqser:thumb-up'"
|
||||
[tooltipPosition]="tooltipPosition"
|
||||
[tooltip]="
|
||||
isImageHint()
|
||||
? ('annotation-actions.force-redaction.label-image-hint' | translate)
|
||||
: ('annotation-actions.force-redaction.label' | translate)
|
||||
"
|
||||
[type]="buttonType"
|
||||
></iqser-circle-button>
|
||||
|
||||
<iqser-circle-button
|
||||
(action)="annotationActionsService.forceAnnotation(annotations, true)"
|
||||
*ngIf="canForceHint"
|
||||
[attr.help-mode-key]="actionsHelpModeKey"
|
||||
[buttonId]="annotations.length === 1 ? 'annotation-' + annotations[0].id + '-force_hint' : 'annotations-force_hint'"
|
||||
[tooltipPosition]="tooltipPosition"
|
||||
[tooltip]="'annotation-actions.force-hint.label' | translate"
|
||||
[type]="buttonType"
|
||||
icon="iqser:thumb-up"
|
||||
></iqser-circle-button>
|
||||
<iqser-circle-button
|
||||
(action)="annotationActionsService.forceAnnotation(annotations(), true)"
|
||||
*ngIf="canForceHint()"
|
||||
[attr.help-mode-key]="actionsHelpModeKey"
|
||||
[buttonId]="annotations().length === 1 ? 'annotation-' + annotations()[0].id + '-force_hint' : 'annotations-force_hint'"
|
||||
[tooltipPosition]="tooltipPosition"
|
||||
[tooltip]="'annotation-actions.force-hint.label' | translate"
|
||||
[type]="buttonType"
|
||||
icon="iqser:thumb-up"
|
||||
></iqser-circle-button>
|
||||
|
||||
<iqser-circle-button
|
||||
(action)="hideAnnotation()"
|
||||
*ngIf="isImage && isVisible() && !hideSkipped"
|
||||
[attr.help-mode-key]="helpModeKey('hide')"
|
||||
[buttonId]="annotations.length === 1 ? 'annotation-' + annotations[0].id + '-hide' : 'annotations-hide'"
|
||||
[tooltipPosition]="tooltipPosition"
|
||||
[tooltip]="'annotation-actions.hide' | translate"
|
||||
[type]="buttonType"
|
||||
icon="iqser:visibility-off"
|
||||
></iqser-circle-button>
|
||||
<iqser-circle-button
|
||||
(action)="hideAnnotation()"
|
||||
*ngIf="isImage() && isVisible() && !hideSkipped()"
|
||||
[attr.help-mode-key]="helpModeKey('hide')"
|
||||
[buttonId]="annotations().length === 1 ? 'annotation-' + annotations()[0].id + '-hide' : 'annotations-hide'"
|
||||
[tooltipPosition]="tooltipPosition"
|
||||
[tooltip]="'annotation-actions.hide' | translate"
|
||||
[type]="buttonType"
|
||||
icon="iqser:visibility-off"
|
||||
></iqser-circle-button>
|
||||
|
||||
<iqser-circle-button
|
||||
(action)="showAnnotation()"
|
||||
*ngIf="isImage && !isVisible() && !hideSkipped"
|
||||
[buttonId]="annotations.length === 1 ? 'annotation-' + annotations[0].id + '-show' : 'annotations-show'"
|
||||
[tooltipPosition]="tooltipPosition"
|
||||
[tooltip]="'annotation-actions.show' | translate"
|
||||
[type]="buttonType"
|
||||
icon="iqser:visibility"
|
||||
></iqser-circle-button>
|
||||
<iqser-circle-button
|
||||
(action)="showAnnotation()"
|
||||
*ngIf="isImage() && !isVisible() && !hideSkipped()"
|
||||
[buttonId]="annotations().length === 1 ? 'annotation-' + annotations()[0].id + '-show' : 'annotations-show'"
|
||||
[tooltipPosition]="tooltipPosition"
|
||||
[tooltip]="'annotation-actions.show' | translate"
|
||||
[type]="buttonType"
|
||||
icon="iqser:visibility"
|
||||
></iqser-circle-button>
|
||||
|
||||
<iqser-circle-button
|
||||
(action)="removeRedaction()"
|
||||
*ngIf="canRemoveRedaction"
|
||||
[attr.help-mode-key]="helpModeKey('remove')"
|
||||
[buttonId]="annotations.length === 1 ? 'annotation-' + annotations[0].id + '-remove' : 'annotations-remove'"
|
||||
[tooltipPosition]="tooltipPosition"
|
||||
[tooltip]="'annotation-actions.remove-annotation.remove-redaction' | translate"
|
||||
[type]="buttonType"
|
||||
icon="iqser:trash"
|
||||
></iqser-circle-button>
|
||||
</ng-container>
|
||||
</div>
|
||||
<iqser-circle-button
|
||||
(action)="removeRedaction()"
|
||||
*ngIf="canRemoveRedaction()"
|
||||
[attr.help-mode-key]="helpModeKey('remove')"
|
||||
[buttonId]="annotations().length === 1 ? 'annotation-' + annotations()[0].id + '-remove' : 'annotations-remove'"
|
||||
[tooltipPosition]="tooltipPosition"
|
||||
[tooltip]="'annotation-actions.remove-annotation.remove-redaction' | translate"
|
||||
[type]="buttonType"
|
||||
icon="iqser:trash"
|
||||
></iqser-circle-button>
|
||||
</ng-container>
|
||||
</div>
|
||||
}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { Component, computed, Input, OnChanges } from '@angular/core';
|
||||
import { Component, computed, input, Input, untracked } from '@angular/core';
|
||||
import { CircleButtonComponent, getConfig, HelpModeService, IqserAllowDirective, IqserPermissionsService } from '@iqser/common-ui';
|
||||
import { AnnotationPermissions } from '@models/file/annotation.permissions';
|
||||
import { AnnotationWrapper } from '@models/file/annotation.wrapper';
|
||||
@ -28,21 +28,77 @@ export type AnnotationButtonType = keyof typeof AnnotationButtonTypes;
|
||||
standalone: true,
|
||||
imports: [CircleButtonComponent, NgIf, TranslateModule, AsyncPipe, IqserAllowDirective],
|
||||
})
|
||||
export class AnnotationActionsComponent implements OnChanges {
|
||||
#annotations: AnnotationWrapper[] = [];
|
||||
readonly #isDocumine = getConfig().IS_DOCUMINE;
|
||||
protected _annotationId = '';
|
||||
export class AnnotationActionsComponent {
|
||||
@Input() buttonType: AnnotationButtonType = AnnotationButtonTypes.default;
|
||||
@Input() tooltipPosition: 'before' | 'above' = 'before';
|
||||
@Input() canPerformAnnotationActions: boolean;
|
||||
@Input() alwaysVisible: boolean;
|
||||
@Input() actionsHelpModeKey: string;
|
||||
readonly roles = Roles;
|
||||
annotationPermissions: AnnotationPermissions;
|
||||
isImage = true;
|
||||
readonly annotations = input.required<AnnotationWrapper[], (AnnotationWrapper | undefined)[]>({
|
||||
transform: value => value.filter(a => a !== undefined),
|
||||
});
|
||||
readonly isVisible = computed(() => {
|
||||
const hidden = this._annotationManager.hidden();
|
||||
return this.#annotations.reduce((acc, annotation) => !hidden.has(annotation.id) && acc, true);
|
||||
return this.annotations().reduce((acc, annotation) => !hidden.has(annotation.id) && acc, true);
|
||||
});
|
||||
readonly somePending = computed(() => {
|
||||
return this.annotations().some(a => a.pending);
|
||||
});
|
||||
readonly sameType = computed(() => {
|
||||
const annotations = this.annotations();
|
||||
const type = annotations[0].superType;
|
||||
return annotations.every(a => a.superType === type);
|
||||
});
|
||||
readonly resizing = computed(() => {
|
||||
return this.annotations().length === 1 && this.annotations()[0].id === this._annotationManager.resizingAnnotationId;
|
||||
});
|
||||
readonly viewerAnnotations = computed(() => {
|
||||
return this._annotationManager.get(this.annotations());
|
||||
});
|
||||
readonly annotationPermissions = computed(() =>
|
||||
AnnotationPermissions.forUser(
|
||||
this._permissionsService.isApprover(this._state.dossier()),
|
||||
this.annotations(),
|
||||
this._state.dictionaries,
|
||||
this._iqserPermissionsService,
|
||||
this._state.file().excludedFromAutomaticAnalysis,
|
||||
),
|
||||
);
|
||||
readonly hideSkipped = computed(() => this.skippedService.hideSkipped() && this.annotations().some(a => a.isSkipped));
|
||||
readonly isImageHint = computed(() => this.annotations().every(a => a.IMAGE_HINT));
|
||||
readonly isImage = computed(() => this.annotations().reduce((acc, a) => acc && a.isImage, true));
|
||||
readonly canRemoveRedaction = computed(
|
||||
() => this.annotationChangesAllowed() && this.annotationPermissions().canRemoveRedaction && this.sameType(),
|
||||
);
|
||||
readonly canForceRedaction = computed(() => this.annotationChangesAllowed() && this.annotationPermissions().canForceRedaction);
|
||||
readonly canForceHint = computed(() => this.annotationChangesAllowed() && this.annotationPermissions().canForceHint);
|
||||
readonly canAcceptRecommendation = computed(
|
||||
() => this.annotationChangesAllowed() && this.annotationPermissions().canAcceptRecommendation,
|
||||
);
|
||||
readonly #isDocumine = getConfig().IS_DOCUMINE;
|
||||
readonly annotationChangesAllowed = computed(
|
||||
() => (!this.#isDocumine || !this._state.file().excludedFromAutomaticAnalysis) && !this.somePending(),
|
||||
);
|
||||
readonly canResize = computed(
|
||||
() => this.annotationChangesAllowed() && this.annotationPermissions().canResizeAnnotation && this.annotations().length === 1,
|
||||
);
|
||||
readonly canEdit = computed(() => {
|
||||
const canEditRedactions =
|
||||
this.annotationPermissions().canChangeLegalBasis ||
|
||||
this.annotationPermissions().canRecategorizeAnnotation ||
|
||||
this.annotationPermissions().canForceHint ||
|
||||
this.annotationPermissions().canForceRedaction;
|
||||
return (
|
||||
this.annotationChangesAllowed() &&
|
||||
(this.annotations().length > 1
|
||||
? this.#isDocumine
|
||||
? this.annotationPermissions().canEditAnnotations
|
||||
: this.annotationPermissions().canEditHints ||
|
||||
this.annotationPermissions().canEditImages ||
|
||||
this.annotationPermissions().canEditAnnotations
|
||||
: canEditRedactions)
|
||||
);
|
||||
});
|
||||
|
||||
constructor(
|
||||
@ -58,137 +114,54 @@ export class AnnotationActionsComponent implements OnChanges {
|
||||
readonly annotationReferencesService: AnnotationReferencesService,
|
||||
) {}
|
||||
|
||||
get annotations(): AnnotationWrapper[] {
|
||||
return this.#annotations;
|
||||
}
|
||||
|
||||
get isImageHint(): boolean {
|
||||
return this.annotations.every(annotation => annotation.IMAGE_HINT);
|
||||
}
|
||||
|
||||
@Input()
|
||||
set annotations(annotations: AnnotationWrapper[]) {
|
||||
this.#annotations = annotations.filter(a => a !== undefined);
|
||||
this.isImage = this.#annotations?.reduce((accumulator, annotation) => annotation.isImage && accumulator, true);
|
||||
this._annotationId = this.#annotations[0]?.id;
|
||||
}
|
||||
|
||||
get canEdit(): boolean {
|
||||
const canEditRedactions =
|
||||
this.annotationPermissions.canChangeLegalBasis ||
|
||||
this.annotationPermissions.canRecategorizeAnnotation ||
|
||||
this.annotationPermissions.canForceHint ||
|
||||
this.annotationPermissions.canForceRedaction;
|
||||
return (
|
||||
this.#annotationChangesAllowed &&
|
||||
(this.annotations.length > 1
|
||||
? this.#isDocumine
|
||||
? this.annotationPermissions.canEditAnnotations
|
||||
: this.annotationPermissions.canEditHints ||
|
||||
this.annotationPermissions.canEditImages ||
|
||||
this.annotationPermissions.canEditAnnotations
|
||||
: canEditRedactions)
|
||||
);
|
||||
}
|
||||
|
||||
get canResize(): boolean {
|
||||
return this.#annotationChangesAllowed && this.annotationPermissions.canResizeAnnotation && this.annotations.length === 1;
|
||||
}
|
||||
|
||||
get canRemoveRedaction(): boolean {
|
||||
return this.#annotationChangesAllowed && this.annotationPermissions.canRemoveRedaction && this.#sameType;
|
||||
}
|
||||
|
||||
get canForceRedaction() {
|
||||
return this.#annotationChangesAllowed && this.annotationPermissions.canForceRedaction;
|
||||
}
|
||||
|
||||
get canForceHint() {
|
||||
return this.#annotationChangesAllowed && this.annotationPermissions.canForceHint;
|
||||
}
|
||||
|
||||
get canAcceptRecommendation() {
|
||||
return this.#annotationChangesAllowed && this.annotationPermissions.canAcceptRecommendation;
|
||||
}
|
||||
|
||||
get viewerAnnotations() {
|
||||
return this._annotationManager.get(this.#annotations);
|
||||
}
|
||||
|
||||
get resizing() {
|
||||
return this.#annotations?.length === 1 && this.#annotations?.[0].id === this._annotationManager.resizingAnnotationId;
|
||||
}
|
||||
|
||||
get resized() {
|
||||
get resized(): boolean {
|
||||
return this._annotationManager.annotationHasBeenResized;
|
||||
}
|
||||
|
||||
get hideSkipped() {
|
||||
return this.skippedService.hideSkipped() && this.annotations.some(a => a.isSkipped);
|
||||
async removeRedaction(): Promise<void> {
|
||||
const annotations = untracked(this.annotations);
|
||||
const permissions = untracked(this.annotationPermissions);
|
||||
await this.annotationActionsService.removeRedaction(annotations, permissions);
|
||||
}
|
||||
|
||||
get #sameType() {
|
||||
const type = this.annotations[0].superType;
|
||||
return this.annotations.every(a => a.superType === type);
|
||||
}
|
||||
|
||||
ngOnChanges(): void {
|
||||
this.#setPermissions();
|
||||
}
|
||||
|
||||
removeRedaction() {
|
||||
this.annotationActionsService.removeRedaction(this.annotations, this.annotationPermissions);
|
||||
}
|
||||
|
||||
acceptRecommendation() {
|
||||
return this.annotationActionsService.convertRecommendationToAnnotation(this.annotations);
|
||||
async acceptRecommendation(): Promise<void> {
|
||||
const annotations = untracked(this.annotations);
|
||||
await this.annotationActionsService.convertRecommendationToAnnotation(annotations);
|
||||
}
|
||||
|
||||
hideAnnotation() {
|
||||
this._annotationManager.hide(this.viewerAnnotations);
|
||||
const viewerAnnotations = untracked(this.viewerAnnotations);
|
||||
this._annotationManager.hide(viewerAnnotations);
|
||||
this._annotationManager.deselect();
|
||||
this._annotationManager.addToHidden(this.viewerAnnotations[0].Id);
|
||||
this._annotationManager.addToHidden(viewerAnnotations[0].Id);
|
||||
}
|
||||
|
||||
showAnnotation() {
|
||||
this._annotationManager.show(this.viewerAnnotations);
|
||||
const viewerAnnotations = untracked(this.viewerAnnotations);
|
||||
this._annotationManager.show(viewerAnnotations);
|
||||
this._annotationManager.deselect();
|
||||
this._annotationManager.removeFromHidden(this.viewerAnnotations[0].Id);
|
||||
this._annotationManager.removeFromHidden(viewerAnnotations[0].Id);
|
||||
}
|
||||
|
||||
resize() {
|
||||
return this.annotationActionsService.resize(this.#annotations[0]);
|
||||
const annotations = untracked(this.annotations);
|
||||
return this.annotationActionsService.resize(annotations[0]);
|
||||
}
|
||||
|
||||
acceptResize() {
|
||||
if (this.resized) {
|
||||
return this.annotationActionsService.acceptResize(this.#annotations[0], this.annotationPermissions);
|
||||
const annotations = untracked(this.annotations);
|
||||
const permissions = untracked(this.annotationPermissions);
|
||||
return this.annotationActionsService.acceptResize(annotations[0], permissions);
|
||||
}
|
||||
}
|
||||
|
||||
cancelResize() {
|
||||
return this.annotationActionsService.cancelResize(this.#annotations[0]);
|
||||
const annotations = untracked(this.annotations);
|
||||
return this.annotationActionsService.cancelResize(annotations[0]);
|
||||
}
|
||||
|
||||
helpModeKey(action: string) {
|
||||
return this.#isDocumine ? `${action}_annotation` : `${this.actionsHelpModeKey}_${action}`;
|
||||
}
|
||||
|
||||
#setPermissions() {
|
||||
this.annotationPermissions = AnnotationPermissions.forUser(
|
||||
this._permissionsService.isApprover(this._state.dossier()),
|
||||
this.#annotations,
|
||||
this._state.dictionaries,
|
||||
this._iqserPermissionsService,
|
||||
this._state.file().excludedFromAutomaticAnalysis,
|
||||
);
|
||||
}
|
||||
|
||||
get #annotationChangesAllowed() {
|
||||
return (!this.#isDocumine || !this._state.file().excludedFromAutomaticAnalysis) && !this.#somePending;
|
||||
}
|
||||
|
||||
get #somePending() {
|
||||
return this.#annotations.some(a => a.pending);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,37 +1,42 @@
|
||||
<div class="active-bar-marker"></div>
|
||||
|
||||
<div [class.removed]="annotation.item.isRemoved" class="annotation">
|
||||
<div [class.removed]="annotation().item.isRemoved" class="annotation">
|
||||
<redaction-annotation-card
|
||||
[annotation]="annotation.item"
|
||||
[isSelected]="annotation.isSelected"
|
||||
[matTooltip]="annotation.item.content.translation | translate: annotation.item.content.params | replaceNbsp"
|
||||
[annotation]="annotation().item"
|
||||
[isSelected]="annotation().isSelected"
|
||||
[matTooltip]="annotation().item.content.translation | translate: annotation().item.content.params | replaceNbsp"
|
||||
matTooltipPosition="above"
|
||||
></redaction-annotation-card>
|
||||
|
||||
<div *ngIf="!annotation.item.isEarmark" class="actions-wrapper">
|
||||
<div
|
||||
*ngIf="!annotation.item.pending"
|
||||
(click)="showComments = !showComments"
|
||||
[matTooltip]="'comments.comments' | translate: { count: annotation.item.numberOfComments }"
|
||||
class="comments-counter"
|
||||
iqserStopPropagation
|
||||
matTooltipPosition="above"
|
||||
>
|
||||
<mat-icon svgIcon="red:comment"></mat-icon>
|
||||
{{ annotation.item.numberOfComments }}
|
||||
</div>
|
||||
@if (!annotation().item.isEarmark) {
|
||||
<div class="actions-wrapper">
|
||||
@if (!annotation().item.pending) {
|
||||
<div
|
||||
(click)="showComments = !showComments"
|
||||
[matTooltip]="'comments.comments' | translate: { count: annotation().item.numberOfComments }"
|
||||
class="comments-counter"
|
||||
iqserStopPropagation
|
||||
matTooltipPosition="above"
|
||||
>
|
||||
<mat-icon svgIcon="red:comment"></mat-icon>
|
||||
{{ annotation().item.numberOfComments }}
|
||||
</div>
|
||||
}
|
||||
|
||||
<div *ngIf="multiSelectService.inactive()" class="actions">
|
||||
<redaction-annotation-actions
|
||||
[annotations]="[annotation.item]"
|
||||
[actionsHelpModeKey]="actionsHelpModeKey"
|
||||
[canPerformAnnotationActions]="pdfProxyService.canPerformActions()"
|
||||
></redaction-annotation-actions>
|
||||
@if (multiSelectService.inactive()) {
|
||||
<div class="actions">
|
||||
<redaction-annotation-actions
|
||||
[actionsHelpModeKey]="actionsHelpModeKey()"
|
||||
[annotations]="[annotation().item]"
|
||||
[canPerformAnnotationActions]="pdfProxyService.canPerformActions()"
|
||||
></redaction-annotation-actions>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
<ng-container *ngIf="showComments">
|
||||
<redaction-comments [annotation]="annotation.item"></redaction-comments>
|
||||
@if (showComments) {
|
||||
<redaction-comments [annotation]="annotation().item"></redaction-comments>
|
||||
|
||||
<div
|
||||
(click)="showComments = false"
|
||||
@ -39,7 +44,7 @@
|
||||
iqserStopPropagation
|
||||
translate="comments.hide-comments"
|
||||
></div>
|
||||
</ng-container>
|
||||
}
|
||||
</div>
|
||||
|
||||
<redaction-annotation-details [annotation]="annotation"></redaction-annotation-details>
|
||||
<redaction-annotation-details [annotation]="annotation()"></redaction-annotation-details>
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { Component, HostBinding, inject, Input, OnChanges } from '@angular/core';
|
||||
import { Component, computed, effect, HostBinding, inject, input } from '@angular/core';
|
||||
import { getConfig, StopPropagationDirective } from '@iqser/common-ui';
|
||||
import { AnnotationWrapper } from '@models/file/annotation.wrapper';
|
||||
import { ListItem } from '@models/file/list-item';
|
||||
@ -6,7 +6,6 @@ import { MultiSelectService } from '../../services/multi-select.service';
|
||||
import { PdfProxyService } from '../../services/pdf-proxy.service';
|
||||
import { ActionsHelpModeKeys } from '../../utils/constants';
|
||||
import { AnnotationCardComponent } from '../annotation-card/annotation-card.component';
|
||||
import { NgIf } from '@angular/common';
|
||||
import { MatTooltip } from '@angular/material/tooltip';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { MatIcon } from '@angular/material/icon';
|
||||
@ -22,7 +21,6 @@ import { ReplaceNbspPipe } from '@common-ui/pipes/replace-nbsp.pipe';
|
||||
standalone: true,
|
||||
imports: [
|
||||
AnnotationCardComponent,
|
||||
NgIf,
|
||||
MatTooltip,
|
||||
TranslateModule,
|
||||
StopPropagationDirective,
|
||||
@ -33,26 +31,28 @@ import { ReplaceNbspPipe } from '@common-ui/pipes/replace-nbsp.pipe';
|
||||
ReplaceNbspPipe,
|
||||
],
|
||||
})
|
||||
export class AnnotationWrapperComponent implements OnChanges {
|
||||
readonly #isDocumine = getConfig().IS_DOCUMINE;
|
||||
export class AnnotationWrapperComponent {
|
||||
readonly annotation = input.required<ListItem<AnnotationWrapper>>();
|
||||
@HostBinding('attr.annotation-id') annotationId: string;
|
||||
@HostBinding('class.active') active: boolean;
|
||||
|
||||
readonly actionsHelpModeKey = computed(() => this.#getActionsHelpModeKey(this.annotation().item));
|
||||
showComments = false;
|
||||
protected readonly pdfProxyService = inject(PdfProxyService);
|
||||
protected readonly multiSelectService = inject(MultiSelectService);
|
||||
@Input({ required: true }) annotation!: ListItem<AnnotationWrapper>;
|
||||
@HostBinding('attr.annotation-id') annotationId: string;
|
||||
@HostBinding('class.active') active = false;
|
||||
actionsHelpModeKey: string;
|
||||
showComments = false;
|
||||
readonly #isDocumine = getConfig().IS_DOCUMINE;
|
||||
|
||||
ngOnChanges() {
|
||||
this.annotationId = this.annotation.item.id;
|
||||
this.active = this.annotation.isSelected;
|
||||
this.actionsHelpModeKey = this.#getActionsHelpModeKey();
|
||||
constructor() {
|
||||
effect(() => {
|
||||
this.active = this.annotation().isSelected;
|
||||
this.annotationId = this.annotation().item.id;
|
||||
});
|
||||
}
|
||||
|
||||
#getActionsHelpModeKey(): string {
|
||||
#getActionsHelpModeKey(item: AnnotationWrapper): string {
|
||||
if (!this.#isDocumine) {
|
||||
const superType = this.annotation.item.superTypeLabel?.split('.')[1];
|
||||
const type = this.annotation.item.type;
|
||||
const superType = item.superTypeLabel?.split('.')[1];
|
||||
const type = item.type;
|
||||
if (superType === 'hint' && (type === 'ocr' || type === 'formula' || type === 'image')) {
|
||||
return ActionsHelpModeKeys[`${superType}-${type}`];
|
||||
}
|
||||
|
||||
@ -1,16 +1,16 @@
|
||||
<ng-container *ngFor="let annotation of annotations; let idx = index; trackBy: _trackBy">
|
||||
<div *ngIf="showHighlightGroup(idx) as highlightGroup" class="workload-separator">
|
||||
@for (annotation of annotations(); track annotation.item.id) {
|
||||
@if (displayedHighlightGroups()[$index]; as highlightGroup) {
|
||||
<redaction-highlights-separator [annotation]="annotation.item" [highlightGroup]="highlightGroup"></redaction-highlights-separator>
|
||||
</div>
|
||||
}
|
||||
|
||||
<redaction-annotation-wrapper
|
||||
*ngIf="!annotation.item.hiddenInWorkload"
|
||||
(click)="annotationClicked(annotation.item, $event)"
|
||||
*ngIf="!annotation.item.hiddenInWorkload"
|
||||
[annotation]="annotation"
|
||||
[id]="'annotation-' + annotation.item.id"
|
||||
[class.documine-wrapper]="isDocumine"
|
||||
[id]="'annotation-' + annotation.item.id"
|
||||
></redaction-annotation-wrapper>
|
||||
</ng-container>
|
||||
}
|
||||
|
||||
<redaction-annotation-references-list
|
||||
(referenceClicked)="referenceClicked($event)"
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { Component, computed, ElementRef, EventEmitter, Input, Output } from '@angular/core';
|
||||
import { Component, computed, ElementRef, input, output } from '@angular/core';
|
||||
import { getConfig, HasScrollbarDirective } from '@iqser/common-ui';
|
||||
import { FilterService } from '@iqser/common-ui/lib/filtering';
|
||||
import { IqserEventTarget } from '@iqser/common-ui/lib/utils';
|
||||
@ -24,13 +24,24 @@ import { AnnotationReferencesListComponent } from '../annotation-references-list
|
||||
imports: [NgForOf, NgIf, HighlightsSeparatorComponent, AnnotationWrapperComponent, AnnotationReferencesListComponent],
|
||||
})
|
||||
export class AnnotationsListComponent extends HasScrollbarDirective {
|
||||
@Input({ required: true }) annotations: ListItem<AnnotationWrapper>[];
|
||||
@Output() readonly pagesPanelActive = new EventEmitter<boolean>();
|
||||
readonly #earmarkGroups = computed(() => {
|
||||
if (this._viewModeService.isEarmarks()) {
|
||||
return this.#getEarmarksGroups();
|
||||
readonly annotations = input.required<ListItem<AnnotationWrapper>[]>();
|
||||
readonly pagesPanelActive = output<boolean>();
|
||||
readonly displayedHighlightGroups = computed(() => {
|
||||
const isEarMarks = this._viewModeService.isEarmarks();
|
||||
let result: Record<number, EarmarkGroup> = {};
|
||||
|
||||
if (isEarMarks) {
|
||||
const annotations = this.annotations();
|
||||
const earmarkGroups = this.#getEarmarksGroups(annotations);
|
||||
for (let idx = 0; idx < annotations.length; ++idx) {
|
||||
const group = earmarkGroups.find(h => h.startIdx === idx);
|
||||
if (group) {
|
||||
result[idx] = group;
|
||||
}
|
||||
}
|
||||
}
|
||||
return [] as EarmarkGroup[];
|
||||
|
||||
return result;
|
||||
});
|
||||
protected readonly isDocumine = getConfig().IS_DOCUMINE;
|
||||
|
||||
@ -77,26 +88,20 @@ export class AnnotationsListComponent extends HasScrollbarDirective {
|
||||
}
|
||||
}
|
||||
|
||||
showHighlightGroup(idx: number): EarmarkGroup {
|
||||
return this._viewModeService.isEarmarks() && this.#earmarkGroups().find(h => h.startIdx === idx);
|
||||
}
|
||||
|
||||
protected readonly _trackBy = (index: number, listItem: ListItem<AnnotationWrapper>) => listItem.item.id;
|
||||
|
||||
#getEarmarksGroups() {
|
||||
#getEarmarksGroups(annotations: ListItem<AnnotationWrapper>[]) {
|
||||
const earmarksGroups: EarmarkGroup[] = [];
|
||||
|
||||
if (!this.annotations?.length) {
|
||||
if (!annotations.length) {
|
||||
return earmarksGroups;
|
||||
}
|
||||
|
||||
let lastGroup: EarmarkGroup;
|
||||
for (let idx = 0; idx < this.annotations.length; ++idx) {
|
||||
if (idx === 0 || this.annotations[idx].item.color !== this.annotations[idx - 1].item.color) {
|
||||
for (let idx = 0; idx < annotations.length; ++idx) {
|
||||
if (idx === 0 || annotations[idx].item.color !== annotations[idx - 1].item.color) {
|
||||
if (lastGroup) {
|
||||
earmarksGroups.push(lastGroup);
|
||||
}
|
||||
lastGroup = { startIdx: idx, length: 1, color: this.annotations[idx].item.color };
|
||||
lastGroup = { startIdx: idx, length: 1, color: annotations[idx].item.color };
|
||||
} else {
|
||||
lastGroup.length += 1;
|
||||
}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { ActivatedRouteSnapshot, NavigationExtras, Router, RouterLink } from '@angular/router';
|
||||
import { ChangeDetectorRef, Component, effect, NgZone, OnDestroy, OnInit, TemplateRef, ViewChild } from '@angular/core';
|
||||
import { ChangeDetectorRef, Component, effect, NgZone, OnDestroy, OnInit, TemplateRef, untracked, ViewChild } from '@angular/core';
|
||||
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
||||
import { ComponentCanDeactivate } from '@guards/can-deactivate.guard';
|
||||
import {
|
||||
@ -100,13 +100,13 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
|
||||
readonly roles = Roles;
|
||||
readonly fileId = this.state.fileId;
|
||||
readonly dossierId = this.state.dossierId;
|
||||
protected readonly isDocumine = getConfig().IS_DOCUMINE;
|
||||
@ViewChild('annotationFilterTemplate', {
|
||||
read: TemplateRef,
|
||||
static: false,
|
||||
})
|
||||
private readonly _filterTemplate: TemplateRef<unknown>;
|
||||
#loadAllAnnotationsEnabled = false;
|
||||
protected readonly isDocumine = getConfig().IS_DOCUMINE;
|
||||
|
||||
constructor(
|
||||
readonly pdf: PdfViewer,
|
||||
@ -179,7 +179,7 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
|
||||
|
||||
effect(() => {
|
||||
this._viewModeService.viewMode();
|
||||
this.updateViewMode().then();
|
||||
this.#updateViewMode().then();
|
||||
});
|
||||
|
||||
effect(() => {
|
||||
@ -207,7 +207,7 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
|
||||
const earmarks$ = isEarmarksViewMode$.pipe(
|
||||
tap(() => this._loadingService.start()),
|
||||
switchMap(() => this._fileDataService.loadEarmarks()),
|
||||
tap(() => this.updateViewMode().then(() => this._loadingService.stop())),
|
||||
tap(() => this.#updateViewMode().then(() => this._loadingService.stop())),
|
||||
);
|
||||
|
||||
const currentPageIfEarmarksView$ = combineLatest([this.pdf.currentPage$, this._viewModeService.viewMode$]).pipe(
|
||||
@ -233,7 +233,7 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
|
||||
|
||||
return isChangingFromEarmarksViewMode$.pipe(
|
||||
map(() => this._fileDataService.earmarks().get(this.pdf.currentPage()) ?? []),
|
||||
map(earmarks => this.deleteAnnotations(earmarks, [])),
|
||||
map(earmarks => this.#deleteAnnotations(earmarks, [])),
|
||||
);
|
||||
}
|
||||
|
||||
@ -242,61 +242,6 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
|
||||
this._viewerHeaderService.disable(ROTATION_ACTION_BUTTONS);
|
||||
}
|
||||
|
||||
async updateViewMode(): Promise<void> {
|
||||
this._logger.info(`[PDF] Update ${this._viewModeService.viewMode()} view mode`);
|
||||
|
||||
const annotations = this._annotationManager.get(a => bool(a.getCustomData('redact-manager')));
|
||||
const redactions = annotations.filter(a => bool(a.getCustomData('redaction')));
|
||||
|
||||
switch (this._viewModeService.viewMode()) {
|
||||
case ViewModes.STANDARD: {
|
||||
const wrappers = this._fileDataService.annotations();
|
||||
const ocrAnnotationIds = wrappers.filter(a => a.isOCR).map(a => a.id);
|
||||
const standardEntries = annotations
|
||||
.filter(a => !bool(a.getCustomData('changeLogRemoved')) && !this._annotationManager.isHidden(a.Id))
|
||||
.filter(a => !ocrAnnotationIds.includes(a.Id));
|
||||
const nonStandardEntries = annotations.filter(
|
||||
a =>
|
||||
bool(a.getCustomData('changeLogRemoved')) ||
|
||||
this._annotationManager.isHidden(a.Id) ||
|
||||
(this._skippedService.hideSkipped() && bool(a.getCustomData('skipped'))),
|
||||
);
|
||||
this._readableRedactionsService.setAnnotationsColor(standardEntries, 'annotationColor');
|
||||
this._readableRedactionsService.setAnnotationsOpacity(standardEntries, true);
|
||||
this._annotationManager.show(standardEntries);
|
||||
this._annotationManager.hide(nonStandardEntries);
|
||||
break;
|
||||
}
|
||||
case ViewModes.DELTA: {
|
||||
const changeLogEntries = annotations.filter(a => bool(a.getCustomData('changeLog')));
|
||||
const nonChangeLogEntries = annotations.filter(a => !bool(a.getCustomData('changeLog')));
|
||||
this._readableRedactionsService.setAnnotationsColor(redactions, 'annotationColor');
|
||||
this._readableRedactionsService.setAnnotationsOpacity(changeLogEntries, true);
|
||||
this._annotationManager.show(changeLogEntries);
|
||||
this._annotationManager.hide(nonChangeLogEntries);
|
||||
break;
|
||||
}
|
||||
case ViewModes.REDACTED: {
|
||||
const nonRedactionEntries = annotations.filter(
|
||||
a => !bool(a.getCustomData('redaction')) || bool(a.getCustomData('changeLogRemoved')),
|
||||
);
|
||||
this._readableRedactionsService.setAnnotationsColor(redactions, 'redactionColor');
|
||||
this._readableRedactionsService.setAnnotationsOpacity(redactions);
|
||||
this._annotationManager.show(redactions);
|
||||
this._annotationManager.hide(nonRedactionEntries);
|
||||
|
||||
break;
|
||||
}
|
||||
case ViewModes.TEXT_HIGHLIGHTS: {
|
||||
this._annotationManager.hide(annotations);
|
||||
}
|
||||
}
|
||||
|
||||
this._logger.info('[PDF] Rebuild filters');
|
||||
this.#rebuildFilters();
|
||||
this._logger.info('[PDF] Update done');
|
||||
}
|
||||
|
||||
ngOnDetach() {
|
||||
this._viewerHeaderService.resetCompareButtons();
|
||||
this._viewerHeaderService.enableLoadAllAnnotations(); // Reset the button state (since the viewer is reused between files)
|
||||
@ -413,7 +358,9 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
|
||||
const annotations$ = this._fileDataService.annotations$.pipe(
|
||||
startWith([] as AnnotationWrapper[]),
|
||||
pairwise(),
|
||||
tap(annotations => this.deleteAnnotations(...annotations)),
|
||||
tap(annotations => this.#deleteAnnotations(...annotations)),
|
||||
tap(() => this.#updateFiltersAfterAnnotationsChanged()),
|
||||
tap(() => this.#updateViewMode()),
|
||||
);
|
||||
|
||||
const currentPageIfNotHighlightsView$ = combineLatest([this.pdf.currentPage$, this._viewModeService.viewMode$]).pipe(
|
||||
@ -432,12 +379,72 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
|
||||
return combineLatest([currentPageAnnotations$, this._documentViewer.loaded$]).pipe(
|
||||
filter(([, loaded]) => loaded),
|
||||
map(([annotations]) => annotations),
|
||||
tap(([oldA, newA]) => this.drawChangedAnnotations(oldA, newA)?.then(() => this.updateViewMode())),
|
||||
tap(([, newAnnotations]) => this.#highlightSelectedAnnotations(newAnnotations)),
|
||||
switchMap(async ([oldAnnotations, newAnnotations]) => {
|
||||
await this.#drawChangedAnnotations(oldAnnotations, newAnnotations);
|
||||
return newAnnotations;
|
||||
}),
|
||||
tap(newAnnotations => this.#highlightSelectedAnnotations(newAnnotations)),
|
||||
);
|
||||
}
|
||||
|
||||
deleteAnnotations(oldAnnotations: AnnotationWrapper[], newAnnotations: AnnotationWrapper[]) {
|
||||
async #updateViewMode(): Promise<void> {
|
||||
const viewMode = untracked(this._viewModeService.viewMode);
|
||||
this._logger.info(`[PDF] Update ${viewMode} view mode`);
|
||||
|
||||
const annotations = this._annotationManager.get(a => bool(a.getCustomData('redact-manager')));
|
||||
const redactions = annotations.filter(a => bool(a.getCustomData('redaction')));
|
||||
|
||||
switch (viewMode) {
|
||||
case ViewModes.STANDARD: {
|
||||
const wrappers = this._fileDataService.annotations();
|
||||
// TODO: const wrappers = untracked(this._fileDataService.annotations);
|
||||
const ocrAnnotationIds = wrappers.filter(a => a.isOCR).map(a => a.id);
|
||||
const standardEntries = annotations
|
||||
.filter(a => !bool(a.getCustomData('changeLogRemoved')) && !this._annotationManager.isHidden(a.Id))
|
||||
.filter(a => !ocrAnnotationIds.includes(a.Id));
|
||||
const nonStandardEntries = annotations.filter(
|
||||
a =>
|
||||
bool(a.getCustomData('changeLogRemoved')) ||
|
||||
this._annotationManager.isHidden(a.Id) ||
|
||||
(untracked(this._skippedService.hideSkipped) && bool(a.getCustomData('skipped'))),
|
||||
);
|
||||
this._readableRedactionsService.setAnnotationsColor(standardEntries, 'annotationColor');
|
||||
this._readableRedactionsService.setAnnotationsOpacity(standardEntries, true);
|
||||
this._annotationManager.show(standardEntries);
|
||||
this._annotationManager.hide(nonStandardEntries);
|
||||
break;
|
||||
}
|
||||
case ViewModes.DELTA: {
|
||||
const changeLogEntries = annotations.filter(a => bool(a.getCustomData('changeLog')));
|
||||
const nonChangeLogEntries = annotations.filter(a => !bool(a.getCustomData('changeLog')));
|
||||
this._readableRedactionsService.setAnnotationsColor(redactions, 'annotationColor');
|
||||
this._readableRedactionsService.setAnnotationsOpacity(changeLogEntries, true);
|
||||
this._annotationManager.show(changeLogEntries);
|
||||
this._annotationManager.hide(nonChangeLogEntries);
|
||||
break;
|
||||
}
|
||||
case ViewModes.REDACTED: {
|
||||
const nonRedactionEntries = annotations.filter(
|
||||
a => !bool(a.getCustomData('redaction')) || bool(a.getCustomData('changeLogRemoved')),
|
||||
);
|
||||
this._readableRedactionsService.setAnnotationsColor(redactions, 'redactionColor');
|
||||
this._readableRedactionsService.setAnnotationsOpacity(redactions);
|
||||
this._annotationManager.show(redactions);
|
||||
this._annotationManager.hide(nonRedactionEntries);
|
||||
|
||||
break;
|
||||
}
|
||||
case ViewModes.TEXT_HIGHLIGHTS: {
|
||||
this._annotationManager.hide(annotations);
|
||||
}
|
||||
}
|
||||
|
||||
this._logger.info('[PDF] Rebuild filters');
|
||||
this.#rebuildFilters();
|
||||
this._logger.info('[PDF] Update done');
|
||||
}
|
||||
|
||||
#deleteAnnotations(oldAnnotations: AnnotationWrapper[], newAnnotations: AnnotationWrapper[]) {
|
||||
const annotationsToDelete = oldAnnotations.filter(oldAnnotation => {
|
||||
const newAnnotation = newAnnotations.find(byId(oldAnnotation.id));
|
||||
return newAnnotation ? hasChanges(oldAnnotation, newAnnotation) : true;
|
||||
@ -447,11 +454,11 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
|
||||
this._annotationManager.delete(annotationsToDelete);
|
||||
}
|
||||
|
||||
drawChangedAnnotations(oldAnnotations: AnnotationWrapper[], newAnnotations: AnnotationWrapper[]) {
|
||||
async #drawChangedAnnotations(oldAnnotations: AnnotationWrapper[], newAnnotations: AnnotationWrapper[]): Promise<void> {
|
||||
const annotationsToDraw = this.#getAnnotationsToDraw(oldAnnotations, newAnnotations);
|
||||
this._logger.info('[ANNOTATIONS] To draw: ', annotationsToDraw);
|
||||
this._annotationManager.delete(annotationsToDraw);
|
||||
return this.#cleanupAndRedrawAnnotations(annotationsToDraw);
|
||||
await this.#cleanupAndRedrawAnnotations(annotationsToDraw);
|
||||
}
|
||||
|
||||
async #openRedactTextDialog(manualRedactionEntryWrapper: ManualRedactionEntryWrapper) {
|
||||
@ -642,7 +649,7 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
|
||||
filter(event => event.type === ViewerEvents.LOAD_ALL_ANNOTATIONS),
|
||||
switchMap(() => {
|
||||
// TODO: this switchMap is ugly, to be refactored
|
||||
const annotations = this._fileDataService.annotations();
|
||||
const annotations = untracked(this._fileDataService.annotations);
|
||||
const showWarning = !this.userPreferenceService.getBool(PreferencesKeys.loadAllAnnotationsWarning);
|
||||
const annotationsExceedThreshold = annotations.length >= this.configService.values.ANNOTATIONS_THRESHOLD;
|
||||
|
||||
@ -655,7 +662,7 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
|
||||
filter(([confirmed]) => confirmed),
|
||||
map(([, annotations]) => {
|
||||
this.#loadAllAnnotationsEnabled = true;
|
||||
this.drawChangedAnnotations([], annotations).then(() => {
|
||||
this.#drawChangedAnnotations([], annotations).then(() => {
|
||||
this._toaster.success(_('load-all-annotations-success'));
|
||||
this._viewerHeaderService.disableLoadAllAnnotations();
|
||||
});
|
||||
@ -663,7 +670,9 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
|
||||
)
|
||||
.subscribe();
|
||||
|
||||
this.addActiveScreenSubscription = this._readableRedactionsService.active$.pipe(map(() => this.updateViewMode())).subscribe();
|
||||
this.addActiveScreenSubscription = this._readableRedactionsService.active$
|
||||
.pipe(switchMap(() => this.#updateViewMode()))
|
||||
.subscribe();
|
||||
|
||||
this.addActiveScreenSubscription = combineLatest([this._viewModeService.viewMode$, this.state.file$, this._documentViewer.loaded$])
|
||||
.pipe(
|
||||
@ -702,11 +711,15 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
|
||||
this._errorService.set(error);
|
||||
}
|
||||
|
||||
#cleanupAndRedrawAnnotations(newAnnotations: List<AnnotationWrapper>) {
|
||||
async #cleanupAndRedrawAnnotations(newAnnotations: List<AnnotationWrapper>): Promise<void> {
|
||||
if (!newAnnotations.length) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
await this._annotationDrawService.draw(newAnnotations, this._skippedService.hideSkipped(), this.state.dossierTemplateId);
|
||||
}
|
||||
|
||||
#updateFiltersAfterAnnotationsChanged(): void {
|
||||
const currentFilters = this._filterService.getGroup('primaryFilters')?.filters || [];
|
||||
this.#rebuildFilters();
|
||||
|
||||
@ -715,8 +728,6 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
|
||||
this.#handleDeltaAnnotationFilters(currentFilters);
|
||||
}, 100);
|
||||
}
|
||||
|
||||
return this._annotationDrawService.draw(newAnnotations, this._skippedService.hideSkipped(), this.state.dossierTemplateId);
|
||||
}
|
||||
|
||||
#handleDeltaAnnotationFilters(currentFilters: NestedFilter[]) {
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
import { computed, Injectable, Signal, signal } from '@angular/core';
|
||||
import { BehaviorSubject } from 'rxjs';
|
||||
import { AnnotationWrapper } from '@models/file/annotation.wrapper';
|
||||
import { FileDataService } from './file-data.service';
|
||||
|
||||
@ -7,7 +6,6 @@ import { FileDataService } from './file-data.service';
|
||||
export class AnnotationReferencesService {
|
||||
readonly references: Signal<AnnotationWrapper[]>;
|
||||
readonly annotation: Signal<AnnotationWrapper | undefined>;
|
||||
private readonly _annotation$ = new BehaviorSubject<AnnotationWrapper | undefined>(undefined);
|
||||
readonly #annotation = signal<AnnotationWrapper | undefined>(undefined);
|
||||
|
||||
constructor(private readonly _fileDataService: FileDataService) {
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { computed, effect, inject, Injectable, NgZone } from '@angular/core';
|
||||
import { computed, effect, inject, Injectable, NgZone, untracked } from '@angular/core';
|
||||
import { getConfig, IqserPermissionsService } from '@iqser/common-ui';
|
||||
import { getCurrentUser } from '@iqser/common-ui/lib/users';
|
||||
import { isJustOne, shareDistinctLast, UI_ROOT_PATH_FN } from '@iqser/common-ui/lib/utils';
|
||||
@ -49,7 +49,7 @@ export class PdfProxyService {
|
||||
readonly redactTextRequested$ = new Subject<ManualRedactionEntryWrapper>();
|
||||
readonly currentUser = getCurrentUser<User>();
|
||||
readonly pageChanged$ = this._pdf.pageChanged$.pipe(
|
||||
tap(() => this.#handleExcludedPageActions()),
|
||||
tap(pageNumber => this.#handleExcludedPageActions(pageNumber)),
|
||||
tap(() => {
|
||||
if (this._multiSelectService.inactive()) {
|
||||
this._annotationManager.deselect();
|
||||
@ -98,7 +98,7 @@ export class PdfProxyService {
|
||||
effect(
|
||||
() => {
|
||||
const canPerformActions = this.canPerformActions();
|
||||
this._pdf.isCompareMode();
|
||||
const isCompareMode = this._pdf.isCompareMode();
|
||||
|
||||
this.#configureTextPopup();
|
||||
|
||||
@ -109,7 +109,8 @@ export class PdfProxyService {
|
||||
this.#deactivateMultiSelect();
|
||||
}
|
||||
|
||||
this.#handleExcludedPageActions();
|
||||
const currentPage = untracked(this._pdf.currentPage);
|
||||
this.#handleExcludedPageActions(currentPage);
|
||||
},
|
||||
{ allowSignalWrites: true },
|
||||
);
|
||||
@ -227,8 +228,9 @@ export class PdfProxyService {
|
||||
this.redactTextRequested$.next({ manualRedactionEntry, type });
|
||||
}
|
||||
|
||||
#handleExcludedPageActions() {
|
||||
const isCurrentPageExcluded = this._state.file().isPageExcluded(this._pdf.currentPage());
|
||||
#handleExcludedPageActions(currentPage: number) {
|
||||
const isCurrentPageExcluded = this._state.file().isPageExcluded(currentPage);
|
||||
|
||||
if (!isCurrentPageExcluded) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -2,7 +2,7 @@ import { Injectable } from '@angular/core';
|
||||
import { List } from '@iqser/common-ui/lib/utils';
|
||||
import { AnnotationWrapper } from '@models/file/annotation.wrapper';
|
||||
import { Core } from '@pdftron/webviewer';
|
||||
import { IRectangle, ISectionRectangle, IPoint, SuperTypes } from '@red/domain';
|
||||
import { IPoint, IRectangle, ISectionRectangle, SuperTypes } from '@red/domain';
|
||||
import { DefaultColorsService } from '@services/entity-services/default-colors.service';
|
||||
import { hexToRgb } from '@utils/functions';
|
||||
import { BoundingBox, Table } from '../../file-preview/services/tables.service';
|
||||
|
||||
@ -19,14 +19,14 @@ const MOVE_OPTION = 'move';
|
||||
|
||||
@Injectable()
|
||||
export class REDAnnotationManager {
|
||||
resizingAnnotationId?: string = undefined;
|
||||
annotationHasBeenResized?: boolean = false;
|
||||
readonly #hidden = signal(new Set<string>());
|
||||
readonly hidden = this.#hidden.asReadonly();
|
||||
#manager: AnnotationManager;
|
||||
readonly #logger = inject(NGXLogger);
|
||||
readonly #annotationSelected$ = new Subject<[Annotation[], string]>();
|
||||
readonly annotationSelected$ = this.#annotationSelected$.asObservable();
|
||||
resizingAnnotationId?: string = undefined;
|
||||
annotationHasBeenResized?: boolean = false;
|
||||
readonly hidden = this.#hidden.asReadonly();
|
||||
|
||||
get selected() {
|
||||
return this.#manager.getSelectedAnnotations();
|
||||
@ -128,21 +128,15 @@ export class REDAnnotationManager {
|
||||
annotations.forEach(annotation => this.#manager.redrawAnnotation(annotation));
|
||||
}
|
||||
|
||||
add(annotations: Annotation | Annotation[]) {
|
||||
async add(annotations: Annotation | Annotation[]): Promise<void> {
|
||||
try {
|
||||
annotations = asList(annotations);
|
||||
this.#manager.addAnnotations(annotations, { imported: true });
|
||||
return this.#manager.drawAnnotationsFromList(annotations);
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
}
|
||||
|
||||
showHidden() {
|
||||
const hidden = this.#getByIds([...this.hidden().values()]);
|
||||
this.show(hidden);
|
||||
}
|
||||
|
||||
#listenForAnnotationSelected() {
|
||||
this.#manager.addEventListener('annotationSelected', async (annotations: Annotation[], action: string) => {
|
||||
this.#logger.info('[PDF] Annotation selected: ', annotations, action);
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { inject, Injectable } from '@angular/core';
|
||||
import { GenericService, isIqserDevMode, Toaster } from '@iqser/common-ui';
|
||||
import { EntryStates, IEntityLog, IEntityLogEntry, ISectionGrid } from '@red/domain';
|
||||
import { GenericService, Toaster } from '@iqser/common-ui';
|
||||
import { EntryStates, IEntityLog, IEntityLogEntry } from '@red/domain';
|
||||
import { firstValueFrom, of } from 'rxjs';
|
||||
import { catchError } from 'rxjs/operators';
|
||||
|
||||
@ -20,10 +20,6 @@ export class EntityLogService extends GenericService<unknown> {
|
||||
return entityLog;
|
||||
}
|
||||
|
||||
getSectionGrid(dossierId: string, fileId: string) {
|
||||
return this._getOne<ISectionGrid>([dossierId, fileId], 'sectionGrid');
|
||||
}
|
||||
|
||||
#filterInvalidEntries(entityLogEntry: IEntityLogEntry[]) {
|
||||
return entityLogEntry.filter(entry => {
|
||||
entry.positions = entry.positions?.filter(p => !!p.rectangle?.length);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user