Merge branch 'RED-8904' into 'master'

Resolve RED-8904

Closes RED-8904

See merge request redactmanager/red-ui!385
This commit is contained in:
Dan Percic 2024-04-10 15:32:19 +02:00
commit 60977c9306
27 changed files with 257 additions and 178 deletions

View File

@ -0,0 +1,5 @@
<cdk-virtual-scroll-viewport [itemSize]="LIST_ITEM_SIZE" [ngStyle]="{ height: redactedTextsAreaHeight + 'px' }">
<ul>
<li *cdkVirtualFor="let value of values">{{ value }}</li>
</ul>
</cdk-virtual-scroll-viewport>

View File

@ -0,0 +1,21 @@
@use 'common-mixins';
cdk-virtual-scroll-viewport {
@include common-mixins.scroll-bar;
}
:host ::ng-deep .cdk-virtual-scroll-orientation-vertical .cdk-virtual-scroll-content-wrapper {
max-width: 100% !important;
}
ul {
padding-left: 16px;
li {
white-space: nowrap;
text-overflow: ellipsis;
list-style-position: inside;
overflow: hidden;
padding-right: 10px;
}
}

View File

@ -0,0 +1,22 @@
import { Component, Input } from '@angular/core';
import { CdkFixedSizeVirtualScroll, CdkVirtualForOf, CdkVirtualScrollViewport } from '@angular/cdk/scrolling';
import { NgStyle } from '@angular/common';
const LIST_ITEM_SIZE = 16;
const MAX_ITEMS_DISPLAY = 5;
@Component({
selector: 'redaction-selected-annotations-list',
standalone: true,
imports: [CdkFixedSizeVirtualScroll, CdkVirtualForOf, CdkVirtualScrollViewport, NgStyle],
templateUrl: './selected-annotations-list.component.html',
styleUrl: './selected-annotations-list.component.scss',
})
export class SelectedAnnotationsListComponent {
@Input({ required: true }) values: string[];
protected readonly LIST_ITEM_SIZE = LIST_ITEM_SIZE;
get redactedTextsAreaHeight() {
return this.values.length <= MAX_ITEMS_DISPLAY ? LIST_ITEM_SIZE * this.values.length : LIST_ITEM_SIZE * MAX_ITEMS_DISPLAY;
}
}

View File

@ -0,0 +1,16 @@
<table>
<thead>
<tr>
<th *ngFor="let column of columns" [ngClass]="{ hide: !column.show }">
<label>{{ column.label }}</label>
</th>
</tr>
</thead>
<tbody [ngStyle]="{ height: redactedTextsAreaHeight + 'px' }">
<tr *ngFor="let row of data">
<td *ngFor="let cell of row" [ngClass]="{ hide: !cell.show, bold: cell.bold }">
{{ cell.label }}
</td>
</tr>
</tbody>
</table>

View File

@ -0,0 +1,63 @@
@use 'common-mixins';
table {
padding: 0 13px;
max-width: 100%;
min-width: 100%;
border-spacing: 0;
tbody {
padding-top: 2px;
overflow-y: auto;
display: block;
@include common-mixins.scroll-bar;
}
tr {
max-width: 100%;
min-width: 100%;
display: table;
th {
label {
opacity: 0.7;
font-weight: normal;
}
}
th,
td {
max-width: 0;
width: 25%;
text-align: start;
white-space: nowrap;
text-overflow: ellipsis;
list-style-position: inside;
overflow: hidden;
padding-right: 8px;
}
th:last-child,
td:last-child {
max-width: 0;
width: 50%;
padding-right: 0;
}
}
}
tbody tr:nth-child(odd) {
td {
background-color: var(--iqser-alt-background);
}
}
.hide {
visibility: hidden;
}
.bold {
font-weight: bold;
}

View File

