RED-7340 - WIP on Rectangle redactions: Use bulk-local redactions + New dialog design

This commit is contained in:
Valentin Mihai 2024-09-27 16:43:01 +03:00
parent 8ab324f69e
commit fad870e83e
17 changed files with 173 additions and 48 deletions

View File

@ -2,9 +2,15 @@ import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
import { addHintTranslations } from '@translations/add-hint-translations';
import { redactTextTranslations } from '@translations/redact-text-translations';
import { removeRedactionTranslations } from '@translations/remove-redaction-translations';
import { ForceAnnotationOptions, RedactOrHintOptions, RemoveRedactionOptions } from '../../file-preview/utils/dialog-types';
import {
ForceAnnotationOptions,
RectangleRedactOptions,
RedactOrHintOptions,
RemoveRedactionOptions,
} from '../../file-preview/utils/dialog-types';
export const SystemDefaults = {
RECTANGLE_REDACT_DEFAULT: RectangleRedactOptions.ONLY_THIS_PAGE,
ADD_REDACTION_DEFAULT: RedactOrHintOptions.IN_DOSSIER,
ADD_HINT_DEFAULT: RedactOrHintOptions.IN_DOSSIER,
FORCE_REDACTION_DEFAULT: ForceAnnotationOptions.ONLY_HERE,

View File

@ -160,7 +160,7 @@ export class AddHintDialogComponent extends IqserDialogComponent<AddHintDialogCo
}
extraOptionChanged(option: DetailsRadioOption<RedactOrHintOption>): void {
this.#applyToAllDossiers = option.extraOption.checked;
this.#applyToAllDossiers = option.additionalCheck.checked;
this.#setDictionaries();
if (this.#applyToAllDossiers && this.form.get('dictionary').value) {
@ -176,7 +176,7 @@ export class AddHintDialogComponent extends IqserDialogComponent<AddHintDialogCo
if (!this.#applyToAllDossiers) {
const selectedDictionaryType = this.form.get('dictionary').value;
const selectedDictionary = this.dictionaries.find(d => d.type === selectedDictionaryType);
this.options[1].extraOption.disabled = selectedDictionary.dossierDictionaryOnly;
this.options[1].additionalCheck.disabled = selectedDictionary.dossierDictionaryOnly;
}
}
@ -223,7 +223,7 @@ export class AddHintDialogComponent extends IqserDialogComponent<AddHintDialogCo
#resetValues() {
this.#applyToAllDossiers = this.applyToAll;
if (!this.#isRss) {
this.options[1].extraOption.checked = this.#applyToAllDossiers;
this.options[1].additionalCheck.checked = this.#applyToAllDossiers;
}
this.form.get('dictionary').setValue(null);
}

View File

@ -3,6 +3,9 @@
<div [translate]="title" class="dialog-header heading-l"></div>
<div class="dialog-content">
<iqser-details-radio [options]="options" (extraOptionChanged)="extraOptionChanged($event)" formControlName="option">
</iqser-details-radio>
<div *ngIf="!isRectangle" class="iqser-input-group w-450">
<label [translate]="'manual-annotation.dialog.content.text'"></label>
<div *ngIf="!isEditingSelectedText" class="flex-align-items-center">
@ -91,27 +94,6 @@
<label [translate]="'manual-annotation.dialog.content.comment'"></label>
<textarea formControlName="comment" iqserHasScrollbar name="comment" rows="4" type="text"></textarea>
</div>
<div *ngIf="isRectangle" class="apply-on-multiple-pages iqser-input-group w-450">
<mat-checkbox
(change)="applyOnMultiplePages = !applyOnMultiplePages"
[checked]="applyOnMultiplePages"
class="mb-15"
color="primary"
>
{{ 'manual-annotation.dialog.content.apply-on-multiple-pages' | translate }}
</mat-checkbox>
<div *ngIf="applyOnMultiplePages">
<input
[placeholder]="'manual-annotation.dialog.content.apply-on-multiple-pages-placeholder' | translate"
class="full-width"
formControlName="multiplePages"
/>
<span class="hint">{{ 'manual-annotation.dialog.content.apply-on-multiple-pages-hint' | translate }}</span>
</div>
</div>
</div>
<div class="dialog-actions">

