Merge remote-tracking branch 'origin/master' into RED-9381

This commit is contained in:
Nicoleta Panaghiu 2024-09-27 15:26:00 +03:00
commit b2091d4f83
11 changed files with 107 additions and 115 deletions

View File

@ -8,7 +8,7 @@ import {
IconButtonTypes, IconButtonTypes,
IqserDialogComponent, IqserDialogComponent,
} from '@iqser/common-ui'; } from '@iqser/common-ui';
import { Dictionary, Dossier, SuperTypes } from '@red/domain'; import { Dictionary, Dossier } from '@red/domain';
import { ActiveDossiersService } from '@services/dossiers/active-dossiers.service'; import { ActiveDossiersService } from '@services/dossiers/active-dossiers.service';
import { DictionaryService } from '@services/entity-services/dictionary.service'; import { DictionaryService } from '@services/entity-services/dictionary.service';
import { Roles } from '@users/roles'; import { Roles } from '@users/roles';
@ -47,12 +47,12 @@ export class EditAnnotationDialogComponent
extends IqserDialogComponent<EditAnnotationDialogComponent, EditRedactionData, EditRedactResult> extends IqserDialogComponent<EditAnnotationDialogComponent, EditRedactionData, EditRedactResult>
implements OnInit implements OnInit
{ {
readonly #dossier: Dossier;
readonly roles = Roles; readonly roles = Roles;
readonly iconButtonTypes = IconButtonTypes; readonly iconButtonTypes = IconButtonTypes;
readonly redactedTexts: string[]; readonly redactedTexts: string[];
dictionaries: Dictionary[] = []; dictionaries: Dictionary[] = [];
form: UntypedFormGroup; form: UntypedFormGroup;
readonly #dossier: Dossier;
constructor( constructor(
private readonly _activeDossiersService: ActiveDossiersService, private readonly _activeDossiersService: ActiveDossiersService,
@ -60,7 +60,7 @@ export class EditAnnotationDialogComponent
private readonly _formBuilder: FormBuilder, private readonly _formBuilder: FormBuilder,
) { ) {
super(); super();
this.#dossier = _activeDossiersService.find(this.data.dossierId); this.#dossier = this._activeDossiersService.find(this.data.dossierId);
const annotations = this.data.annotations; const annotations = this.data.annotations;
this.redactedTexts = annotations.map(annotation => annotation.value); this.redactedTexts = annotations.map(annotation => annotation.value);
this.form = this.#getForm(); this.form = this.#getForm();
@ -83,10 +83,6 @@ export class EditAnnotationDialogComponent
this.#setTypes(); this.#setTypes();
} }
reasonChanged() {
this.form.patchValue({ reason: this.dictionaries.find(d => d.type === SuperTypes.ManualRedaction) });
}
save(): void { save(): void {
const value = this.form.value; const value = this.form.value;
this.dialogRef.close({ this.dialogRef.close({
@ -106,8 +102,4 @@ export class EditAnnotationDialogComponent
type: [sameType ? this.data.annotations[0].type : null], type: [sameType ? this.data.annotations[0].type : null],
}); });
} }
#allRectangles() {
return this.data.annotations.reduce((acc, a) => acc && a.AREA, true);
}
} }

View File