@ -0,0 +1,27 @@
import { Component, Input } from '@angular/core';
import { NgClass, NgForOf, NgStyle } from '@angular/common';
export interface ValueColumn {
label: string;
show: boolean;
bold?: boolean;
}
const TABLE_ROW_SIZE = 18;
const MAX_ITEMS_DISPLAY = 10;
@Component({
selector: 'redaction-selected-annotations-table',
standalone: true,
imports: [NgForOf, NgClass, NgStyle],
templateUrl: './selected-annotations-table.component.html',
styleUrl: './selected-annotations-table.component.scss',
})
export class SelectedAnnotationsTableComponent {
@Input({ required: true }) columns: ValueColumn[];
@Input({ required: true }) data: ValueColumn[][];
get redactedTextsAreaHeight() {
return this.data.length <= MAX_ITEMS_DISPLAY ? TABLE_ROW_SIZE * this.data.length : TABLE_ROW_SIZE * MAX_ITEMS_DISPLAY;
}
}

View File

@ -5,14 +5,7 @@
<div class="dialog-content redaction">
<div class="iqser-input-group" *ngIf="showList">
<label [translate]="'edit-redaction.dialog.content.redacted-text'" class="selected-text"></label>
<cdk-virtual-scroll-viewport
[itemSize]="16"
[ngStyle]="{ height: redactedTexts.length <= 5 ? 16 * redactedTexts.length + 'px' : 80 + 'px' }"
>
<ul *cdkVirtualFor="let text of redactedTexts">
<li>{{ text }}</li>
</ul>
</cdk-virtual-scroll-viewport>
<redaction-selected-annotations-list [values]="redactedTexts"></redaction-selected-annotations-list>
</div>
<div class="iqser-input-group required w-450">

View File

@ -1,21 +0,0 @@
@use 'common-mixins';
cdk-virtual-scroll-viewport {
@include common-mixins.scroll-bar;
}
:host ::ng-deep .cdk-virtual-scroll-orientation-vertical .cdk-virtual-scroll-content-wrapper {
max-width: 100% !important;
}
ul {
padding-left: 16px;
}
li {
white-space: nowrap;
text-overflow: ellipsis;
list-style-position: inside;
overflow: hidden;
padding-right: 10px;
}

View File

@ -7,16 +7,8 @@
></div>
<div class="dialog-content redaction" [class.fixed-height]="isRedacted && isImage">
<div class="iqser-input-group" *ngIf="!isImage">
<cdk-virtual-scroll-viewport
*ngIf="!!redactedTexts.length && !allRectangles"
[itemSize]="16"
[ngStyle]="{ height: redactedTexts.length <= 5 ? 16 * redactedTexts.length + 'px' : 80 + 'px' }"
>
<ul *cdkVirtualFor="let text of redactedTexts">
<li>{{ text }}</li>
</ul>
</cdk-virtual-scroll-viewport>
<div class="iqser-input-group" *ngIf="!isImage && redactedTexts.length && !allRectangles">
<redaction-selected-annotations-list [values]="redactedTexts"></redaction-selected-annotations-list>
</div>
<div *ngIf="!isManualRedaction" class="iqser-input-group w-450" [class.required]="!form.controls.type.disabled">

View File

@ -6,23 +6,3 @@
overflow-y: auto;
}
}
cdk-virtual-scroll-viewport {
@include common-mixins.scroll-bar;
}
:host ::ng-deep .cdk-virtual-scroll-orientation-vertical .cdk-virtual-scroll-content-wrapper {
max-width: 100% !important;
}
ul {
padding-left: 16px;
}
li {
white-space: nowrap;
text-overflow: ellipsis;
list-style-position: inside;
overflow: hidden;
padding-right: 10px;
}

View File

@ -4,6 +4,7 @@
<div *ngIf="isHintDialog" class="dialog-header heading-l" [translate]="'manual-annotation.dialog.header.force-hint'"></div>
<div class="dialog-content">
<redaction-selected-annotations-table [columns]="tableColumns" [data]="tableData"></redaction-selected-annotations-table>
<div *ngIf="!isHintDialog && !isDocumine" class="iqser-input-group required w-400">
<label [translate]="'manual-annotation.dialog.content.reason'"></label>
<mat-form-field>

View File

@ -1,3 +1,7 @@
.full-width {
width: 100%;
}
.dialog-content {
padding-top: 8px;
}

View File

