RED-3721: Highlights bulk actions
This commit is contained in:
parent
f31cfda850
commit
358b30866b
@ -29,34 +29,34 @@
|
||||
<iqser-circle-button
|
||||
(action)="resize($event)"
|
||||
*ngIf="annotationPermissions.canResizeAnnotation && annotations.length === 1"
|
||||
[iqserHelpMode]="helpModeKey + '_resize'"
|
||||
[scrollableParentView]="scrollableParentView"
|
||||
[tooltipPosition]="tooltipPosition"
|
||||
[tooltip]="'annotation-actions.resize.label' | translate"
|
||||
[type]="buttonType"
|
||||
icon="red:resize"
|
||||
[iqserHelpMode]="helpModeKey + '_resize'"
|
||||
[scrollableParentView]="scrollableParentView"
|
||||
></iqser-circle-button>
|
||||
|
||||
<iqser-circle-button
|
||||
(action)="annotationActionsService.changeLegalBasis($event, annotations, annotationsChanged)"
|
||||
*ngIf="annotationPermissions.canChangeLegalBasis"
|
||||
[scrollableParentView]="scrollableParentView"
|
||||
[tooltipPosition]="tooltipPosition"
|
||||
[tooltip]="'annotation-actions.edit-reason.label' | translate"
|
||||
[type]="buttonType"
|
||||
icon="iqser:edit"
|
||||
iqserHelpMode="redaction_edit_reason"
|
||||
[scrollableParentView]="scrollableParentView"
|
||||
></iqser-circle-button>
|
||||
|
||||
<iqser-circle-button
|
||||
(action)="acceptRecommendation($event)"
|
||||
*ngIf="annotationPermissions.canAcceptRecommendation"
|
||||
[scrollableParentView]="scrollableParentView"
|
||||
[tooltipPosition]="tooltipPosition"
|
||||
[tooltip]="'annotation-actions.accept-recommendation.label' | translate"
|
||||
[type]="buttonType"
|
||||
icon="iqser:check"
|
||||
iqserHelpMode="recommendation_accept_or_reject"
|
||||
[scrollableParentView]="scrollableParentView"
|
||||
></iqser-circle-button>
|
||||
|
||||
<iqser-circle-button
|
||||
@ -68,6 +68,33 @@
|
||||
icon="iqser:check"
|
||||
></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.convertHighlights(annotations)"
|
||||
*ngIf="(viewModeService.viewMode$ | async) === 'TEXT_HIGHLIGHTS'"
|
||||
[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.viewMode$ | async) === 'TEXT_HIGHLIGHTS'"
|
||||
[tooltipPosition]="tooltipPosition"
|
||||
[tooltip]="'annotation-actions.remove-highlights.label' | translate"
|
||||
[type]="buttonType"
|
||||
icon="iqser:trash"
|
||||
></iqser-circle-button>
|
||||
|
||||
<iqser-circle-button
|
||||
(action)="annotationActionsService.undoDirectAction($event, annotations, annotationsChanged)"
|
||||
*ngIf="annotationPermissions.canUndo"
|
||||
@ -89,16 +116,16 @@
|
||||
<iqser-circle-button
|
||||
(action)="annotationActionsService.recategorizeImages($event, annotations, annotationsChanged)"
|
||||
*ngIf="annotationPermissions.canRecategorizeImage"
|
||||
[iqserHelpMode]="helpModeKey + '_recategorize'"
|
||||
[tooltipPosition]="tooltipPosition"
|
||||
[tooltip]="'annotation-actions.recategorize-image' | translate"
|
||||
[type]="buttonType"
|
||||
[iqserHelpMode]="helpModeKey + '_recategorize'"
|
||||
icon="red:thumb-down"
|
||||
></iqser-circle-button>
|
||||
|
||||
<iqser-circle-button
|
||||
(action)="annotationReferencesService.show(annotations[0])"
|
||||
*ngIf="annotations[0].reference.length && !multiSelectService.isActive"
|
||||
*ngIf="!multiSelectService.isActive && annotations[0].reference.length"
|
||||
[tooltipPosition]="tooltipPosition"
|
||||
[tooltip]="'annotation-actions.see-references.label' | translate"
|
||||
[type]="buttonType"
|
||||
@ -108,20 +135,20 @@
|
||||
<iqser-circle-button
|
||||
(action)="annotationActionsService.forceAnnotation($event, annotations, annotationsChanged)"
|
||||
*ngIf="annotationPermissions.canForceRedaction"
|
||||
[scrollableParentView]="scrollableParentView"
|
||||
[tooltipPosition]="tooltipPosition"
|
||||
[tooltip]="'annotation-actions.force-redaction.label' | translate"
|
||||
[type]="buttonType"
|
||||
icon="red:thumb-up"
|
||||
iqserHelpMode="skipped_force_redaction"
|
||||
[scrollableParentView]="scrollableParentView"
|
||||
></iqser-circle-button>
|
||||
|
||||
<iqser-circle-button
|
||||
(action)="annotationActionsService.forceAnnotation($event, annotations, annotationsChanged, true)"
|
||||
*ngIf="annotationPermissions.canForceHint"
|
||||
[iqserHelpMode]="helpModeKey + '_force_hint'"
|
||||
[tooltipPosition]="tooltipPosition"
|
||||
[tooltip]="'annotation-actions.force-hint.label' | translate"
|
||||
[iqserHelpMode]="helpModeKey + '_force_hint'"
|
||||
[type]="buttonType"
|
||||
icon="red:thumb-up"
|
||||
></iqser-circle-button>
|
||||
@ -129,10 +156,10 @@
|
||||
<iqser-circle-button
|
||||
(action)="hideAnnotation($event)"
|
||||
*ngIf="isImage && isVisible"
|
||||
[iqserHelpMode]="helpModeKey + '_hide'"
|
||||
[tooltipPosition]="tooltipPosition"
|
||||
[tooltip]="'annotation-actions.hide' | translate"
|
||||
[type]="buttonType"
|
||||
[iqserHelpMode]="helpModeKey + '_hide'"
|
||||
icon="red:visibility-off"
|
||||
></iqser-circle-button>
|
||||
|
||||
@ -148,34 +175,34 @@
|
||||
<iqser-circle-button
|
||||
(action)="removeOrSuggestRemoveAnnotation($event, true)"
|
||||
*ngIf="annotationPermissions.canRemoveOrSuggestToRemoveFromDictionary"
|
||||
[iqserHelpMode]="helpModeKey + '_remove_from_dictionary'"
|
||||
[scrollableParentView]="scrollableParentView"
|
||||
[tooltipPosition]="tooltipPosition"
|
||||
[tooltip]="'annotation-actions.remove-annotation.remove-from-dict' | translate"
|
||||
[type]="buttonType"
|
||||
icon="red:remove-from-dict"
|
||||
[iqserHelpMode]="helpModeKey + '_remove_from_dictionary'"
|
||||
[scrollableParentView]="scrollableParentView"
|
||||
></iqser-circle-button>
|
||||
|
||||
<iqser-circle-button
|
||||
(action)="markAsFalsePositive($event)"
|
||||
*ngIf="annotationPermissions.canMarkAsFalsePositive"
|
||||
[iqserHelpMode]="helpModeKey + '_false_positive'"
|
||||
[scrollableParentView]="scrollableParentView"
|
||||
[tooltipPosition]="tooltipPosition"
|
||||
[tooltip]="'annotation-actions.remove-annotation.false-positive' | translate"
|
||||
[type]="buttonType"
|
||||
icon="red:thumb-down"
|
||||
[iqserHelpMode]="helpModeKey + '_false_positive'"
|
||||
[scrollableParentView]="scrollableParentView"
|
||||
></iqser-circle-button>
|
||||
|
||||
<iqser-circle-button
|
||||
(action)="removeOrSuggestRemoveAnnotation($event, false)"
|
||||
*ngIf="annotationPermissions.canRemoveOrSuggestToRemoveOnlyHere"
|
||||
[iqserHelpMode]="helpModeKey + '_remove_only_here'"
|
||||
[scrollableParentView]="scrollableParentView"
|
||||
[tooltipPosition]="tooltipPosition"
|
||||
[tooltip]="'annotation-actions.remove-annotation.only-here' | translate"
|
||||
[type]="buttonType"
|
||||
icon="iqser:trash"
|
||||
[iqserHelpMode]="helpModeKey + '_remove_only_here'"
|
||||
[scrollableParentView]="scrollableParentView"
|
||||
></iqser-circle-button>
|
||||
</ng-container>
|
||||
</div>
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { Component, EventEmitter, Input, OnChanges, Output } from '@angular/core';
|
||||
import { ChangeDetectorRef, Component, EventEmitter, Input, OnChanges, Output } from '@angular/core';
|
||||
import { AnnotationWrapper } from '@models/file/annotation.wrapper';
|
||||
import { PermissionsService } from '@services/permissions.service';
|
||||
import { AnnotationPermissions } from '@models/file/annotation.permissions';
|
||||
@ -11,6 +11,7 @@ import { HelpModeService, ScrollableParentView, ScrollableParentViews } from '@i
|
||||
import { PdfViewer } from '../../services/pdf-viewer.service';
|
||||
import { FileDataService } from '../../services/file-data.service';
|
||||
import { DictionariesMapService } from '@services/entity-services/dictionaries-map.service';
|
||||
import { ViewModeService } from '../../services/view-mode.service';
|
||||
|
||||
export const AnnotationButtonTypes = {
|
||||
dark: 'dark',
|
||||
@ -34,9 +35,11 @@ export class AnnotationActionsComponent implements OnChanges {
|
||||
|
||||
constructor(
|
||||
readonly multiSelectService: MultiSelectService,
|
||||
readonly viewModeService: ViewModeService,
|
||||
readonly annotationActionsService: AnnotationActionsService,
|
||||
readonly annotationReferencesService: AnnotationReferencesService,
|
||||
readonly helpModeService: HelpModeService,
|
||||
private readonly _changeRef: ChangeDetectorRef,
|
||||
private readonly _userService: UserService,
|
||||
private readonly _pdf: PdfViewer,
|
||||
private readonly _state: FilePreviewStateService,
|
||||
@ -52,8 +55,8 @@ export class AnnotationActionsComponent implements OnChanges {
|
||||
}
|
||||
|
||||
@Input()
|
||||
set annotations(value: AnnotationWrapper[]) {
|
||||
this._annotations = value.filter(a => a !== undefined);
|
||||
set annotations(annotations: AnnotationWrapper[]) {
|
||||
this._annotations = annotations.filter(a => a !== undefined);
|
||||
}
|
||||
|
||||
get viewerAnnotations() {
|
||||
@ -86,6 +89,7 @@ export class AnnotationActionsComponent implements OnChanges {
|
||||
|
||||
async ngOnChanges(): Promise<void> {
|
||||
await this._setPermissions();
|
||||
this._changeRef.markForCheck();
|
||||
}
|
||||
|
||||
removeOrSuggestRemoveAnnotation($event: MouseEvent, removeFromDict: boolean) {
|
||||
|
||||
@ -5,24 +5,34 @@
|
||||
<div class="dialog-content">
|
||||
<div [translate]="translations[data.operation].details" class="mb-24"></div>
|
||||
|
||||
<div class="iqser-input-group required w-150">
|
||||
<label translate="highlight-action-dialog.form.color.label"></label>
|
||||
<input class="hex-color-input" formControlName="color" name="color" type="text" />
|
||||
<div
|
||||
[colorPicker]="form.get('color').value"
|
||||
[cpDisabled]="true"
|
||||
[style.background]="form.get('color').value"
|
||||
class="input-icon"
|
||||
></div>
|
||||
</div>
|
||||
<ng-container *ngIf="data.color">
|
||||
<div class="iqser-input-group required w-150">
|
||||
<label translate="highlight-action-dialog.form.color.label"></label>
|
||||
<input class="hex-color-input" formControlName="color" name="color" type="text" />
|
||||
<div
|
||||
[colorPicker]="form.get('color').value"
|
||||
[cpDisabled]="true"
|
||||
[style.background]="form.get('color').value"
|
||||
class="input-icon"
|
||||
></div>
|
||||
</div>
|
||||
|
||||
<div class="iqser-input-group">
|
||||
<mat-radio-group formControlName="option">
|
||||
<mat-radio-button *ngFor="let option of options" [value]="option" color="primary">
|
||||
{{ translations[option] | translate }}
|
||||
</mat-radio-button>
|
||||
</mat-radio-group>
|
||||
</div>
|
||||
<div class="iqser-input-group">
|
||||
<mat-radio-group formControlName="option">
|
||||
<mat-radio-button *ngFor="let option of options" [value]="option" color="primary">
|
||||
{{ translations[option] | translate }}
|
||||
</mat-radio-button>
|
||||
</mat-radio-group>
|
||||
</div>
|
||||
</ng-container>
|
||||
|
||||
<ng-container *ngIf="!data.color">
|
||||
<div class="iqser-input-group">
|
||||
<mat-checkbox color="primary" formControlName="confirmation" name="confirmation">
|
||||
{{ translations[data.operation].confirmation | translate: { count: data.highlights.length } }}
|
||||
</mat-checkbox>
|
||||
</div>
|
||||
</ng-container>
|
||||
</div>
|
||||
|
||||
<div class="dialog-actions">
|
||||
|
||||
@ -39,11 +39,14 @@ export class HighlightActionDialogComponent extends BaseDialogComponent {
|
||||
|
||||
async save(): Promise<void> {
|
||||
this._loadingService.start();
|
||||
const { dossierId, fileId, operation, highlights, pageNumber } = this.data;
|
||||
const { dossierId, fileId, operation, highlights, pageNumber, color } = this.data;
|
||||
|
||||
// !color means we are in bulk select mode, so we don't need to apply additional page filters
|
||||
const filteredHighlights =
|
||||
this.form.get('option').value === TextHighlightOperationPages.ALL_PAGES
|
||||
!color || this.form.get('option').value === TextHighlightOperationPages.ALL_PAGES
|
||||
? highlights
|
||||
: highlights.filter(h => h.pageNumber === pageNumber);
|
||||
|
||||
const ids = filteredHighlights.map(h => h.id);
|
||||
await firstValueFrom(this._textHighlightService.performHighlightsAction(ids, dossierId, fileId, operation));
|
||||
this._loadingService.stop();
|
||||
@ -51,9 +54,15 @@ export class HighlightActionDialogComponent extends BaseDialogComponent {
|
||||
}
|
||||
|
||||
private _getForm(): FormGroup {
|
||||
return this._formBuilder.group({
|
||||
color: [{ value: this.data.color, disabled: true }, Validators.required],
|
||||
option: [null, Validators.required],
|
||||
});
|
||||
if (this.data.color) {
|
||||
return this._formBuilder.group({
|
||||
color: [{ value: this.data.color, disabled: true }, Validators.required],
|
||||
option: [null, Validators.required],
|
||||
});
|
||||
} else {
|
||||
return this._formBuilder.group({
|
||||
confirmation: [false, Validators.requiredTrue],
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -17,6 +17,7 @@ import {
|
||||
IRecategorizationRequest,
|
||||
IRectangle,
|
||||
IResizeRequest,
|
||||
TextHighlightOperation,
|
||||
} from '@red/domain';
|
||||
import { toPosition } from '../../dossier/utils/pdf-calculation.utils';
|
||||
import { AnnotationDrawService } from './annotation-draw.service';
|
||||
@ -71,6 +72,16 @@ export class AnnotationActionsService {
|
||||
);
|
||||
}
|
||||
|
||||
removeHighlights(highlights: AnnotationWrapper[]): void {
|
||||
const data = this._getHighlightOperationData(TextHighlightOperation.REMOVE, highlights);
|
||||
this._dialogService.openDialog('highlightAction', null, data);
|
||||
}
|
||||
|
||||
convertHighlights(highlights: AnnotationWrapper[]): void {
|
||||
const data = this._getHighlightOperationData(TextHighlightOperation.CONVERT, highlights);
|
||||
this._dialogService.openDialog('highlightAction', null, data);
|
||||
}
|
||||
|
||||
rejectSuggestion($event: MouseEvent, annotations: AnnotationWrapper[], annotationsChanged: EventEmitter<AnnotationWrapper[]>) {
|
||||
$event?.stopPropagation();
|
||||
const { dossierId, fileId } = this._state;
|
||||
@ -485,6 +496,15 @@ export class AnnotationActionsService {
|
||||
this._processObsAndEmit(this._manualRedactionService.addAnnotation(requests, dossierId, fileId), annotations, annotationsChanged);
|
||||
}
|
||||
|
||||
private _getHighlightOperationData(operation: TextHighlightOperation, highlights: AnnotationWrapper[]) {
|
||||
return {
|
||||
dossierId: this._state.dossierId,
|
||||
fileId: this._state.fileId,
|
||||
operation,
|
||||
highlights,
|
||||
};
|
||||
}
|
||||
|
||||
private _processObsAndEmit(
|
||||
obs: Observable<unknown>,
|
||||
annotations: AnnotationWrapper[],
|
||||
|
||||
@ -4,6 +4,9 @@ import { boolFactory } from '@iqser/common-ui';
|
||||
import { ViewModeService } from './view-mode.service';
|
||||
import { map, tap } from 'rxjs/operators';
|
||||
import { FilePreviewStateService } from './file-preview-state.service';
|
||||
import { ViewMode, ViewModes } from '@red/domain';
|
||||
|
||||
const ENABLED_MULTISELECT: ViewMode[] = [ViewModes.STANDARD, ViewModes.TEXT_HIGHLIGHTS];
|
||||
|
||||
@Injectable()
|
||||
export class MultiSelectService {
|
||||
@ -15,8 +18,8 @@ export class MultiSelectService {
|
||||
|
||||
constructor(private readonly _viewModeService: ViewModeService, private readonly _state: FilePreviewStateService) {
|
||||
[this.active$, this.inactive$] = boolFactory(this.#active$.asObservable());
|
||||
this.enabled$ = combineLatest([this._viewModeService.isStandard$, _state.isWritable$]).pipe(
|
||||
map((result: boolean[]) => !result.some(res => !res)),
|
||||
this.enabled$ = combineLatest([this._viewModeService.viewMode$, _state.isWritable$]).pipe(
|
||||
map(([viewMode, isWritable]) => isWritable && ENABLED_MULTISELECT.includes(viewMode)),
|
||||
tap(enabled => this.#enabled$.next(enabled)),
|
||||
);
|
||||
}
|
||||
|
||||
@ -6,11 +6,13 @@ export const highlightsTranslations = {
|
||||
title: _('highlight-action-dialog.convert.title'),
|
||||
details: _('highlight-action-dialog.convert.details'),
|
||||
save: _('highlight-action-dialog.convert.save'),
|
||||
confirmation: _('highlight-action-dialog.convert.confirmation'),
|
||||
},
|
||||
[TextHighlightOperation.REMOVE]: {
|
||||
title: _('highlight-action-dialog.remove.title'),
|
||||
details: _('highlight-action-dialog.remove.details'),
|
||||
save: _('highlight-action-dialog.remove.save'),
|
||||
confirmation: _('highlight-action-dialog.remove.confirmation'),
|
||||
},
|
||||
[TextHighlightOperationPages.ALL_PAGES]: _('highlight-action-dialog.all-pages'),
|
||||
[TextHighlightOperationPages.THIS_PAGE]: _('highlight-action-dialog.this-page'),
|
||||
|
||||
@ -195,6 +195,9 @@
|
||||
"accept-suggestion": {
|
||||
"label": "Genehmigen und zum Wörterbuch hinzufügen"
|
||||
},
|
||||
"convert-highlights": {
|
||||
"label": ""
|
||||
},
|
||||
"edit-reason": {
|
||||
"label": "Begründung bearbeiten"
|
||||
},
|
||||
@ -299,6 +302,9 @@
|
||||
"only-here": "nur hier entfernen",
|
||||
"remove-from-dict": "Aus dem Wörterbuch entfernen"
|
||||
},
|
||||
"remove-highlights": {
|
||||
"label": ""
|
||||
},
|
||||
"resize-accept": {
|
||||
"label": "Größe speichern"
|
||||
},
|
||||
@ -1421,6 +1427,7 @@
|
||||
},
|
||||
"all-pages": "",
|
||||
"convert": {
|
||||
"confirmation": "",
|
||||
"details": "",
|
||||
"save": "",
|
||||
"title": ""
|
||||
@ -1431,6 +1438,7 @@
|
||||
}
|
||||
},
|
||||
"remove": {
|
||||
"confirmation": "",
|
||||
"details": "",
|
||||
"save": "",
|
||||
"title": ""
|
||||
|
||||
@ -195,6 +195,9 @@
|
||||
"accept-suggestion": {
|
||||
"label": "Approve Suggestion"
|
||||
},
|
||||
"convert-highlights": {
|
||||
"label": "Convert Selected Highlights"
|
||||
},
|
||||
"edit-reason": {
|
||||
"label": "Edit Reason"
|
||||
},
|
||||
@ -299,6 +302,9 @@
|
||||
"only-here": "Remove only here",
|
||||
"remove-from-dict": "Remove from dictionary"
|
||||
},
|
||||
"remove-highlights": {
|
||||
"label": "Remove Selected Highlights"
|
||||
},
|
||||
"resize-accept": {
|
||||
"label": "Save Resize"
|
||||
},
|
||||
@ -1421,6 +1427,7 @@
|
||||
},
|
||||
"all-pages": "For all pages in this document",
|
||||
"convert": {
|
||||
"confirmation": "The {count} selected {count, plural, one{highlight} other{highlights}} will be converted",
|
||||
"details": "All highlights from the document will be converted to Imported Redactions, using the color set up in the Default Colors section of the app.",
|
||||
"save": "Convert Highlights",
|
||||
"title": "Convert highlights to imported redactions"
|
||||
@ -1431,6 +1438,7 @@
|
||||
}
|
||||
},
|
||||
"remove": {
|
||||
"confirmation": "The {count} selected {count, plural, one{highlight} other{highlights}} will be removed from the document",
|
||||
"details": "Removing highlights from the document will delete all the rectangles and leave a white background behind the highlighted text.",
|
||||
"save": "Remove Highlights",
|
||||
"title": "Remove highlights"
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user