RED-3721: Highlights bulk actions

This commit is contained in:
Adina Țeudan 2022-04-07 16:27:40 +03:00
parent f31cfda850
commit 358b30866b
9 changed files with 134 additions and 43 deletions

View File

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

View File

@ -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) {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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