@ -1,7 +1,7 @@
<div *ngIf="dictionaries && selectedDictionary" class="dictionary-content"> <div *ngIf="dictionaries && selectedDictionary" class="dictionary-content">
<div class="dictionaries"> <div class="dictionaries">
<div <div
(click)="selectDictionary(dictionary)" (click)="selectDictionary(dictionary, undefined, true)"
*ngFor="let dictionary of dictionaries" *ngFor="let dictionary of dictionaries"
[class.active]="dictionary.label === selectedDictionary.label" [class.active]="dictionary.label === selectedDictionary.label"
class="dictionary" class="dictionary"
@ -33,44 +33,16 @@
</div> </div>
</div> </div>
<div class="entries"> <div class="entries">
<div class="header-wrapper"> <div class="heading flex-align-items-center">
<div class="header-left"> {{ selectedDictionary?.label }}
<div class="heading"> <iqser-circle-button
<div class="flex-align-items-center"> (action)="openEditDictionaryModal()"
{{ selectedDictionary?.label }} *ngIf="selectedDictionary.dossierDictionaryOnly && selectedDictionary.hasDictionary"
<iqser-circle-button [size]="20"
(action)="openEditDictionaryModal()" [tooltip]="'edit-dossier-dialog.dictionary.edit-button-tooltip' | translate"
*ngIf="selectedDictionary.dossierDictionaryOnly && selectedDictionary.hasDictionary" class="p-left-8"
[size]="20" icon="iqser:edit"
[tooltip]="'edit-dossier-dialog.dictionary.edit-button-tooltip' | translate" ></iqser-circle-button>
class="p-left-8"
icon="iqser:edit"
></iqser-circle-button>
</div>
<div class="small-label stats-subtitle">
<div>
<mat-icon svgIcon="red:entries"></mat-icon>
<ng-container *ngIf="activeEntryType === entryTypes.ENTRY || selectedDictionary.hint">
{{
'edit-dossier-dialog.dictionary.entries'
| translate: { length: entriesToDisplay.length, hint: selectedDictionary.hint }
}}
</ng-container>
<ng-container *ngIf="activeEntryType === entryTypes.FALSE_POSITIVE && !selectedDictionary.hint">
{{
'edit-dossier-dialog.dictionary.false-positive-entries' | translate: { length: entriesToDisplay.length }
}}
</ng-container>
<ng-container *ngIf="activeEntryType === entryTypes.FALSE_RECOMMENDATION && !selectedDictionary.hint">
{{
'edit-dossier-dialog.dictionary.false-recommendation-entries'
| translate: { length: entriesToDisplay.length }
}}
</ng-container>
</div>
</div>
</div>
</div>
</div> </div>
<redaction-dictionary-manager <redaction-dictionary-manager
@ -85,14 +57,14 @@
[withFloatingActions]="false" [withFloatingActions]="false"
> >
<ng-container slot="typeSwitch"> <ng-container slot="typeSwitch">
<div *ngIf="!selectedDictionary.hint" [class.read-only]="!canEdit" class="header-right flex"> <div class="flex">
<iqser-icon-button <iqser-icon-button
(click)="selectEntryType(entryTypes.ENTRY)" (click)="selectEntryType(entryTypes.ENTRY, true)"
[active]="activeEntryType === entryTypes.ENTRY" [active]="activeEntryType === entryTypes.ENTRY"
[label]="'edit-dossier-dialog.dictionary.to-redact' | translate: { count: selectedDictionary.entries.length }" [label]="'edit-dossier-dialog.dictionary.to-redact' | translate: { count: selectedDictionary.entries.length }"
></iqser-icon-button> ></iqser-icon-button>
<iqser-icon-button <iqser-icon-button
(click)="selectEntryType(entryTypes.FALSE_POSITIVE)" (click)="selectEntryType(entryTypes.FALSE_POSITIVE, true)"
[active]="activeEntryType === entryTypes.FALSE_POSITIVE" [active]="activeEntryType === entryTypes.FALSE_POSITIVE"
[label]=" [label]="
'edit-dossier-dialog.dictionary.false-positives' 'edit-dossier-dialog.dictionary.false-positives'
@ -100,7 +72,7 @@
" "
></iqser-icon-button> ></iqser-icon-button>
<iqser-icon-button <iqser-icon-button
(click)="selectEntryType(entryTypes.FALSE_RECOMMENDATION)" (click)="selectEntryType(entryTypes.FALSE_RECOMMENDATION, true)"
[active]="activeEntryType === entryTypes.FALSE_RECOMMENDATION" [active]="activeEntryType === entryTypes.FALSE_RECOMMENDATION"
[label]=" [label]="
'edit-dossier-dialog.dictionary.false-recommendations' 'edit-dossier-dialog.dictionary.false-recommendations'

View File