View File

@ -1,4 +1,4 @@
import { Component, Inject, OnInit } from '@angular/core';
import { Component, Inject, OnChanges, OnInit, SimpleChanges } from '@angular/core';
import { ReactiveFormsModule, Validators } from '@angular/forms';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
@ -25,6 +25,12 @@ import { MatFormField } from '@angular/material/form-field';
import { MatOption, MatSelect, MatSelectTrigger } from '@angular/material/select';
import { MatTooltip } from '@angular/material/tooltip';
import { MatCheckbox } from '@angular/material/checkbox';
import { DetailsRadioOption } from '@common-ui/inputs/details-radio/details-radio-option';
import { RectangleRedactOption, RectangleRedactOptions, RedactOrHintOption } from '../../utils/dialog-types';
import { getRectangleRedactOptions } from '../../utils/dialog-options';
import { DetailsRadioComponent } from '@common-ui/inputs/details-radio/details-radio.component';
import { SystemDefaults } from '../../../account/utils/dialog-defaults';
import { validatePageRange } from '../../utils/form-validators';
export interface LegalBasisOption {
label?: string;
@ -53,19 +59,21 @@ export const NON_READABLE_CONTENT = 'non-readable content';
IqserDenyDirective,
MatCheckbox,
IconButtonComponent,
DetailsRadioComponent,
],
providers: [ManualRedactionService],
})
export class ManualAnnotationDialogComponent extends BaseDialogComponent implements OnInit {
readonly #dossier: Dossier;
readonly roles = Roles;
isDictionaryRequest: boolean;
isFalsePositiveRequest: boolean;
isEditingSelectedText = false;
applyOnMultiplePages = false;
manualRedactionTypeExists = true;
possibleDictionaries: Dictionary[] = [];
legalOptions: LegalBasisOption[] = [];
protected readonly roles = Roles;
protected readonly options: DetailsRadioOption<RectangleRedactOption>[];
protected isDictionaryRequest: boolean;
protected isFalsePositiveRequest: boolean;
protected isEditingSelectedText = false;
protected applyOnMultiplePages = false;
protected manualRedactionTypeExists = true;
protected possibleDictionaries: Dictionary[] = [];
protected legalOptions: LegalBasisOption[] = [];
constructor(
readonly iqserPermissionsService: IqserPermissionsService,
@ -85,10 +93,20 @@ export class ManualAnnotationDialogComponent extends BaseDialogComponent impleme
this.manualRedactionTypeExists = this._dictionaryService.hasManualType(this.#dossier.dossierTemplateId);
this.options = getRectangleRedactOptions();
this.form = this.#getForm();
this.initialFormValue = this.form.getRawValue();
}
extraOptionChanged(option: DetailsRadioOption<RectangleRedactOption>): void {
if (option.value === RectangleRedactOptions.MULTIPLE_PAGES) {
setTimeout(() => {
this.form.get('option')?.updateValueAndValidity();
}, 0);
}
}
get title() {
return this._manualRedactionService.getTitle(this.data.manualRedactionEntryWrapper.type);
}
@ -200,6 +218,7 @@ export class ManualAnnotationDialogComponent extends BaseDialogComponent impleme
comment: [null],
classification: [NON_READABLE_CONTENT],
multiplePages: '',
option: [this.#getOption(SystemDefaults.RECTANGLE_REDACT_DEFAULT), validatePageRange()],
});
}
@ -229,6 +248,10 @@ export class ManualAnnotationDialogComponent extends BaseDialogComponent impleme
: this.form.get('selectedText').value;
}
#getOption(option: RectangleRedactOption): DetailsRadioOption<RectangleRedactOption> {
return this.options.find(o => o.value === option);
}
#selectReason() {
if (this.legalOptions.length === 1) {
this.form.get('reason').setValue(this.legalOptions[0]);

View File

@ -128,7 +128,7 @@ export class RedactRecommendationDialogComponent
}
extraOptionChanged(option: DetailsRadioOption<RedactOrHintOption>): void {
this.#applyToAllDossiers = option.extraOption.checked;
this.#applyToAllDossiers = option.additionalCheck.checked;
this.#setDictionaries();
if (this.#applyToAllDossiers && this.form.controls.dictionary.value) {
@ -144,7 +144,7 @@ export class RedactRecommendationDialogComponent
if (!this.#applyToAllDossiers) {
const selectedDictionaryType = this.form.controls.dictionary.value;
const selectedDictionary = this.dictionaries.find(d => d.type === selectedDictionaryType);
this.options[0].extraOption.disabled = selectedDictionary.dossierDictionaryOnly;
this.options[0].additionalCheck.disabled = selectedDictionary.dossierDictionaryOnly;
}
}

View File

@ -150,7 +150,7 @@ export class RedactTextDialogComponent
}
extraOptionChanged(option: DetailsRadioOption<RedactOrHintOption>): void {
this.#applyToAllDossiers = option.extraOption.checked;
this.#applyToAllDossiers = option.additionalCheck.checked;
this.#setDictionaries();
if (this.#applyToAllDossiers && this.form.controls.dictionary.value) {
@ -166,7 +166,7 @@ export class RedactTextDialogComponent
if (!this.#applyToAllDossiers) {
const selectedDictionaryType = this.form.controls.dictionary.value;
const selectedDictionary = this.dictionaries.find(d => d.type === selectedDictionaryType);
this.options[2].extraOption.disabled = selectedDictionary.dossierDictionaryOnly;
this.options[2].additionalCheck.disabled = selectedDictionary.dossierDictionaryOnly;
}
}
@ -230,7 +230,7 @@ export class RedactTextDialogComponent
#resetValues() {
this.#applyToAllDossiers = this.applyToAll;
this.options[2].extraOption.checked = this.#applyToAllDossiers;
this.options[2].additionalCheck.checked = this.#applyToAllDossiers;
if (this.dictionaryRequest) {
this.form.controls.reason.setValue(null);
this.form.controls.dictionary.setValue(null);

View File

@ -82,7 +82,7 @@ export class ResizeRedactionDialogComponent extends IqserDialogComponent<
super.close({
comment: formValue.comment,
updateDictionary,
addToAllDossiers: !!formValue.option?.extraOption?.checked,
addToAllDossiers: !!formValue.option?.additionalCheck?.checked,
});
}

View File

@ -435,7 +435,7 @@ export class AnnotationActionsService {
type: redaction.type,
positions: redaction.positions,
addToDictionary: true,
addToAllDossiers: !!dialogResult.option.extraOption?.checked || !!dialogResult.applyToAllDossiers,
addToAllDossiers: !!dialogResult.option.additionalCheck?.checked || !!dialogResult.applyToAllDossiers,
reason: 'False Positive',
dictionaryEntryType: redaction.isRecommendation
? DictionaryEntryTypes.FALSE_RECOMMENDATION
@ -561,7 +561,7 @@ export class AnnotationActionsService {
value: redaction.value,
comment: dialogResult.comment,
removeFromDictionary: dialogResult.option.value === RemoveRedactionOptions.IN_DOSSIER,
removeFromAllDossiers: !!dialogResult.option.extraOption?.checked || !!dialogResult.applyToAllDossiers,
removeFromAllDossiers: !!dialogResult.option.additionalCheck?.checked || !!dialogResult.applyToAllDossiers,
}));
}
}