@ -7,6 +7,7 @@ import { Dossier, ILegalBasisChangeRequest } from '@red/domain';
import { firstValueFrom } from 'rxjs';
import { AnnotationWrapper } from '@models/file/annotation.wrapper';
import { Roles } from '@users/roles';
import { ValueColumn } from '../../components/selected-annotations-table/selected-annotations-table.component';
export interface LegalBasisOption {
label?: string;
@ -23,8 +24,24 @@ const DOCUMINE_LEGAL_BASIS = 'n-a.';
})
export class ForceAnnotationDialogComponent extends BaseDialogComponent implements OnInit {
readonly isDocumine = getConfig().IS_DOCUMINE;
protected readonly roles = Roles;
readonly tableColumns = [
{
label: 'Value',
show: true,
},
{
label: 'Type',
show: true,
},
];
readonly tableData: ValueColumn[][] = this._data.annotations.map(redaction => [
{ label: redaction.value, show: true, bold: true },
{ label: redaction.typeLabel, show: true },
]);
legalOptions: LegalBasisOption[] = [];
protected readonly roles = Roles;
constructor(
private readonly _justificationsService: JustificationsService,
@ -33,7 +50,7 @@ export class ForceAnnotationDialogComponent extends BaseDialogComponent implemen
private readonly _data: { readonly dossier: Dossier; readonly hint: boolean; annotations: AnnotationWrapper[] },
) {
super(_dialogRef);
this.form = this._getForm();
this.form = this.#getForm();
}
get isHintDialog() {
@ -66,17 +83,17 @@ export class ForceAnnotationDialogComponent extends BaseDialogComponent implemen
}
save() {
this._dialogRef.close(this._createForceRedactionRequest());
this._dialogRef.close(this.#createForceRedactionRequest());
}
private _getForm(): UntypedFormGroup {
#getForm(): UntypedFormGroup {
return this._formBuilder.group({
reason: this._data.hint ? ['Forced Hint'] : [null, !this.isDocumine ? Validators.required : null],
comment: [null],
});
}
private _createForceRedactionRequest(): ILegalBasisChangeRequest {
#createForceRedactionRequest(): ILegalBasisChangeRequest {
const request: ILegalBasisChangeRequest = {};
request.legalBasis = !this.isDocumine ? this.form.get('reason').value.legalBasis : DOCUMINE_LEGAL_BASIS;

View File

@ -3,9 +3,8 @@
<div [translate]="'redact-text.dialog.title'" class="dialog-header heading-l"></div>
<div class="dialog-content redaction">
<div *ngIf="form.controls.selectedText.value as selectedText" class="iqser-input-group w-450">
<label [translate]="'redact-text.dialog.content.selected-text'" class="selected-text"></label>
{{ selectedText }}
<div class="iqser-input-group">
<redaction-selected-annotations-list [values]="selectedValues"></redaction-selected-annotations-list>
</div>
<iqser-details-radio

View File

@ -30,6 +30,7 @@ export class RedactRecommendationDialogComponent
dictionaryRequest = false;
legalOptions: LegalBasisOption[] = [];
dictionaries: Dictionary[] = [];
readonly selectedValues = this.data.annotations.map(annotation => annotation.value);
readonly form = inject(FormBuilder).group({
selectedText: this.isMulti ? null : this.firstEntry.value,
comment: [null],

View File

@ -8,7 +8,11 @@
[class.fixed-height-36]="dictionaryRequest"
[ngClass]="isEditingSelectedText ? 'flex relative' : 'flex-align-items-center'"
>
<span *ngIf="!isEditingSelectedText" [innerHTML]="form.controls.selectedText.value"></span>
<ul>
<li>
<span *ngIf="!isEditingSelectedText" [innerHTML]="form.controls.selectedText.value"></span>
</li>
</ul>
<textarea
*ngIf="isEditingSelectedText"

View File

@ -7,24 +7,10 @@
<div [ngStyle]="{ height: dialogContentHeight + redactedTextsAreaHeight + 'px' }" class="dialog-content redaction">
<div class="iqser-input-group">
<table>
<thead>
<tr>
<th *ngFor="let column of columns()" [ngClass]="{ hide: !column.show }">
<label>{{ column.label }}</label>
</th>
</tr>
</thead>
<tbody [ngStyle]="{ height: redactedTextsAreaHeight + 'px' }">
<tr *ngFor="let text of redactedTexts; let idx = index">
<td>
<b>{{ text }}</b>
</td>
<td>{{ data.redactions[idx].typeLabel }}</td>
<td [ngClass]="{ hide: !isFalsePositive() }">{{ data.falsePositiveContext[idx] }}</td>
</tr>
</tbody>
</table>
<redaction-selected-annotations-table
[columns]="tableColumns()"
[data]="tableData()"
></redaction-selected-annotations-table>
</div>
<iqser-details-radio [options]="options" formControlName="option"></iqser-details-radio>

View File

@ -1,64 +1,4 @@
@use 'common-mixins';
.dialog-content {
padding-top: 8px;
padding-bottom: 35px;
}
table {
padding: 0 13px;
max-width: 100%;
min-width: 100%;
border-spacing: 0;
tbody {
padding-top: 2px;
overflow-y: auto;
display: block;
@include common-mixins.scroll-bar;
}
tr {
max-width: 100%;
min-width: 100%;
display: table;
th {
label {
opacity: 0.7;
font-weight: normal;
}
}
th,
td {
max-width: 0;
width: 25%;
text-align: start;
white-space: nowrap;
text-overflow: ellipsis;
list-style-position: inside;
overflow: hidden;
padding-right: 8px;
}
th:last-child,
td:last-child {
max-width: 0;
width: 50%;
padding-right: 0;
}
}
}
tbody tr:nth-child(odd) {
td {
background-color: var(--iqser-alt-background);
}
}
.hide {
visibility: hidden;
}

View File

@ -7,11 +7,7 @@ import { Roles } from '@users/roles';
import { DialogHelpModeKeys } from '../../utils/constants';
import { toSignal } from '@angular/core/rxjs-interop';
import { map } from 'rxjs/operators';
interface ValuesColumns {
label: string;
show: boolean;
}
import { ValueColumn } from '../../components/selected-annotations-table/selected-annotations-table.component';
@Component({
templateUrl: './remove-redaction-dialog.component.html',
@ -37,7 +33,7 @@ export class RemoveRedactionDialogComponent extends IqserDialogComponent<
readonly selectedOption = toSignal(this.form.get('option').valueChanges.pipe(map(value => value.value)));
readonly isFalsePositive = computed(() => this.selectedOption() === RemoveRedactionOptions.FALSE_POSITIVE);
readonly columns = computed<ValuesColumns[]>(() => [
readonly tableColumns = computed<ValueColumn[]>(() => [
{
label: 'Value',
show: true,
@ -52,6 +48,14 @@ export class RemoveRedactionDialogComponent extends IqserDialogComponent<
},
]);
readonly tableData = computed<ValueColumn[][]>(() =>
this.data.redactions.map((redaction, index) => [
{ label: redaction.value, show: true, bold: true },
{ label: redaction.typeLabel, show: true },
{ label: this.data.falsePositiveContext[index], show: this.isFalsePositive() },
]),
);
constructor(private readonly _formBuilder: FormBuilder) {
super();
}

View File

@ -7,14 +7,18 @@
<div class="dialog-content redaction">
<ng-container *ngIf="!redaction.isImage && !redaction.AREA">
<div class="iqser-input-group w-450">
<label [translate]="'resize-redaction.dialog.content.original-text'" class="selected-text"></label>
<span>{{ redaction.value }}</span>
<div class="flex-start">
<label [translate]="'resize-redaction.dialog.content.original-text'"></label>
<span class="multi-line-ellipsis"
><b>{{ redaction.value }}</b></span
>
</div>
<div class="iqser-input-group w-450">
<label [translate]="'resize-redaction.dialog.content.resized-text'" class="selected-text"></label>
<span>{{ data.text }}</span>
<div class="flex-start">
<label [translate]="'resize-redaction.dialog.content.resized-text'"></label>
<span class="multi-line-ellipsis"
><b>{{ data.text }}</b></span
>
</div>
</ng-container>

View File

@ -0,0 +1,20 @@
.multi-line-ellipsis {
-webkit-box-orient: vertical;
display: -webkit-box;
-webkit-line-clamp: 2;
overflow: hidden;
text-overflow: ellipsis;
white-space: normal;
}
.flex-start {
display: flex;
align-items: start;
padding: 0 13px;
label {
opacity: 0.7;
font-weight: normal;
min-width: 15%;
}
}

View File

@ -8,6 +8,7 @@ import { ResizeRedactionData, ResizeRedactionResult } from '../../utils/dialog-t
@Component({
templateUrl: './resize-redaction-dialog.component.html',
styleUrls: ['./resize-redaction-dialog.component.scss'],
})
export class ResizeRedactionDialogComponent extends IqserDialogComponent<
ResizeRedactionDialogComponent,

View File

@ -70,6 +70,8 @@ import { DocumentUnloadedGuard } from './services/document-unloaded.guard';
import { FilePreviewDialogService } from './services/file-preview-dialog.service';
import { ManualRedactionService } from './services/manual-redaction.service';
import { TablesService } from './services/tables.service';
import { SelectedAnnotationsTableComponent } from './components/selected-annotations-table/selected-annotations-table.component';
import { SelectedAnnotationsListComponent } from './components/selected-annotations-list/selected-annotations-list.component';
const routes: IqserRoutes = [
{
@ -152,6 +154,8 @@ const components = [
LogPipe,
ReplaceNbspPipe,
DisableStopPropagationDirective,
SelectedAnnotationsTableComponent,
SelectedAnnotationsListComponent,
],
providers: [FilePreviewDialogService, ManualRedactionService, DocumentUnloadedGuard, TablesService],
})

View File

@ -1988,7 +1988,6 @@
"reason": "Reason",
"reason-placeholder": "Select a reason...",
"revert-text": "Revert to selected text",
"selected-text": "Selected text:",
"type": "Type",
"type-placeholder": "Select type..."
},
@ -2141,8 +2140,8 @@
},
"content": {
"comment": "Comment",
"original-text": "Original annotation:",
"resized-text": "Resized annotation:"
"original-text": "Original",
"resized-text": "Resized"
},
"header": "Resize annotation"
}
@ -2167,8 +2166,8 @@
"label": "Resize only here"
}
},
"original-text": "Original text:",
"resized-text": "Resized text:",
"original-text": "Original",
"resized-text": "Resized",
"type": "Type"
},
"header": "Resize {type}"

View File

@ -1988,7 +1988,6 @@
"reason": "Reason",
"reason-placeholder": "Select a reason...",
"revert-text": "Revert to selected text",
"selected-text": "Selected text:",
"type": "Type",
"type-placeholder": "Select type..."
},
@ -2141,8 +2140,8 @@
},
"content": {
"comment": "Comment",
"original-text": "Original annotation:",
"resized-text": "Resized annotation:"
"original-text": "Original",
"resized-text": "Resized"
},
"header": "Resize annotation"
}
@ -2167,8 +2166,8 @@
"label": "Resize only here"
}
},
"original-text": "Original text:",
"resized-text": "Resized text:",
"original-text": "Original",
"resized-text": "Resized",
"type": "Type"
},
"header": "Resize {type}"

