Merge remote-tracking branch 'origin/master'

# Conflicts:
#	apps/red-ui/src/app/modules/shared/components/add-edit-entity/add-edit-entity.component.ts
This commit is contained in:
Nicoleta Panaghiu 2023-07-25 12:09:04 +03:00
commit 8a20d8ef93
34 changed files with 633 additions and 224 deletions

View File

@ -2,9 +2,9 @@
<form (submit)="save()" [formGroup]="form">
<div [translate]="'add-hint.dialog.title'" class="dialog-header heading-l"></div>
<div class="dialog-content">
<div class="dialog-content redaction">
<div class="iqser-input-group w-450">
<label [translate]="'add-hint.dialog.content.selected-text'"></label>
<label class="selected-text" [translate]="'add-hint.dialog.content.selected-text'"></label>
{{ form.get('selectedText').value }}
</div>

View File

@ -1,8 +0,0 @@
label {
font-weight: bold;
padding-bottom: 8px;
}
iqser-details-radio {
padding-top: 20px;
}

View File

@ -1,6 +1,6 @@
import { Component, OnInit } from '@angular/core';
import { DetailsRadioOption, IconButtonTypes, IqserPermissionsService } from '@iqser/common-ui';
import { Dictionary, Dossier, IAddRedactionRequest, SuperTypes } from '@red/domain';
import { Dictionary, Dossier, IAddRedactionRequest } from '@red/domain';
import { FormBuilder, UntypedFormGroup } from '@angular/forms';
import { Roles } from '@users/roles';
import { JustificationsService } from '@services/entity-services/justifications.service';
@ -14,7 +14,6 @@ import { AddHintData, AddHintResult } from '../../utils/dialog-types';
@Component({
templateUrl: './add-hint-dialog.component.html',
styleUrls: ['./add-hint-dialog.component.scss'],
})
export class AddHintDialogComponent extends IqserDialogComponent<AddHintDialogComponent, AddHintData, AddHintResult> implements OnInit {
readonly roles = Roles;

View File

@ -2,9 +2,9 @@
<form (submit)="save()" [formGroup]="form">
<div [translate]="'add-annotation.dialog.title'" class="dialog-header heading-l"></div>
<div class="dialog-content">
<div class="dialog-content redaction">
<div class="iqser-input-group w-450">
<label [translate]="'add-annotation.dialog.content.selected-text'"></label>
<label class="selected-text" [translate]="'add-annotation.dialog.content.selected-text'"></label>
{{ form.get('selectedText').value }}
</div>

View File

@ -1,8 +0,0 @@
label {
font-weight: bold;
padding-bottom: 8px;
}
iqser-details-radio {
padding-top: 20px;
}

View File

@ -1,6 +1,6 @@
import { Component, OnInit } from '@angular/core';
import { IconButtonTypes, IqserPermissionsService } from '@iqser/common-ui';
import { Dictionary, Dossier, IAddRedactionRequest, SuperTypes } from '@red/domain';
import { Dictionary, Dossier, IAddRedactionRequest } from '@red/domain';
import { FormBuilder, UntypedFormGroup } from '@angular/forms';
import { Roles } from '@users/roles';
import { ActiveDossiersService } from '@services/dossiers/active-dossiers.service';
@ -10,7 +10,6 @@ import { AddAnnotationData, AddAnnotationResult } from '../../../utils/dialog-ty
@Component({
templateUrl: './add-annotation-dialog.component.html',
styleUrls: ['./add-annotation-dialog.component.scss'],
})
export class AddAnnotationDialogComponent
extends IqserDialogComponent<AddAnnotationDialogComponent, AddAnnotationData, AddAnnotationResult>

View File

@ -8,7 +8,6 @@ import { getRemoveRedactionOptions, RemoveAnnotationOption, RemoveAnnotationOpti
@Component({
templateUrl: './remove-annotation-dialog.component.html',
styleUrls: ['./remove-annotation-dialog.component.scss'],
})
export class RemoveAnnotationDialogComponent extends IqserDialogComponent<
RemoveAnnotationDialogComponent,

View File

@ -0,0 +1,49 @@
<section class="dialog">
<form (submit)="save()" [formGroup]="form">
<div [innerHTML]="'resize-annotation.dialog.header' | translate" class="dialog-header heading-l"></div>
<div class="dialog-content redaction">
<div class="iqser-input-group w-450">
<label class="selected-text" [translate]="'resize-annotation.dialog.content.original-text'"></label>
{{ redaction.value }}
</div>
<div class="iqser-input-group w-450">
<label class="selected-text" [translate]="'resize-annotation.dialog.content.resized-text'"></label>
{{ data.text }}
</div>
<ng-container *deny="roles.getRss">
<div class="iqser-input-group required w-450">
<label [translate]="'resize-annotation.dialog.content.type'"></label>
<mat-form-field>
<mat-select formControlName="dictionary">
<mat-select-trigger>{{ displayedDictionaryLabel }}</mat-select-trigger>
<mat-option [value]="redaction.entity.type">
<span> {{ redaction.entity.label }} </span>
</mat-option>
</mat-select>
</mat-form-field>
</div>
</ng-container>
<div class="iqser-input-group w-450">
<label [translate]="'resize-annotation.dialog.content.comment'"></label>
<textarea formControlName="comment" iqserHasScrollbar name="comment" rows="4" type="text"></textarea>
</div>
</div>
<div class="dialog-actions">
<iqser-icon-button
[label]="'resize-annotation.dialog.actions.save' | translate"
[submit]="true"
[type]="iconButtonTypes.primary"
></iqser-icon-button>
<div [translate]="'resize-annotation.dialog.actions.cancel'" class="all-caps-label cancel" mat-dialog-close></div>
</div>
</form>
<iqser-circle-button (action)="close()" class="dialog-close" icon="iqser:close"></iqser-circle-button>
</section>

View File

@ -0,0 +1,66 @@
import { Component, OnInit } from '@angular/core';
import { IconButtonTypes, IqserDialogComponent } from '@iqser/common-ui';
import { FormBuilder, FormControl, UntypedFormGroup } from '@angular/forms';
import { AnnotationWrapper } from '@models/file/annotation.wrapper';
import { ActiveDossiersService } from '@services/dossiers/active-dossiers.service';
import { Dictionary, Dossier } from '@red/domain';
import { Roles } from '@users/roles';
import { DictionariesMapService } from '@services/entity-services/dictionaries-map.service';
import { ResizeAnnotationData, ResizeAnnotationResult } from '../../../utils/dialog-types';
@Component({
templateUrl: './resize-annotation-dialog.component.html',
})
export class ResizeAnnotationDialogComponent
extends IqserDialogComponent<ResizeAnnotationDialogComponent, ResizeAnnotationData, ResizeAnnotationResult>
implements OnInit
{
readonly roles = Roles;
readonly iconButtonTypes = IconButtonTypes;
dictionaries: Dictionary[] = [];
redaction: AnnotationWrapper;
form!: UntypedFormGroup;
readonly #dossier: Dossier;
constructor(
private readonly _activeDossiersService: ActiveDossiersService,
private readonly _dictionariesMapService: DictionariesMapService,
private readonly _formBuilder: FormBuilder,
) {
super();
this.#dossier = _activeDossiersService.find(this.data.dossierId);
this.redaction = this.data.redaction;
this.form = this.#getForm();
}
get displayedDictionaryLabel() {
const dictType = this.form.get('dictionary').value;
if (dictType) {
return this.dictionaries.find(d => d.type === dictType)?.label ?? null;
}
return null;
}
async ngOnInit() {
this.dictionaries = this._dictionariesMapService.get(this.#dossier.dossierTemplateId);
}
#getForm(): UntypedFormGroup {
return this._formBuilder.group({
comment: [null],
dictionary: new FormControl({ value: this.redaction.entity.type, disabled: true }),
});
}
save(): void {
const formValue = this.form.getRawValue();
this.dialogRef.close({
comment: formValue.comment,
updateDictionary: this.redaction.entity.hasDictionary,
});
}
}

View File

@ -2,9 +2,9 @@
<form (submit)="save()" [formGroup]="form">
<div [translate]="'redact-text.dialog.title'" class="dialog-header heading-l"></div>
<div class="dialog-content">
<div class="dialog-content redaction">
<div class="iqser-input-group w-450">
<label [translate]="'redact-text.dialog.content.selected-text'"></label>
<label class="selected-text" [translate]="'redact-text.dialog.content.selected-text'"></label>
{{ form.get('selectedText').value }}
</div>

View File

@ -1,8 +0,0 @@
label {
font-weight: bold;
padding-bottom: 8px;
}
iqser-details-radio {
padding-top: 20px;
}

View File

@ -16,7 +16,6 @@ import { RedactTextData, RedactTextResult } from '../../utils/dialog-types';
@Component({
templateUrl: './redact-text-dialog.component.html',
styleUrls: ['./redact-text-dialog.component.scss'],
})
export class RedactTextDialogComponent
extends IqserDialogComponent<RedactTextDialogComponent, RedactTextData, RedactTextResult>

View File

@ -10,7 +10,6 @@ import { getRemoveRedactionOptions, RemoveRedactionOption } from '../../utils/di
@Component({
templateUrl: './remove-redaction-dialog.component.html',
styleUrls: ['./remove-redaction-dialog.component.scss'],
})
export class RemoveRedactionDialogComponent extends IqserDialogComponent<
RemoveRedactionDialogComponent,

View File

@ -1,31 +0,0 @@
<section class="dialog">
<form (submit)="save()" [formGroup]="form">
<div [translate]="'resize-annotation-dialog.header'" class="dialog-header heading-l"></div>
<div class="dialog-content">
<div class="iqser-input-group w-300">
<label [translate]="'resize-annotation-dialog.content.comment'"></label>
<textarea formControlName="comment" iqserHasScrollbar name="comment" rows="4" type="text"></textarea>
</div>
<div *ngIf="form.get('updateDictionary')" class="iqser-input-group">
<mat-checkbox color="primary" formControlName="updateDictionary"
><span [innerHTML]="'resize-annotation-dialog.content.update-dictionary' | translate : { text: this.text }"></span>
</mat-checkbox>
</div>
</div>
<div class="dialog-actions">
<iqser-icon-button
[disabled]="disabled"
[label]="'resize-annotation-dialog.actions.save' | translate"
[submit]="true"
[type]="iconButtonTypes.primary"
></iqser-icon-button>
<div [translate]="'resize-annotation-dialog.actions.cancel'" class="all-caps-label cancel" mat-dialog-close></div>
</div>
</form>
<iqser-circle-button (action)="close()" class="dialog-close" icon="iqser:close"></iqser-circle-button>
</section>

View File

@ -1,39 +0,0 @@
import { Component, Inject, OnInit } from '@angular/core';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { BaseDialogComponent } from '@iqser/common-ui';
import { AnnotationWrapper } from '@models/file/annotation.wrapper';
@Component({
templateUrl: './resize-annotation-dialog.component.html',
})
export class ResizeAnnotationDialogComponent extends BaseDialogComponent implements OnInit {
constructor(
protected readonly _dialogRef: MatDialogRef<ResizeAnnotationDialogComponent>,
@Inject(MAT_DIALOG_DATA) private readonly _data: { annotation: AnnotationWrapper; text?: string },
) {
super(_dialogRef);
}
get disabled(): boolean {
return !this.valid;
}
get text(): string {
return this._data.text;
}
ngOnInit() {
const controlsConfig = { comment: [null] };
if (this._data.annotation.isModifyDictionary) {
controlsConfig['updateDictionary'] = [false];
}
this.form = this._formBuilder.group(controlsConfig);
this.initialFormValue = this.form.getRawValue();
}
save() {
this._dialogRef.close(this.form.getRawValue());
}
}

View File

@ -0,0 +1,54 @@
<section class="dialog">
<form (submit)="save()" [formGroup]="form">
<div
[innerHTML]="'resize-redaction.dialog.header' | translate : { type: redaction.hint ? 'hint' : 'redaction' }"
class="dialog-header heading-l"
></div>
<div class="dialog-content redaction">
<div class="iqser-input-group w-450">
<label class="selected-text" [translate]="'resize-redaction.dialog.content.original-text'"></label>
{{ redaction.value }}
</div>
<div class="iqser-input-group w-450">
<label class="selected-text" [translate]="'resize-redaction.dialog.content.resized-text'"></label>
{{ data.text }}
</div>
<iqser-details-radio [options]="options" formControlName="option"></iqser-details-radio>
<ng-container *deny="roles.getRss">
<div class="iqser-input-group required w-450">
<label [translate]="'resize-redaction.dialog.content.type'"></label>
<mat-form-field>
<mat-select formControlName="dictionary">
<mat-select-trigger>{{ displayedDictionaryLabel }}</mat-select-trigger>
<mat-option [value]="redaction.entity.type">
<span> {{ redaction.entity.label }} </span>
</mat-option>
</mat-select>
</mat-form-field>
</div>
</ng-container>
<div class="iqser-input-group w-450">
<label [translate]="'resize-redaction.dialog.content.comment'"></label>
<textarea formControlName="comment" iqserHasScrollbar name="comment" rows="4" type="text"></textarea>
</div>
</div>
<div class="dialog-actions">
<iqser-icon-button
[label]="'resize-redaction.dialog.actions.save' | translate"
[submit]="true"
[type]="iconButtonTypes.primary"
></iqser-icon-button>
<div [translate]="'resize-redaction.dialog.actions.cancel'" class="all-caps-label cancel" mat-dialog-close></div>
</div>
</form>
<iqser-circle-button (action)="close()" class="dialog-close" icon="iqser:close"></iqser-circle-button>
</section>

View File

@ -0,0 +1,72 @@
import { Component, OnInit } from '@angular/core';
import { DetailsRadioOption, IconButtonTypes, IqserDialogComponent, IqserPermissionsService } from '@iqser/common-ui';
import { ResizeRedactionData, ResizeRedactionResult } from '../../utils/dialog-types';
import { FormBuilder, FormControl, UntypedFormGroup } from '@angular/forms';
import { AnnotationWrapper } from '@models/file/annotation.wrapper';
import { getResizeRedactionOptions, ResizeOptions, ResizeRedactionOption } from '../../utils/dialog-options';
import { ActiveDossiersService } from '@services/dossiers/active-dossiers.service';
import { Dictionary, Dossier } from '@red/domain';
import { Roles } from '@users/roles';
import { DictionariesMapService } from '@services/entity-services/dictionaries-map.service';
@Component({
templateUrl: './resize-redaction-dialog.component.html',
})
export class ResizeRedactionDialogComponent
extends IqserDialogComponent<ResizeRedactionDialogComponent, ResizeRedactionData, ResizeRedactionResult>
implements OnInit
{
readonly roles = Roles;
readonly iconButtonTypes = IconButtonTypes;
readonly options: DetailsRadioOption<ResizeRedactionOption>[];
dictionaries: Dictionary[] = [];
redaction: AnnotationWrapper;
form!: UntypedFormGroup;
readonly #dossier: Dossier;
readonly #isRss = this._iqserPermissionsService.has(Roles.getRss);
constructor(
private readonly _activeDossiersService: ActiveDossiersService,
private readonly _iqserPermissionsService: IqserPermissionsService,
private readonly _dictionariesMapService: DictionariesMapService,
private readonly _formBuilder: FormBuilder,
) {
super();
this.#dossier = _activeDossiersService.find(this.data.dossierId);
this.redaction = this.data.redaction;
this.options = getResizeRedactionOptions(this.redaction, this.#dossier, this.#isRss);
this.form = this.#getForm();
}
get displayedDictionaryLabel() {
const dictType = this.form.get('dictionary').value;
if (dictType) {
return this.dictionaries.find(d => d.type === dictType)?.label ?? null;
}
return null;
}
async ngOnInit() {
this.dictionaries = this._dictionariesMapService.get(this.#dossier.dossierTemplateId);
}
#getForm(): UntypedFormGroup {
return this._formBuilder.group({
comment: [null],
dictionary: new FormControl({ value: this.redaction.entity.type, disabled: true }),
option: [this.options[0]],
});
}
save(): void {
const formValue = this.form.getRawValue();
const updateDictionary = formValue.option.value === ResizeOptions.IN_DOSSIER;
this.dialogRef.close({
comment: formValue.comment,
updateDictionary,
});
}
}

View File

@ -42,7 +42,7 @@ import { HighlightsSeparatorComponent } from './components/highlights-separator/
import { PendingChangesGuard } from '@guards/can-deactivate.guard';
import { ManualAnnotationDialogComponent } from './dialogs/manual-redaction-dialog/manual-annotation-dialog.component';
import { ForceAnnotationDialogComponent } from './dialogs/force-redaction-dialog/force-annotation-dialog.component';
import { ResizeAnnotationDialogComponent } from './dialogs/resize-annotation-dialog/resize-annotation-dialog.component';
import { ResizeRedactionDialogComponent } from './dialogs/resize-redaction-dialog/resize-redaction-dialog.component';
import { ChangeLegalBasisDialogComponent } from './dialogs/change-legal-basis-dialog/change-legal-basis-dialog.component';
import { RecategorizeImageDialogComponent } from './dialogs/recategorize-image-dialog/recategorize-image-dialog.component';
import { HighlightActionDialogComponent } from './dialogs/highlight-action-dialog/highlight-action-dialog.component';
@ -69,6 +69,7 @@ import { TenantPipe } from '@iqser/common-ui/lib/tenants';
import { AddHintDialogComponent } from './dialogs/add-hint-dialog/add-hint-dialog.component';
import { AddAnnotationDialogComponent } from './dialogs/docu-mine/add-annotation-dialog/add-annotation-dialog.component';
import { RemoveAnnotationDialogComponent } from './dialogs/docu-mine/remove-annotation-dialog/remove-annotation-dialog.component';
import { ResizeAnnotationDialogComponent } from './dialogs/docu-mine/resize-annotation-dialog/resize-annotation-dialog.component';
const routes: IqserRoutes = [
{
@ -82,7 +83,7 @@ const routes: IqserRoutes = [
const dialogs = [
ManualAnnotationDialogComponent,
ForceAnnotationDialogComponent,
ResizeAnnotationDialogComponent,
ResizeRedactionDialogComponent,
ChangeLegalBasisDialogComponent,
RecategorizeImageDialogComponent,
HighlightActionDialogComponent,
@ -95,6 +96,7 @@ const dialogs = [
RemoveRedactionDialogComponent,
AddAnnotationDialogComponent,
RemoveAnnotationDialogComponent,
ResizeAnnotationDialogComponent,
];
const components = [

View File

@ -35,14 +35,16 @@ import { DossierTemplatesService } from '@services/dossier-templates/dossier-tem
import { isJustOne, List } from '@iqser/common-ui/lib/utils';
import { PermissionsService } from '@services/permissions.service';
import {
RedactTextData,
RemoveAnnotationResult,
RemoveRedactionData,
RemoveRedactionPermissions,
RemoveRedactionResult,
ResizeRedactionData,
ResizeRedactionResult,
} from '../utils/dialog-types';
import { RemoveRedactionOptions } from '../utils/dialog-options';
import { RemoveAnnotationDialogComponent } from '../dialogs/docu-mine/remove-annotation-dialog/remove-annotation-dialog.component';
import { ResizeRedactionDialogComponent } from '../dialogs/resize-redaction-dialog/resize-redaction-dialog.component';
import { ResizeAnnotationDialogComponent } from '../dialogs/docu-mine/resize-annotation-dialog/resize-annotation-dialog.component';
@Injectable()
export class AnnotationActionsService {
@ -221,11 +223,20 @@ export class AnnotationActionsService {
return this.convertRecommendationToAnnotation([recommendation]);
}
const dossier = this._state.dossier();
const isImageText = annotation.isImage ? 'Image' : textAndPositions.text;
const text = annotation.rectangle ? annotation.value : isImageText;
const data = { annotation, text };
this._dialogService.openDialog('resizeAnnotation', data, (result: { comment: string; updateDictionary: boolean }) => {
const data = {
redaction: annotation,
text,
dossierId: dossier.dossierId,
};
const result: ResizeRedactionResult = await this.#getResizeRedactionDialog(data).result();
if (result) {
const resizeRequest: IResizeRequest = {
annotationId: annotation.id,
comment: result.comment,
@ -239,7 +250,7 @@ export class AnnotationActionsService {
const { fileId, dossierId } = this._state;
const request = this._manualRedactionService.resizeOrSuggestResize([resizeRequest], dossierId, fileId);
this.#processObsAndEmit(request);
});
}
}
async cancelResize(annotationWrapper: AnnotationWrapper) {
@ -415,4 +426,11 @@ export class AnnotationActionsService {
}
return this._iqserDialog.openDefault(RemoveRedactionDialogComponent, { data });
}
#getResizeRedactionDialog(data: ResizeRedactionData) {
if (this.#isDocumine) {
return this._iqserDialog.openDefault(ResizeAnnotationDialogComponent, { data });
}
return this._iqserDialog.openDefault(ResizeRedactionDialogComponent, { data });
}
}

View File

@ -6,7 +6,6 @@ import { ManualAnnotationDialogComponent } from '../dialogs/manual-redaction-dia
import { ChangeLegalBasisDialogComponent } from '../dialogs/change-legal-basis-dialog/change-legal-basis-dialog.component';
import { RecategorizeImageDialogComponent } from '../dialogs/recategorize-image-dialog/recategorize-image-dialog.component';
import { ConfirmationDialogComponent, DialogConfig, DialogService } from '@iqser/common-ui';
import { ResizeAnnotationDialogComponent } from '../dialogs/resize-annotation-dialog/resize-annotation-dialog.component';
import { HighlightActionDialogComponent } from '../dialogs/highlight-action-dialog/highlight-action-dialog.component';
import { RssDialogComponent } from '../dialogs/rss-dialog/rss-dialog.component';
@ -16,7 +15,6 @@ type DialogType =
| 'rss'
| 'recategorizeImage'
| 'changeLegalBasis'
| 'resizeAnnotation'
| 'forceAnnotation'
| 'manualAnnotation'
| 'highlightAction';
@ -38,9 +36,6 @@ export class FilePreviewDialogService extends DialogService<DialogType> {
changeLegalBasis: {
component: ChangeLegalBasisDialogComponent,
},
resizeAnnotation: {
component: ResizeAnnotationDialogComponent,
},
forceAnnotation: {
component: ForceAnnotationDialogComponent,
},

View File

@ -5,6 +5,8 @@ import { Dossier } from '@red/domain';
import { removeRedactionTranslations } from '@translations/remove-redaction-translations';
import { RemoveRedactionData } from './dialog-types';
import { removeAnnotationTranslations } from '@translations/remove-annotation-translations';
import { resizeRedactionTranslations } from '@translations/resize-redaction-translations';
import { AnnotationWrapper } from '@models/file/annotation.wrapper';
const PIN_ICON = 'red:push-pin';
const FOLDER_ICON = 'red:folder';
@ -17,6 +19,9 @@ export const RedactOrHintOptions = {
export type RedactOrHintOption = keyof typeof RedactOrHintOptions;
export const ResizeOptions = RedactOrHintOptions;
export type ResizeRedactionOption = RedactOrHintOption;
export const RemoveRedactionOptions = {
ONLY_HERE: 'ONLY_HERE',
IN_DOSSIER: 'IN_DOSSIER',
@ -40,7 +45,7 @@ export const getRedactOrHintOptions = (
label: translations.onlyHere.label,
description: translations.onlyHere.description,
icon: PIN_ICON,
value: RedactOrHintOptions.ONLY_HERE,
value: ResizeOptions.ONLY_HERE,
},
];
if (!isRss) {
@ -49,7 +54,7 @@ export const getRedactOrHintOptions = (
description: translations.inDossier.description,
descriptionParams: { dossierName: dossier.dossierName },
icon: FOLDER_ICON,
value: RedactOrHintOptions.IN_DOSSIER,
value: ResizeOptions.IN_DOSSIER,
extraOption: {
label: translations.inDossier.extraOptionLabel,
checked: applyToAllDossiers,
@ -60,6 +65,35 @@ export const getRedactOrHintOptions = (
return options;
};
export const getResizeRedactionOptions = (
redaction: AnnotationWrapper,
dossier: Dossier,
isRss: boolean,
): DetailsRadioOption<ResizeRedactionOption>[] => {
const translations = resizeRedactionTranslations;
const options: DetailsRadioOption<ResizeRedactionOption>[] = [
{
label: translations.onlyHere.label,
description: translations.onlyHere.description,
icon: PIN_ICON,
value: RedactOrHintOptions.ONLY_HERE,
},
];
if (!isRss) {
const dictBasedType = redaction.isModifyDictionary;
options.push({
label: translations.inDossier.label,
description: translations.inDossier.description,
descriptionParams: { dossierName: dossier.dossierName },
disabled: !dictBasedType,
tooltip: !dictBasedType ? translations.inDossier.tooltip : null,
icon: FOLDER_ICON,
value: RedactOrHintOptions.IN_DOSSIER,
});
}
return options;
};
export const getRemoveRedactionOptions = (
data: RemoveRedactionData,
isDocumine: boolean = false,

View File

@ -25,6 +25,21 @@ export interface RedactTextResult {
export type AddHintResult = RedactTextResult;
export type AddAnnotationResult = RedactTextResult;
export interface ResizeRedactionData {
redaction: AnnotationWrapper;
text: string;
dossierId: string;
}
export type ResizeAnnotationData = ResizeRedactionData;
export interface ResizeRedactionResult {
comment: string;
updateDictionary: boolean;
}
export type ResizeAnnotationResult = ResizeRedactionResult;
export interface RemoveRedactionPermissions {
canRemoveOnlyHere: boolean;
canRemoveFromDictionary: boolean;

View File

@ -84,15 +84,39 @@
</mat-slide-toggle>
</div>
<div *ngIf="form.get('caseSensitive')" class="iqser-input-group">
<mat-checkbox color="primary" formControlName="caseSensitive" name="caseSensitive">
{{ 'add-edit-entity.form.case-sensitive' | translate }}
</mat-checkbox>
</div>
<div *ngIf="form.get('hasDictionary')?.value" class="dictionary-extras">
<ng-container *deny="roles.getRss">
<span
(click)="toggleDossierOnlyEntity()"
*ngIf="form.get('dossierDictionaryOnly') as field"
[class.cursor-pointer]="!field.disabled"
[class.disabled]="field.disabled"
class="round-checkbox-form-entry"
>
<iqser-round-checkbox [active]="!field.value" [size]="18"></iqser-round-checkbox>
{{ 'add-edit-entity.form.template-and-dossier-dictionaries' | translate }}
</span>
<span
(click)="toggleDossierOnlyEntity()"
*ngIf="form.get('dossierDictionaryOnly') as field"
[class.cursor-pointer]="!field.disabled"
[class.disabled]="field.disabled"
class="round-checkbox-form-entry"
>
<iqser-round-checkbox [active]="field.value" [size]="18"></iqser-round-checkbox>
{{ 'add-edit-entity.form.dossier-dictionary-only' | translate }}
</span>
</ng-container>
<div *ngIf="form.get('addToDictionaryAction')" class="iqser-input-group">
<mat-checkbox color="primary" formControlName="addToDictionaryAction" name="addToDictionaryAction">
{{ 'add-edit-entity.form.add-to-dictionary-action' | translate }}
</mat-checkbox>
<div *ngIf="form.get('manageEntriesInDictionaryEditorOnly')" class="iqser-input-group">
<mat-checkbox color="primary" formControlName="manageEntriesInDictionaryEditorOnly" name="manageEntriesInDictionaryEditorOnly">
{{ 'add-edit-entity.form.manage-entries-in-dictionary-editor-only' | translate }}
</mat-checkbox>
</div>
<div *ngIf="form.get('caseSensitive')" class="iqser-input-group">
<mat-checkbox color="primary" formControlName="caseSensitive" name="caseSensitive">
{{ 'add-edit-entity.form.case-sensitive' | translate }}
</mat-checkbox>
</div>
</div>
</form>

View File

@ -9,3 +9,27 @@
margin-top: 0;
}
}
.dictionary-extras {
margin-left: 38px;
margin-top: 14px;
.round-checkbox-form-entry:first-of-type {
margin-bottom: 10px;
}
}
.round-checkbox-form-entry {
display: flex;
gap: 8px;
margin-bottom: 20px;
user-select: none;
}
.cursor-pointer {
cursor: pointer;
}
.disabled {
opacity: 0.6;
}

View File

@ -8,6 +8,7 @@ import { DictionariesMapService } from '@services/entity-services/dictionaries-m
import { PermissionsService } from '@services/permissions.service';
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
import { DictionaryService } from '@services/entity-services/dictionary.service';
import { Roles } from '@users/roles';
import { BaseFormComponent, getConfig, LoadingService, Toaster } from '@iqser/common-ui';
const REDACTION_FIELDS = ['defaultReason'];
@ -35,6 +36,7 @@ export class AddEditEntityComponent extends BaseFormComponent implements OnInit
technicalName$: Observable<string>;
colors: Color[];
readonly roles = Roles;
readonly isDocumine = getConfig().IS_DOCUMINE;
@ -50,10 +52,6 @@ export class AddEditEntityComponent extends BaseFormComponent implements OnInit
super();
}
cpDisabled(color: Color) {
return this.form.get(color.controlName).disabled;
}
get #isDossierRedaction(): boolean {
return this.entity?.type === 'dossier_redaction';
}
@ -62,26 +60,34 @@ export class AddEditEntityComponent extends BaseFormComponent implements OnInit
return !!this.entity?.systemManaged;
}
get #addToDictionaryActionControl() {
return { value: !!this.entity?.addToDictionaryAction, disabled: this.#isSystemManaged && !this.#isDossierRedaction };
}
get #isHint(): boolean {
return !!this.form.get('hint')?.value;
}
cpDisabled(color: Color) {
return this.form.get(color.controlName).disabled;
}
revert(): void {
this.form.patchValue(this.initialFormValue);
}
ngOnInit() {
this._initializeForm();
this.#initializeForm();
}
toggleDossierOnlyEntity() {
const propName = 'dossierDictionaryOnly';
const control = this.form.get(propName);
if (control.disabled) {
return;
}
this.form.patchValue({ [propName]: !control?.value });
}
async save(): Promise<void> {
this._loadingService.start();
const dictionary = this._formToObject();
const dictionary = this.#formToObject();
try {
if (this.entity) {
// edit mode
@ -110,26 +116,26 @@ export class AddEditEntityComponent extends BaseFormComponent implements OnInit
labelParams: () => ({ type: this.#isHint ? 'hint' : 'redaction' }),
placeholder: _('add-edit-entity.form.color-placeholder'),
controlName: 'hexColor',
hasColor$: this._colorEmpty$(form, 'hexColor'),
hasColor$: this.#colorEmpty$(form, 'hexColor'),
},
{
label: _('add-edit-entity.form.color'),
labelParams: () => ({ type: 'recommendation' }),
placeholder: _('add-edit-entity.form.color-placeholder'),
controlName: 'recommendationHexColor',
hasColor$: this._colorEmpty$(form, 'recommendationHexColor'),
hasColor$: this.#colorEmpty$(form, 'recommendationHexColor'),
},
{
label: _('add-edit-entity.form.color'),
labelParams: () => ({ type: this.#isHint ? 'ignored' : 'skipped' }),
placeholder: _('add-edit-entity.form.color-placeholder'),
controlName: 'skippedHexColor',
hasColor$: this._colorEmpty$(form, 'skippedHexColor'),
hasColor$: this.#colorEmpty$(form, 'skippedHexColor'),
},
];
}
private _initializeForm(): void {
#initializeForm(): void {
let controlsConfig: Record<string, any> = {
label: [this.entity?.label, [Validators.required, Validators.minLength(3)]],
hexColor: [this.entity?.hexColor, [Validators.required, Validators.minLength(7)]],
@ -145,26 +151,28 @@ export class AddEditEntityComponent extends BaseFormComponent implements OnInit
rank: [{ value: this.entity?.rank, disabled: this.#isSystemManaged }, Validators.required],
hint: [{ value: !!this.entity?.hint, disabled: this.#isSystemManaged }],
hasDictionary: [{ value: !!this.entity?.hasDictionary, disabled: this.#isSystemManaged }],
caseSensitive: [{ value: this.entity ? !this.entity.caseInsensitive : false, disabled: this.#isSystemManaged }],
dossierDictionaryOnly: [{ value: !!this.entity?.dossierDictionaryOnly, disabled: this.#isSystemManaged || this.entity }],
caseSensitive: [{ value: !!this.entity?.caseInsensitive, disabled: this.#isSystemManaged }],
manageEntriesInDictionaryEditorOnly: [
{
value: !this.entity?.addToDictionaryAction,
disabled: this.#isSystemManaged && !this.#isDossierRedaction,
},
],
};
}
if (!this.entity?.hint && !this.#isDossierRedaction) {
Object.assign(controlsConfig, {
controlsConfig = {
...controlsConfig,
defaultReason: [{ value: null, disabled: true }],
});
}
if (this.entity?.hasDictionary || this.#isDossierRedaction) {
Object.assign(controlsConfig, {
addToDictionaryAction: [this.#addToDictionaryActionControl],
});
};
}
const form = this._formBuilder.group(controlsConfig);
this.#initializeColors(form);
this.technicalName$ = form.get('label').valueChanges.pipe(map((value: string) => this._toTechnicalName(value)));
this.technicalName$ = form.get('label').valueChanges.pipe(map((value: string) => this.#toTechnicalName(value)));
if (!this.#isDossierRedaction) {
form.get('hint').valueChanges.subscribe(isHint => {
@ -175,17 +183,9 @@ export class AddEditEntityComponent extends BaseFormComponent implements OnInit
}
});
form.get('hasDictionary').valueChanges.subscribe(hasDictionary => {
if (hasDictionary) {
form.addControl('addToDictionaryAction', new UntypedFormControl(this.#addToDictionaryActionControl));
} else {
form.removeControl('addToDictionaryAction');
}
});
if (!this.entity) {
form.get('label').valueChanges.subscribe((label: string) => {
form.get('type').setValue(this._toTechnicalName(label));
form.get('type').setValue(this.#toTechnicalName(label));
});
}
}
@ -199,7 +199,7 @@ export class AddEditEntityComponent extends BaseFormComponent implements OnInit
this.form = form;
}
private _toTechnicalName(value: string) {
#toTechnicalName(value: string) {
const existingTechnicalNames = this._dictionariesMapService.get(this.dossierTemplateId).map(dict => dict.type);
const baseTechnicalName = toSnakeCase(value.trim());
let technicalName = baseTechnicalName.replaceAll(/[^A-Za-z0-9_-]/g, '');
@ -213,19 +213,20 @@ export class AddEditEntityComponent extends BaseFormComponent implements OnInit
return technicalName;
}
private _colorEmpty$(form: UntypedFormGroup, field: string) {
#colorEmpty$(form: UntypedFormGroup, field: string) {
return form.get(field).valueChanges.pipe(
startWith(form.get(field).value),
map((value: string) => !value || value?.length === 0),
);
}
private _formToObject(): IDictionary {
#formToObject(): IDictionary {
// Fields which aren't set for hints, need additional check
const addToDictionaryAction = !!this.form.get('addToDictionaryAction')?.value;
const addToDictionaryAction = !this.form.get('manageEntriesInDictionaryEditorOnly')?.value;
const hasDictionary = !!this.form.get('hasDictionary')?.value;
const dossierDictionaryOnly = !!this.form.get('dossierDictionaryOnly')?.value;
let entity = {
const entity = {
...this.entity,
label: this.form.get('label').value,
hexColor: this.form.get('hexColor').value,
@ -233,10 +234,11 @@ export class AddEditEntityComponent extends BaseFormComponent implements OnInit
skippedHexColor: this.form.get('skippedHexColor').value,
dossierTemplateId: this.dossierTemplateId,
addToDictionaryAction,
dossierDictionaryOnly,
};
if (this.entity?.type !== 'dossier_redaction') {
entity = {
return {
...entity,
type: this.form.get('type').value,
description: this.form.get('description').value,

View File

@ -15,6 +15,7 @@ import {
IqserAllowDirective,
IqserDenyDirective,
IqserHelpModeModule,
RoundCheckboxComponent,
StopPropagationDirective,
} from '@iqser/common-ui';
import { NavigateLastDossiersScreenDirective } from './directives/navigate-last-dossiers-screen.directive';
@ -98,6 +99,7 @@ const deleteThisWhenAllComponentsAreStandalone = [DonutChartComponent];
IqserAllowDirective,
IqserDenyDirective,
SelectComponent,
RoundCheckboxComponent,
],
exports: [...modules, ...components, ...utils, ...deleteThisWhenAllComponentsAreStandalone],
providers: [

View File

@ -0,0 +1,19 @@
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
import { DialogOption } from '@translations/redact-text-translations';
interface ResizeDialogOption extends DialogOption {
tooltip?: string;
}
export const resizeRedactionTranslations: Record<'onlyHere' | 'inDossier', ResizeDialogOption> = {
onlyHere: {
label: _('resize-redaction.dialog.content.options.only-here.label'),
description: _('resize-redaction.dialog.content.options.only-here.description'),
},
inDossier: {
label: _('resize-redaction.dialog.content.options.in-dossier.label'),
description: _('resize-redaction.dialog.content.options.in-dossier.description'),
extraOptionLabel: _('resize-redaction.dialog.content.options.in-dossier.extraOptionLabel'),
tooltip: _('resize-redaction.dialog.content.options.in-dossier.tooltip'),
},
} as const;

View File

@ -1,9 +1,9 @@
{
"ADMIN_CONTACT_NAME": null,
"ADMIN_CONTACT_URL": null,
"API_URL": "https://dev-04.iqser.cloud",
"APP_NAME": "DocuMine",
"IS_DOCUMINE": true,
"API_URL": "https://dan.iqser.cloud",
"APP_NAME": "RedactManager",
"IS_DOCUMINE": false,
"AUTO_READ_TIME": 3,
"BACKEND_APP_VERSION": "4.4.40",
"EULA_URL": "EULA_URL",
@ -12,13 +12,13 @@
"MAX_RETRIES_ON_SERVER_ERROR": 3,
"OAUTH_CLIENT_ID": "redaction",
"OAUTH_IDP_HINT": null,
"OAUTH_URL": "https://dev-04.iqser.cloud/auth",
"OAUTH_URL": "https://dan.iqser.cloud/auth",
"RECENT_PERIOD_IN_HOURS": 24,
"SELECTION_MODE": "structural",
"MANUAL_BASE_URL": "https://docs.redactmanager.com/preview",
"ANNOTATIONS_THRESHOLD": 1000,
"THEME": "scm",
"BASE_TRANSLATIONS_DIRECTORY": "/assets/i18n/scm/",
"THEME": "redact",
"BASE_TRANSLATIONS_DIRECTORY": "/assets/i18n/redact/",
"AVAILABLE_NOTIFICATIONS_DAYS": 30,
"AVAILABLE_OLD_NOTIFICATIONS_MINUTES": 60,
"NOTIFICATIONS_THRESHOLD": 1000,

View File

@ -118,7 +118,6 @@
"invalid-color-or-rank": ""
},
"form": {
"add-to-dictionary-action": "",
"case-sensitive": "",
"color": "",
"color-placeholder": "",
@ -126,15 +125,18 @@
"default-reason-placeholder": "",
"description": "",
"description-placeholder": "",
"dossier-dictionary-only": "",
"has-dictionary": "",
"hint": "",
"manage-entries-in-dictionary-editor-only": "",
"name": "",
"name-placeholder": "",
"rank": "",
"rank-placeholder": "",
"redaction": "",
"technical-name": "",
"technical-name-hint": ""
"technical-name-hint": "",
"template-and-dossier-dictionaries": ""
},
"success": {
"create": "",
@ -2100,16 +2102,47 @@
},
"header": "Temporäres Passwort für {userName} festlegen"
},
"resize-annotation-dialog": {
"actions": {
"cancel": "Abbrechen",
"save": "Änderungen speichern"
},
"content": {
"comment": "Kommentar",
"update-dictionary": ""
},
"header": "Schwärzung ändern"
"resize-annotation": {
"dialog": {
"actions": {
"cancel": "",
"save": ""
},
"content": {
"comment": "",
"original-text": "",
"resized-text": "",
"type": ""
},
"header": ""
}
},
"resize-redaction": {
"dialog": {
"actions": {
"cancel": "",
"save": ""
},
"content": {
"comment": "",
"options": {
"in-dossier": {
"description": "",
"extraOptionLabel": "",
"label": "",
"tooltip": ""
},
"only-here": {
"description": "",
"label": ""
}
},
"original-text": "",
"resized-text": "",
"type": ""
},
"header": ""
}
},
"roles": {
"inactive": "Inaktiv",

View File

@ -118,7 +118,6 @@
"invalid-color-or-rank": "Invalid color or rank! Rank is already used by another entity or the color is not a valid hexColor!"
},
"form": {
"add-to-dictionary-action": "Enable 'Add to dictionary'",
"case-sensitive": "Case Sensitive",
"color": "{type, select, redaction{Redaction} hint{Hint} recommendation{Recommendation} skipped{Skipped Redaction} ignored{Ignored Hint} other{}} Color",
"color-placeholder": "#",
@ -126,15 +125,18 @@
"default-reason-placeholder": "No Default Reason",
"description": "Description",
"description-placeholder": "Enter Description",
"dossier-dictionary-only": "Dossier dictionary only",
"has-dictionary": "Has dictionary",
"hint": "Hint",
"manage-entries-in-dictionary-editor-only": "Manage entries in Dictionary editor only",
"name": "Display Name",
"name-placeholder": "Enter Name",
"rank": "Rank",
"rank-placeholder": "1000",
"redaction": "Redaction",
"technical-name": "Technical Name",
"technical-name-hint": "{type, select, edit{Autogenerated based on the initial display name.} create{Autogenerates based on the display name and cannot be edited after saving.} other{}}"
"technical-name-hint": "{type, select, edit{Autogenerated based on the initial display name.} create{Autogenerates based on the display name and cannot be edited after saving.} other{}}",
"template-and-dossier-dictionaries": "Template & dossier dictionaries"
},
"success": {
"create": "Entity added!",
@ -2100,16 +2102,47 @@
},
"header": "Set Temporary Password for {userName}"
},
"resize-annotation-dialog": {
"actions": {
"cancel": "Cancel",
"save": "Save Changes"
},
"content": {
"comment": "Comment",
"update-dictionary": "Update dictionary: {text}"
},
"header": "Resize Redaction"
"resize-annotation": {
"dialog": {
"actions": {
"cancel": "Cancel",
"save": "Save Changes"
},
"content": {
"comment": "Comment",
"original-text": "Original annotation:",
"resized-text": "Resized annotation:",
"type": "Type"
},
"header": "Resize annotation"
}
},
"resize-redaction": {
"dialog": {
"actions": {
"cancel": "Cancel",
"save": "Save Changes"
},
"content": {
"comment": "Comment",
"options": {
"in-dossier": {
"description": "Resize in every document in {dossierName}.",
"extraOptionLabel": "Apply to all dossiers",
"label": "Resize in dossier",
"tooltip": "Only available for dictionary-based types"
},
"only-here": {
"description": "Resize only at this position in this document.",
"label": "Resize only here"
}
},
"original-text": "Original text:",
"resized-text": "Resized text:",
"type": "Type"
},
"header": "Resize {type}"
}
},
"roles": {
"inactive": "Inactive",

View File

@ -118,7 +118,6 @@
"invalid-color-or-rank": ""
},
"form": {
"add-to-dictionary-action": "",
"case-sensitive": "",
"color": "",
"color-placeholder": "",
@ -126,15 +125,18 @@
"default-reason-placeholder": "",
"description": "",
"description-placeholder": "",
"dossier-dictionary-only": "",
"has-dictionary": "",
"hint": "",
"manage-entries-in-dictionary-editor-only": "",
"name": "",
"name-placeholder": "",
"rank": "",
"rank-placeholder": "",
"redaction": "",
"technical-name": "",
"technical-name-hint": ""
"technical-name-hint": "",
"template-and-dossier-dictionaries": ""
},
"success": {
"create": "",
@ -2100,16 +2102,47 @@
},
"header": "Temporäres Passwort für {userName} festlegen"
},
"resize-annotation-dialog": {
"actions": {
"cancel": "Abbrechen",
"save": "Änderungen speichern"
},
"content": {
"comment": "Kommentar",
"update-dictionary": ""
},
"header": "Schwärzung ändern"
"resize-annotation": {
"dialog": {
"actions": {
"cancel": "",
"save": ""
},
"content": {
"comment": "",
"original-text": "",
"resized-text": "",
"type": ""
},
"header": ""
}
},
"resize-redaction": {
"dialog": {
"actions": {
"cancel": "",
"save": ""
},
"content": {
"comment": "",
"options": {
"in-dossier": {
"description": "",
"extraOptionLabel": "",
"label": "",
"tooltip": ""
},
"only-here": {
"description": "",
"label": ""
}
},
"original-text": "",
"resized-text": "",
"type": ""
},
"header": ""
}
},
"roles": {
"inactive": "Inaktiv",

View File

@ -118,7 +118,6 @@
"invalid-color-or-rank": "Invalid color or rank! Rank is already used by another entity or the color is not a valid hexColor!"
},
"form": {
"add-to-dictionary-action": "Enable 'Add to dictionary'",
"case-sensitive": "Case Sensitive",
"color": "{type, select, redaction{Component} hint{Hint} recommendation{Recommendation} skipped{Skipped Component} ignored{Ignored Hint} other{}} Color",
"color-placeholder": "#",
@ -126,15 +125,18 @@
"default-reason-placeholder": "No Default Reason",
"description": "Description",
"description-placeholder": "Enter Description",
"dossier-dictionary-only": "",
"has-dictionary": "Has dictionary",
"hint": "Hint",
"manage-entries-in-dictionary-editor-only": "",
"name": "Display Name",
"name-placeholder": "Enter Name",
"rank": "Rank",
"rank-placeholder": "1000",
"redaction": "Component",
"technical-name": "Technical Name",
"technical-name-hint": "{type, select, edit{Autogenerated based on the initial display name.} create{Autogenerates based on the display name and cannot be edited after saving.} other{}}"
"technical-name-hint": "{type, select, edit{Autogenerated based on the initial display name.} create{Autogenerates based on the display name and cannot be edited after saving.} other{}}",
"template-and-dossier-dictionaries": ""
},
"success": {
"create": "Entity added!",
@ -2100,16 +2102,47 @@
},
"header": "Set Temporary Password for {userName}"
},
"resize-annotation-dialog": {
"actions": {
"cancel": "Cancel",
"save": "Save Changes"
},
"content": {
"comment": "Comment",
"update-dictionary": "Update dictionary: {text}"
},
"header": "Resize Component"
"resize-annotation": {
"dialog": {
"actions": {
"cancel": "Cancel",
"save": "Save Changes"
},
"content": {
"comment": "Comment",
"original-text": "Original annotation:",
"resized-text": "Resized annotation:",
"type": "Type"
},
"header": "Resize annotation"
}
},
"resize-redaction": {
"dialog": {
"actions": {
"cancel": "Cancel",
"save": "Save Changes"
},
"content": {
"comment": "Comment",
"options": {
"in-dossier": {
"description": "Resize in every document in {dossierName}.",
"extraOptionLabel": "Apply to all dossiers",
"label": "Resize in dossier",
"tooltip": "Only available for dictionary-based types"
},
"only-here": {
"description": "Resize only at this position in this document.",
"label": "Resize only here"
}
},
"original-text": "Original text:",
"resized-text": "Resized text:",
"type": "Type"
},
"header": "Resize {type}"
}
},
"roles": {
"inactive": "Inactive",