@ -11,13 +11,14 @@
.dictionaries { .dictionaries {
border-right: 1px solid var(--iqser-separator); border-right: 1px solid var(--iqser-separator);
overflow-y: scroll; overflow-y: auto;
width: 200px;
@include common-mixins.scroll-bar; @include common-mixins.scroll-bar;
.dictionary { .dictionary {
height: 40px; height: 40px;
padding: 15px; padding: 15px;
border: 1px solid var(--iqser-separator); border-bottom: 1px solid var(--iqser-separator);
display: flex; display: flex;
gap: 10px; gap: 10px;
cursor: pointer; cursor: pointer;
@ -43,33 +44,7 @@
.entries { .entries {
flex-grow: 1; flex-grow: 1;
padding: 16px 0 16px 12px; padding: 16px 0 16px 12px;
overflow: hidden;
.header-wrapper {
display: flex;
justify-content: space-between;
.header-left {
display: flex;
.iqser-input-group {
margin-left: 24px;
}
}
.read-only {
padding-right: 100px;
}
.display-name {
display: flex;
align-items: center;
margin-bottom: 24px;
> div {
font-weight: 600;
}
}
}
} }
} }

View File

@ -1,5 +1,12 @@
import { Component, Input, OnInit, ViewChild } from '@angular/core'; import { Component, Input, OnInit, ViewChild } from '@angular/core';
import { CircleButtonComponent, IconButtonComponent, IqserDialog, LoadingService } from '@iqser/common-ui'; import {
CircleButtonComponent,
ConfirmationDialogService,
ConfirmOptions,
IconButtonComponent,
IqserDialog,
LoadingService,
} from '@iqser/common-ui';
import { List } from '@iqser/common-ui/lib/utils'; import { List } from '@iqser/common-ui/lib/utils';
import { Dictionary, DictionaryEntryType, DictionaryEntryTypes, Dossier } from '@red/domain'; import { Dictionary, DictionaryEntryType, DictionaryEntryTypes, Dossier } from '@red/domain';
import { DictionaryService } from '@services/entity-services/dictionary.service'; import { DictionaryService } from '@services/entity-services/dictionary.service';
@ -42,6 +49,14 @@ export class EditDossierDictionaryComponent implements OnInit {
readonly entryTypes = DictionaryEntryTypes; readonly entryTypes = DictionaryEntryTypes;
@ViewChild(DictionaryManagerComponent, { static: false }) private readonly _dictionaryManager: DictionaryManagerComponent; @ViewChild(DictionaryManagerComponent, { static: false }) private readonly _dictionaryManager: DictionaryManagerComponent;
constructor(
private readonly _dictionaryService: DictionaryService,
private readonly _permissionsService: PermissionsService,
private readonly _loadingService: LoadingService,
private readonly _iqserDialog: IqserDialog,
private readonly _confirmationDialogService: ConfirmationDialogService,
) {}
get changed(): boolean { get changed(): boolean {
return this._dictionaryManager?.editor.hasChanges; return this._dictionaryManager?.editor.hasChanges;
} }
@ -54,13 +69,6 @@ export class EditDossierDictionaryComponent implements OnInit {
return this._dictionaryManager?.editor.hasChanges; return this._dictionaryManager?.editor.hasChanges;
} }
constructor(
private readonly _dictionaryService: DictionaryService,
private readonly _permissionsService: PermissionsService,
private readonly _loadingService: LoadingService,
private readonly _iqserDialog: IqserDialog,
) {}
async ngOnInit() { async ngOnInit() {
this._loadingService.start(); this._loadingService.start();
this.canEdit = this._permissionsService.canEditDossierDictionary(this.dossier); this.canEdit = this._permissionsService.canEditDossierDictionary(this.dossier);
@ -95,16 +103,23 @@ export class EditDossierDictionaryComponent implements OnInit {
this._dictionaryManager.revert(); this._dictionaryManager.revert();
} }
selectDictionary(dictionary: Dictionary, entryType?: DictionaryEntryType) { async selectDictionary(dictionary: Dictionary, entryType?: DictionaryEntryType, checkForChanges = false) {
if (checkForChanges && !(await this._checkForChanges()).continue) {
return;
}
this.selectedDictionary = dictionary; this.selectedDictionary = dictionary;
this.selectEntryType(entryType); await this.selectEntryType(entryType);
} }
selectEntryType(selectedEntryType: DictionaryEntryType) { async selectEntryType(selectedEntryType: DictionaryEntryType, checkForChanges = false) {
this.activeEntryType = selectedEntryType ?? this.activeEntryType; if (checkForChanges && !(await this._checkForChanges()).continue) {
const entryType = this.selectedDictionary.hint ? DictionaryEntryTypes.ENTRY : this.activeEntryType; return;
}
switch (entryType) { this.activeEntryType = selectedEntryType ?? this.activeEntryType;
switch (this.activeEntryType) {
case DictionaryEntryTypes.ENTRY: { case DictionaryEntryTypes.ENTRY: {
this.entriesToDisplay = this.selectedDictionary.entries; this.entriesToDisplay = this.selectedDictionary.entries;
break; break;
@ -141,13 +156,32 @@ export class EditDossierDictionaryComponent implements OnInit {
await this.#retrieveDictionaries(); await this.#retrieveDictionaries();
} }
private async _checkForChanges(): Promise<{ continue: boolean }> {
if (this.changed) {
const dialogRef = this._confirmationDialogService.open({ disableConfirm: !this.valid });
const result = await firstValueFrom(dialogRef.afterClosed());
if (result === ConfirmOptions.CONFIRM) {
this._loadingService.start();
const { success } = await this.save();
this._loadingService.stop();
if (!success) {
return { continue: false };
}
} else if (!result) {
return { continue: false };
}
}
return { continue: true };
}
async #updateDossierDictionary() { async #updateDossierDictionary() {
await this.#retrieveDictionaries(); await this.#retrieveDictionaries();
let dictionaryToSelect = this.dictionaries[0]; let dictionaryToSelect = this.dictionaries[0];
if (this.selectedDictionary) { if (this.selectedDictionary) {
dictionaryToSelect = this.dictionaries.find(d => d.type === this.selectedDictionary.type); dictionaryToSelect = this.dictionaries.find(d => d.type === this.selectedDictionary.type);
} }
this.selectDictionary(dictionaryToSelect, this.activeEntryType); await this.selectDictionary(dictionaryToSelect, this.activeEntryType);
} }
async #retrieveDictionaries() { async #retrieveDictionaries() {

View File

@ -13,7 +13,8 @@
></iqser-circle-button> ></iqser-circle-button>
<iqser-circle-button <iqser-circle-button
(action)="editor.openFindPanel()" (action)="editor.toggleFindPanel()"
[attr.aria-expanded]="_isSearchOpen()"
[matTooltip]="'dictionary-overview.search' | translate" [matTooltip]="'dictionary-overview.search' | translate"
class="ml-8" class="ml-8"
icon="iqser:search" icon="iqser:search"
@ -92,6 +93,7 @@
<div class="editor-container"> <div class="editor-container">
<redaction-editor <redaction-editor
[(isSearchOpen)]="_isSearchOpen"
[canEdit]="canEdit" [canEdit]="canEdit"
[diffEditorText]="diffEditorText" [diffEditorText]="diffEditorText"
[initialEntries]="initialEntries" [initialEntries]="initialEntries"

View File

@ -1,4 +1,15 @@
import { ChangeDetectorRef, Component, EventEmitter, Input, OnChanges, OnInit, Output, SimpleChanges, ViewChild } from '@angular/core'; import {
ChangeDetectorRef,
Component,
EventEmitter,
Input,
OnChanges,
OnInit,
Output,
signal,
SimpleChanges,
ViewChild,
} from '@angular/core';
import { CircleButtonComponent, IconButtonComponent, IconButtonTypes, LoadingService } from '@iqser/common-ui'; import { CircleButtonComponent, IconButtonComponent, IconButtonTypes, LoadingService } from '@iqser/common-ui';
import { Dictionary, DictionaryEntryType, DictionaryEntryTypes, DictionaryType, Dossier, DossierTemplate, IDictionary } from '@red/domain'; import { Dictionary, DictionaryEntryType, DictionaryEntryTypes, DictionaryType, Dossier, DossierTemplate, IDictionary } from '@red/domain';
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker'; import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
@ -77,6 +88,7 @@ export class DictionaryManagerComponent implements OnChanges, OnInit {
selectDossierTemplate = { name: _('dictionary-overview.compare.select-dossier-template') } as DossierTemplate; selectDossierTemplate = { name: _('dictionary-overview.compare.select-dossier-template') } as DossierTemplate;
compare = false; compare = false;
dictionaries: List<Dictionary> = this.#dictionaries; dictionaries: List<Dictionary> = this.#dictionaries;
protected readonly _isSearchOpen = signal(false);
protected initialDossierTemplateId: string; protected initialDossierTemplateId: string;
readonly #currentTab = window.location.href.split('/').pop(); readonly #currentTab = window.location.href.split('/').pop();
#dossierTemplate = this.dossierTemplatesService.all[0]; #dossierTemplate = this.dossierTemplatesService.all[0];

View File

@ -1,4 +1,4 @@
import { Component, Input, OnChanges, OnInit, SimpleChanges } from '@angular/core'; import { Component, Input, model, OnChanges, OnInit, SimpleChanges, untracked } from '@angular/core';
import { LoadingService } from '@iqser/common-ui'; import { LoadingService } from '@iqser/common-ui';
import { EditorThemeService } from '@services/editor-theme.service'; import { EditorThemeService } from '@services/editor-theme.service';
import { Subject } from 'rxjs'; import { Subject } from 'rxjs';
@ -39,6 +39,7 @@ export class EditorComponent implements OnInit, OnChanges {
@Input() diffEditorText: string; @Input() diffEditorText: string;
@Input() @OnChange<List, EditorComponent>('revert') initialEntries: List; @Input() @OnChange<List, EditorComponent>('revert') initialEntries: List;
@Input() canEdit = false; @Input() canEdit = false;
readonly isSearchOpen = model.required<boolean>();
/** /**
* Used as [modified] input on diff editor * Used as [modified] input on diff editor
* Shouldn't be updated when editing in diff editor. * Shouldn't be updated when editing in diff editor.
@ -84,9 +85,14 @@ export class EditorComponent implements OnInit, OnChanges {
return this.currentEntries.length; return this.currentEntries.length;
} }
async openFindPanel(): Promise<void> { async toggleFindPanel(): Promise<void> {
const isFindPanelOpen = untracked(this.isSearchOpen);
const editor = this.showDiffEditor ? this._diffEditor.getOriginalEditor() : this.codeEditor; const editor = this.showDiffEditor ? this._diffEditor.getOriginalEditor() : this.codeEditor;
await editor.getAction('actions.find').run(); if (isFindPanelOpen) {
await (editor.getContribution('editor.contrib.findController') as any).closeFindWidget();
} else {
await editor.getAction('actions.find').run();
}
} }
onPaste(event: ClipboardEvent) { onPaste(event: ClipboardEvent) {
@ -127,11 +133,13 @@ export class EditorComponent implements OnInit, OnChanges {
this._diffEditor.getModifiedEditor().onDidChangeModelContent(() => { this._diffEditor.getModifiedEditor().onDidChangeModelContent(() => {
this.value = this._diffEditor.getModel().modified.getValue(); this.value = this._diffEditor.getModel().modified.getValue();
}); });
this._initializeFindWidget(editor.getOriginalEditor());
this.#setTheme(); this.#setTheme();
} }
onCodeEditorInit(editor: MonacoStandaloneCodeEditor): void { onCodeEditorInit(editor: MonacoStandaloneCodeEditor): void {
this.codeEditor = editor; this.codeEditor = editor;
this._initializeFindWidget(editor);
this.#setTheme(); this.#setTheme();
} }
@ -143,6 +151,15 @@ export class EditorComponent implements OnInit, OnChanges {
this._editorTextChanged$.next(this.value); this._editorTextChanged$.next(this.value);
} }
private _initializeFindWidget(editor: MonacoStandaloneCodeEditor): void {
this.isSearchOpen.set(false);
(editor.getContribution('editor.contrib.findController') as any).getState().onFindReplaceStateChange(event => {
if (event.isRevealed) {
this.isSearchOpen.update(v => !v);
}
});
}
#getDecorations(newText: string) { #getDecorations(newText: string) {
const currentEntries = newText.split('\n'); const currentEntries = newText.split('\n');
const newDecorations: IModelDeltaDecoration[] = []; const newDecorations: IModelDeltaDecoration[] = [];

View File

@ -1234,11 +1234,8 @@
"save": "Speichern", "save": "Speichern",
"title": "{label} bearbeiten" "title": "{label} bearbeiten"
}, },
"entries": "{length} {length, plural, one{Eintrag} other{Einträge}}",
"entries-count": "", "entries-count": "",
"false-positive-entries": "{length} {length, plural, one{Falsch-Positiver} other{Falsch-Positive}}",
"false-positives": "Falsch-Positive ({count})", "false-positives": "Falsch-Positive ({count})",
"false-recommendation-entries": "{length} {length, plural, one{falsche Empfehlung} other{falsche Empfehlungen}}",
"false-recommendations": "Falsche Empfehlungen ({count})", "false-recommendations": "Falsche Empfehlungen ({count})",
"to-redact": "Schwärzungen ({count})" "to-redact": "Schwärzungen ({count})"
}, },

View File

@ -1234,11 +1234,8 @@
"save": "Save", "save": "Save",
"title": "Edit {label}" "title": "Edit {label}"
}, },
"entries": "{length} {length, plural, one{entry} other{entries}} to redact",
"entries-count": "{count} {count, plural, one{entry} other{entries}}", "entries-count": "{count} {count, plural, one{entry} other{entries}}",
"false-positive-entries": "{length} false positive {length, plural, one{entry} other{entries}}",
"false-positives": "False positives ({count})", "false-positives": "False positives ({count})",
"false-recommendation-entries": "{length} false recommendation {length, plural, one{entry} other{entries}}",
"false-recommendations": "False recommendations ({count})", "false-recommendations": "False recommendations ({count})",
"to-redact": "Entries ({count})" "to-redact": "Entries ({count})"
}, },

View File

@ -1234,11 +1234,8 @@
"save": "", "save": "",
"title": "" "title": ""
}, },
"entries": "{length} {length, plural, one{entry} other{entries}} to {hint, select, true{annotate} other{redact}}",
"entries-count": "", "entries-count": "",
"false-positive-entries": "{length} false positive {length, plural, one{entry} other{entries}}",
"false-positives": "False positives ({count})", "false-positives": "False positives ({count})",
"false-recommendation-entries": "{length} false recommendation {length, plural, one{entry} other{entries}}",
"false-recommendations": "False recommendations ({count})", "false-recommendations": "False recommendations ({count})",
"to-redact": "To redact ({count})" "to-redact": "To redact ({count})"
}, },

View File

@ -1234,11 +1234,8 @@
"save": "", "save": "",
"title": "" "title": ""
}, },
"entries": "{length} {length, plural, one{entry} other{entries}} to {hint, select, true{annotate} other{redact}}",
"entries-count": "{count} {count, plural, one{entry} other{entries}}", "entries-count": "{count} {count, plural, one{entry} other{entries}}",
"false-positive-entries": "{length} false positive {length, plural, one{entry} other{entries}}",
"false-positives": "False positives ({count})", "false-positives": "False positives ({count})",
"false-recommendation-entries": "{length} false recommendation {length, plural, one{entry} other{entries}}",
"false-recommendations": "False recommendations ({count})", "false-recommendations": "False recommendations ({count})",
"to-redact": "To redact ({count})" "to-redact": "To redact ({count})"
}, },