View File

@ -1988,7 +1988,6 @@
"reason": "Reason",
"reason-placeholder": "Select a reasons...",
"revert-text": "",
"selected-text": "Selected text:",
"type": "Type",
"type-placeholder": "Select type..."
},
@ -2141,8 +2140,8 @@
},
"content": {
"comment": "Comment",
"original-text": "Original annotation:",
"resized-text": "Resized annotation:"
"original-text": "Original",
"resized-text": "Resized"
},
"header": "Resize annotation"
}
@ -2167,8 +2166,8 @@
"label": "Resize only here"
}
},
"original-text": "Original text:",
"resized-text": "Resized text:",
"original-text": "Original",
"resized-text": "Resized",
"type": "Type"
},
"header": "Resize {type}"

View File

@ -1988,7 +1988,6 @@
"reason": "Reason",
"reason-placeholder": "Select a reasons...",
"revert-text": "",
"selected-text": "Selected text:",
"type": "Type",
"type-placeholder": "Select type..."
},
@ -2141,8 +2140,8 @@
},
"content": {
"comment": "Comment",
"original-text": "Original annotation:",
"resized-text": "Resized annotation:"
"original-text": "Original",
"resized-text": "Resized"
},
"header": "Resize annotation"
}
@ -2167,8 +2166,8 @@
"label": "Resize only here"
}
},
"original-text": "Original text:",
"resized-text": "Resized text:",
"original-text": "Original",
"resized-text": "Resized",
"type": "Type"
},
"header": "Resize {type}"