View File

@ -8,6 +8,8 @@ import { removeRedactionTranslations } from '@translations/remove-redaction-tran
import { resizeRedactionTranslations } from '@translations/resize-redaction-translations';
import {
ForceAnnotationOption,
RectangleRedactOption,
RectangleRedactOptions,
RedactOrHintOption,
RedactOrHintOptions,
RemoveRedactionData,
@ -16,6 +18,7 @@ import {
ResizeOptions,
ResizeRedactionOption,
} from './dialog-types';
import { rectangleRedactTranslations } from '@translations/rectangle-redact-translations';
const PIN_ICON = 'red:push-pin';
const DOCUMENT_ICON = 'iqser:document';
@ -43,7 +46,7 @@ export const getEditRedactionOptions = (
descriptionParams: { dossierName: dossierName },
icon: FOLDER_ICON,
value: ResizeOptions.IN_DOSSIER,
extraOption: {
additionalCheck: {
label: editRedactionTranslations.inDossier.extraOptionLabel,
checked: applyToAllDossiers,
hidden: dossierDictionaryOnly,
@ -95,7 +98,7 @@ export const getRedactOrHintOptions = (
icon: FOLDER_ICON,
value: ResizeOptions.IN_DOSSIER,
disabled: isPageExcluded,
extraOption: {
additionalCheck: {
label: translations.inDossier.extraOptionLabel,
checked: applyToAllDossiers,
hidden: !isApprover,
@ -105,6 +108,29 @@ export const getRedactOrHintOptions = (
return options;
};
export const getRectangleRedactOptions = (): DetailsRadioOption<RectangleRedactOption>[] => {
return [
{
label: rectangleRedactTranslations.onlyThisPage.label,
description: rectangleRedactTranslations.onlyThisPage.description,
icon: PIN_ICON,
value: RectangleRedactOptions.ONLY_THIS_PAGE,
},
{
label: rectangleRedactTranslations.multiplePages.label,
description: rectangleRedactTranslations.multiplePages.description,
icon: DOCUMENT_ICON,
value: RectangleRedactOptions.MULTIPLE_PAGES,
additionalInput: {
label: rectangleRedactTranslations.multiplePages.extraOptionLabel,
description: rectangleRedactTranslations.multiplePages.extraOptionDescription,
placeholder: rectangleRedactTranslations.multiplePages.extraOptionPlaceholder,
value: '',
},
},
];
};
export const getResizeRedactionOptions = (
redaction: AnnotationWrapper,
dossier: Dossier,
@ -136,7 +162,7 @@ export const getResizeRedactionOptions = (
tooltip: !dictBasedType ? translations.inDossier.tooltip : null,
icon: FOLDER_ICON,
value: RedactOrHintOptions.IN_DOSSIER,
extraOption: {
additionalCheck: {
label: translations.inDossier.extraOptionLabel,
checked: applyToAllDossiers,
hidden: !isApprover,
@ -190,7 +216,7 @@ export const getRemoveRedactionOptions = (
},
icon: FOLDER_ICON,
value: RemoveRedactionOptions.IN_DOSSIER,
extraOption: !isDocumine
additionalCheck: !isDocumine
? {
label: translations.IN_DOSSIER.extraOptionLabel,
checked: applyToAllDossiers,
@ -212,7 +238,7 @@ export const getRemoveRedactionOptions = (
},
icon: FOLDER_ICON,
value: RemoveRedactionOptions.DO_NOT_RECOMMEND,
extraOption: !isDocumine
additionalCheck: !isDocumine
? {
label: translations.DO_NOT_RECOMMEND.extraOptionLabel,
checked: applyToAllDossiers,
@ -232,7 +258,7 @@ export const getRemoveRedactionOptions = (
},
icon: REMOVE_FROM_DICT_ICON,
value: RemoveRedactionOptions.FALSE_POSITIVE,
extraOption: !isDocumine
additionalCheck: !isDocumine
? {
label: translations.FALSE_POSITIVE.extraOptionLabel,
checked: applyToAllDossiers,

View File

@ -11,6 +11,13 @@ export const RedactOrHintOptions = {
export type RedactOrHintOption = keyof typeof RedactOrHintOptions;
export const RectangleRedactOptions = {
ONLY_THIS_PAGE: 'ONLY_THIS_PAGE',
MULTIPLE_PAGES: 'MULTIPLE_PAGES',
} as const;
export type RectangleRedactOption = keyof typeof RectangleRedactOptions;
export const ForceAnnotationOptions = {
ONLY_HERE: 'ONLY_HERE',
IN_DOCUMENT: 'IN_DOCUMENT',

View File

@ -0,0 +1,12 @@
import { AbstractControl, ValidatorFn } from '@angular/forms';
export const validatePageRange = (): ValidatorFn => {
return (control: AbstractControl): { [key: string]: any } | null => {
const option = control.value;
if (option?.additionalInput) {
const validRange = /^(\d+(-\d+)?)(,\d+(-\d+)?)*$/.test(option.additionalInput.value);
return validRange ? null : { invalidRange: true };
}
return null;
};
};

View File

@ -0,0 +1,16 @@
import { DialogOption } from '@translations/redact-text-translations';
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
export const rectangleRedactTranslations: Record<'onlyThisPage' | 'multiplePages', DialogOption> = {
onlyThisPage: {
label: _('manual-annotation.dialog.content.options.only-this-page.label'),
description: _('manual-annotation.dialog.content.options.only-this-page.description'),
},
multiplePages: {
label: _('manual-annotation.dialog.content.options.multiple-pages.label'),
description: _('manual-annotation.dialog.content.options.multiple-pages.description'),
extraOptionLabel: _('manual-annotation.dialog.content.options.multiple-pages.extraOptionLabel'),
extraOptionDescription: _('manual-annotation.dialog.content.options.multiple-pages.extraOptionDescription'),
extraOptionPlaceholder: _('manual-annotation.dialog.content.options.multiple-pages.extraOptionPlaceholder'),
},
} as const;

View File

@ -7,6 +7,7 @@ export interface DialogOption {
descriptionBulk?: string;
extraOptionLabel?: string;
extraOptionDescription?: string;
extraOptionPlaceholder?: string;
}
export const redactTextTranslations: Record<'onlyHere' | 'inDocument' | 'inDossier', DialogOption> = {

View File

@ -1878,6 +1878,19 @@
"dictionary": "Wörterbuch",
"edit-selected-text": "Ausgewählten Text bearbeiten",
"legalBasis": "Rechtsgrundlage",
"options": {
"multiple-pages": {
"description": "",
"extraOptionDescription": "",
"extraOptionLabel": "",
"extraOptionPlaceholder": "",
"label": ""
},
"only-this-page": {
"description": "",
"label": ""
}
},
"reason": "Grund",
"reason-placeholder": "Grund auswählen...",
"rectangle": "Bereichsschwärzung",

View File

@ -1878,6 +1878,19 @@
"dictionary": "Dictionary",
"edit-selected-text": "Edit selected text",
"legalBasis": "Legal basis",
"options": {
"multiple-pages": {
"description": "Edit redaction the range of pages",
"extraOptionDescription": "Minus(-) for range and comma(,) for enumeration",
"extraOptionLabel": "Range",
"extraOptionPlaceholder": "e.g. 1-20,22,32",
"label": "Apply on multiple pages"
},
"only-this-page": {
"description": "Edit redaction only at this position in this document",
"label": "Apply only on this page"
}
},
"reason": "Reason",
"reason-placeholder": "Select a reason...",
"rectangle": "Custom rectangle",

View File

@ -1878,6 +1878,19 @@
"dictionary": "Wörterbuch",
"edit-selected-text": "Edit selected text",
"legalBasis": "Rechtsgrundlage",
"options": {
"multiple-pages": {
"description": "",
"extraOptionDescription": "",
"extraOptionLabel": "",
"extraOptionPlaceholder": "",
"label": ""
},
"only-this-page": {
"description": "",
"label": ""
}
},
"reason": "Begründung",
"reason-placeholder": "Wählen Sie eine Begründung aus ...",
"rectangle": "Benutzerdefinierter Bereich",

View File

@ -1878,6 +1878,19 @@
"dictionary": "Dictionary",
"edit-selected-text": "Edit selected text",
"legalBasis": "Legal Basis",
"options": {
"multiple-pages": {
"description": "Edit redaction the range of pages",
"extraOptionDescription": "Minus(-) for range and comma(,) for enumeration",
"extraOptionLabel": "Range",
"extraOptionPlaceholder": "e.g. 1-20,22,32",
"label": "Apply on multiple pages"
},
"only-this-page": {
"description": "Edit redaction only at this position in this document",
"label": "Apply only on this page"
}
},
"reason": "Reason",
"reason-placeholder": "Select a reason ...",
"rectangle": "Custom Rectangle",