RED-10139: rid file-preview module of anything resembling getters.

This commit is contained in:
Nicoleta Panaghiu 2024-10-16 13:46:19 +03:00
parent d06cd4be54
commit f00a92a5ea
54 changed files with 402 additions and 449 deletions

View File

@ -22,8 +22,7 @@ import { MatFormField } from '@angular/material/form-field';
import { MatOption, MatSelect, MatSelectTrigger } from '@angular/material/select'; import { MatOption, MatSelect, MatSelectTrigger } from '@angular/material/select';
import { MatSlideToggle } from '@angular/material/slide-toggle'; import { MatSlideToggle } from '@angular/material/slide-toggle';
import { PdfViewer } from '../../../../pdf-viewer/services/pdf-viewer.service'; import { PdfViewer } from '../../../../pdf-viewer/services/pdf-viewer.service';
import { formControlToSignal } from '@utils/functions'; import { AsControl, formValueToSignal } from '@common-ui/utils';
import { AsControl } from '@common-ui/utils';
interface UserProfileForm { interface UserProfileForm {
email: string; email: string;
@ -59,7 +58,7 @@ export class UserProfileScreenComponent extends BaseFormComponent {
readonly profileKeys = ['email', 'firstName', 'lastName']; readonly profileKeys = ['email', 'firstName', 'lastName'];
readonly languages = this._translateService.langs; readonly languages = this._translateService.langs;
readonly language = formControlToSignal<UserProfileForm['language']>(this.form.controls.language); readonly language = formValueToSignal<UserProfileForm['language']>(this.form.controls.language);
readonly languageSelectLabel = computed(() => this.translations[this.language()]); readonly languageSelectLabel = computed(() => this.translations[this.language()]);
constructor( constructor(

View File

@ -1,19 +1,19 @@
<div class="needs-work"> <div class="needs-work">
<redaction-annotation-icon <redaction-annotation-icon
*ngIf="file.analysisRequired" *ngIf="file().analysisRequired"
[color]="analysisColor$ | async" [color]="analysisColor$ | async"
label="A" label="A"
type="square" type="square"
></redaction-annotation-icon> ></redaction-annotation-icon>
<redaction-annotation-icon *ngIf="updated" [color]="updatedColor$ | async" label="U" type="square"></redaction-annotation-icon> <redaction-annotation-icon *ngIf="updated()" [color]="updatedColor$ | async" label="U" type="square"></redaction-annotation-icon>
<redaction-annotation-icon <redaction-annotation-icon
*ngIf="file.hasRedactions" *ngIf="file().hasRedactions"
[color]="redactionColor$ | async" [color]="redactionColor$ | async"
[label]="'redaction-abbreviation' | translate" [label]="'redaction-abbreviation' | translate"
type="square" type="square"
></redaction-annotation-icon> ></redaction-annotation-icon>
<redaction-annotation-icon *ngIf="file.hasImages" [color]="imageColor$ | async" label="I" type="square"></redaction-annotation-icon> <redaction-annotation-icon *ngIf="file().hasImages" [color]="imageColor$ | async" label="I" type="square"></redaction-annotation-icon>
<redaction-annotation-icon *ngIf="file.hintsOnly" [color]="hintColor$ | async" label="H" type="circle"></redaction-annotation-icon> <redaction-annotation-icon *ngIf="file().hintsOnly" [color]="hintColor$ | async" label="H" type="circle"></redaction-annotation-icon>
<mat-icon *ngIf="file.hasAnnotationComments" svgIcon="red:comment"></mat-icon> <mat-icon *ngIf="file().hasAnnotationComments" svgIcon="red:comment"></mat-icon>
<ng-container *ngIf="noWorkloadItems"> -</ng-container> <ng-container *ngIf="noWorkloadItems()"> -</ng-container>
</div> </div>

View File

@ -1,4 +1,4 @@
import { ChangeDetectionStrategy, Component, Input, OnInit } from '@angular/core'; import { ChangeDetectionStrategy, Component, computed, input, Input, OnInit } from '@angular/core';
import { annotationDefaultColorConfig, DefaultBasedColorType, File } from '@red/domain'; import { annotationDefaultColorConfig, DefaultBasedColorType, File } from '@red/domain';
import { DossiersService } from '@services/dossiers/dossiers.service'; import { DossiersService } from '@services/dossiers/dossiers.service';
import { DefaultColorsService } from '@services/entity-services/default-colors.service'; import { DefaultColorsService } from '@services/entity-services/default-colors.service';
@ -21,13 +21,27 @@ import { MatIcon } from '@angular/material/icon';
}) })
export class FileWorkloadComponent implements OnInit { export class FileWorkloadComponent implements OnInit {
#dossierTemplateId: string; #dossierTemplateId: string;
@Input() file: File; file = input<File>();
imageColor$: Observable<string>; imageColor$: Observable<string>;
updatedColor$: Observable<string>; updatedColor$: Observable<string>;
analysisColor$: Observable<string>; analysisColor$: Observable<string>;
hintColor$: Observable<string>; hintColor$: Observable<string>;
redactionColor$: Observable<string>; redactionColor$: Observable<string>;
readonly updated = computed(
() => this.file().hasUpdates && this.file().assignee === this._userService.currentUser.id && !this.file().isApproved,
);
readonly noWorkloadItems = computed(
() =>
!this.updated() &&
!this.file().analysisRequired &&
!this.file().hasRedactions &&
!this.file().hasImages &&
!this.file().hintsOnly &&
!this.file().hasAnnotationComments,
);
constructor( constructor(
private readonly _userService: UserService, private readonly _userService: UserService,
private readonly _defaultColorsService: DefaultColorsService, private readonly _defaultColorsService: DefaultColorsService,
@ -35,23 +49,8 @@ export class FileWorkloadComponent implements OnInit {
private readonly _dictionariesMapService: DictionariesMapService, private readonly _dictionariesMapService: DictionariesMapService,
) {} ) {}
get updated(): boolean {
return this.file.hasUpdates && this.file.assignee === this._userService.currentUser.id && !this.file.isApproved;
}
get noWorkloadItems(): boolean {
return (
!this.updated &&
!this.file.analysisRequired &&
!this.file.hasRedactions &&
!this.file.hasImages &&
!this.file.hintsOnly &&
!this.file.hasAnnotationComments
);
}
ngOnInit() { ngOnInit() {
this.#dossierTemplateId = this._dossiersService.find(this.file.dossierId).dossierTemplateId; this.#dossierTemplateId = this._dossiersService.find(this.file().dossierId).dossierTemplateId;
this.updatedColor$ = this.#getDefaultColor$('updated'); this.updatedColor$ = this.#getDefaultColor$('updated');
this.analysisColor$ = this.#getDefaultColor$('analysis'); this.analysisColor$ = this.#getDefaultColor$('analysis');
this.hintColor$ = this.#getDefaultColor$('hint'); this.hintColor$ = this.#getDefaultColor$('hint');

View File

@ -60,7 +60,7 @@ export class AnnotationActionsComponent {
AnnotationPermissions.forUser( AnnotationPermissions.forUser(
this._permissionsService.isApprover(this._state.dossier()), this._permissionsService.isApprover(this._state.dossier()),
this.annotations(), this.annotations(),
this._state.dictionaries, untracked(this._state.dictionaries),
this._iqserPermissionsService, this._iqserPermissionsService,
this._state.file().excludedFromAutomaticAnalysis, this._state.file().excludedFromAutomaticAnalysis,
), ),

View File

@ -1,44 +1,42 @@
<div class="details"> <div class="details">
<redaction-annotation-icon <redaction-annotation-icon
[color]="annotation.color" [color]="annotation().color"
[label]="annotation.isEarmark ? '' : ((annotationTypesTranslations[annotation.superType] | translate)?.[0] | uppercase)" [label]="isEarmark() ? '' : ((annotationTypesTranslations[annotation().superType] | translate)?.[0] | uppercase)"
[type]="annotation.iconShape" [type]="iconShape()"
class="mt-6 mr-10" class="mt-6 mr-10"
></redaction-annotation-icon> ></redaction-annotation-icon>
<div class="flex-1"> <div class="flex-1">
<div> <div>
<strong>{{ annotation.superTypeLabel | translate }}</strong> <strong>{{ annotation().superTypeLabel | translate }}</strong>
&nbsp; &nbsp;
<strong *ngIf="annotation.pending" class="pending-analysis"> <strong *ngIf="annotation().pending" class="pending-analysis">
{{ 'annotation.pending' | translate }} {{ 'annotation().pending' | translate }}
</strong> </strong>
</div> </div>
<div *ngIf="annotation.typeLabel"> <div *ngIf="annotation().typeLabel">
<strong> <strong>
<span>{{ annotation.descriptor | translate }}</span <span>{{ descriptor() | translate }}</span
>: >:
</strong> </strong>
{{ annotation.typeLabel }} {{ annotation().typeLabel }}
</div> </div>
<div <div *deny="roles.getRss; if: !!annotation().shortContent && !isHint() && !isSkipped() && !isIgnoredHint()">
*deny="roles.getRss; if: !!annotation.shortContent && !annotation.isHint && !annotation.isSkipped && !annotation.isIgnoredHint" <strong><span translate="content"></span>: </strong>{{ annotation().shortContent }}
>
<strong><span translate="content"></span>: </strong>{{ annotation.shortContent }}
</div> </div>
<div *ngIf="annotation.isEarmark"> <div *ngIf="isEarmark()">
<strong><span translate="color"></span>: </strong>{{ annotation.color }} <strong><span translate="color"></span>: </strong>{{ annotation().color }}
</div> </div>
<div *ngIf="annotation.isEarmark"> <div *ngIf="isEarmark()">
<strong><span translate="size"></span>: </strong>{{ annotation.width }}x{{ annotation.height }} px <strong><span translate="size"></span>: </strong>{{ width() }}x{{ height() }} px
</div> </div>
</div> </div>
<div class="active-icon-marker-container"> <div class="active-icon-marker-container">
<iqser-round-checkbox *ngIf="multiSelectService.active() && isSelected" active></iqser-round-checkbox> <iqser-round-checkbox *ngIf="multiSelectService.active() && isSelected()" active></iqser-round-checkbox>
</div> </div>
</div> </div>

View File

@ -1,5 +1,5 @@
import { NgIf, UpperCasePipe } from '@angular/common'; import { NgIf, UpperCasePipe } from '@angular/common';
import { Component, Input } from '@angular/core'; import { Component, computed, input } from '@angular/core';
import { RoundCheckboxComponent } from '@common-ui/inputs/round-checkbox/round-checkbox.component'; import { RoundCheckboxComponent } from '@common-ui/inputs/round-checkbox/round-checkbox.component';
import { getConfig, IqserDenyDirective } from '@iqser/common-ui'; import { getConfig, IqserDenyDirective } from '@iqser/common-ui';
import { AnnotationWrapper } from '@models/file/annotation.wrapper'; import { AnnotationWrapper } from '@models/file/annotation.wrapper';
@ -20,8 +20,17 @@ export class AnnotationCardComponent {
protected readonly roles = Roles; protected readonly roles = Roles;
protected readonly annotationTypesTranslations = annotationTypesTranslations; protected readonly annotationTypesTranslations = annotationTypesTranslations;
protected readonly isDocumine = getConfig().IS_DOCUMINE; protected readonly isDocumine = getConfig().IS_DOCUMINE;
@Input() annotation: AnnotationWrapper; annotation = input<AnnotationWrapper>();
@Input() isSelected = false; isSelected = input(false);
readonly isEarmark = computed(() => this.annotation().isEarmark);
readonly iconShape = computed(() => this.annotation().iconShape);
readonly width = computed(() => this.annotation().width);
readonly height = computed(() => this.annotation().height);
readonly isHint = computed(() => this.annotation().isHint);
readonly isSkipped = computed(() => this.annotation().isSkipped);
readonly isIgnoredHint = computed(() => this.annotation().isIgnoredHint);
readonly descriptor = computed(() => this.annotation().descriptor);
constructor(readonly multiSelectService: MultiSelectService) {} constructor(readonly multiSelectService: MultiSelectService) {}
} }

View File

@ -8,7 +8,7 @@
matTooltipPosition="above" matTooltipPosition="above"
></redaction-annotation-card> ></redaction-annotation-card>
@if (!annotation().item.isEarmark) { @if (!isEarmark()) {
<div class="actions-wrapper"> <div class="actions-wrapper">
@if (!annotation().item.pending) { @if (!annotation().item.pending) {
<div <div

View File

@ -42,6 +42,8 @@ export class AnnotationWrapperComponent {
protected readonly multiSelectService = inject(MultiSelectService); protected readonly multiSelectService = inject(MultiSelectService);
readonly #isDocumine = getConfig().IS_DOCUMINE; readonly #isDocumine = getConfig().IS_DOCUMINE;
readonly isEarmark = computed(() => this.annotation().item.isEarmark);
constructor() { constructor() {
effect(() => { effect(() => {
this.active = this.annotation().isSelected; this.active = this.annotation().isSelected;

View File

@ -23,7 +23,9 @@
<div class="section"> <div class="section">
<div *ngFor="let attr of _fileAttributes()" class="attribute"> <div *ngFor="let attr of _fileAttributes()" class="attribute">
<div class="small-label">{{ attr.label }}:</div> <div class="small-label">{{ attr.label }}:</div>
<div>{{ attr.value ? (isDate(attr) ? (attr.value | date : 'd MMM yyyy') : attr.value) : '-' }}</div> <div>
{{ attr.value ? (attr.type === FileAttributeConfigTypes.DATE ? (attr.value | date: 'd MMM yyyy') : attr.value) : '-' }}
</div>
</div> </div>
</div> </div>

View File

@ -43,6 +43,7 @@ export class DocumentInfoComponent extends ContextComponent<Context> {
this._fileAttributesConfig(); this._fileAttributesConfig();
return this._attributes(); return this._attributes();
}); });
protected readonly FileAttributeConfigTypes = FileAttributeConfigTypes;
constructor( constructor(
readonly permissionsService: PermissionsService, readonly permissionsService: PermissionsService,
@ -56,10 +57,6 @@ export class DocumentInfoComponent extends ContextComponent<Context> {
this._dialogService.openDialog('documentInfo', this._state.file()); this._dialogService.openDialog('documentInfo', this._state.file());
} }
isDate(attribute: FileAttribute) {
return attribute.type === FileAttributeConfigTypes.DATE;
}
closeView() { closeView() {
this._documentInfoService.hide(); this._documentInfoService.hide();
setLocalStorageDataByFileId(this._state.fileId, 'show-document-info', false); setLocalStorageDataByFileId(this._state.fileId, 'show-document-info', false);

View File

@ -54,7 +54,7 @@
<div class="editing-actions"> <div class="editing-actions">
<iqser-icon-button <iqser-icon-button
(action)="save()" (action)="save()"
[disabled]="disabled" [disabled]="disabled()"
[label]="'component-management.actions.save' | translate" [label]="'component-management.actions.save' | translate"
[type]="iconButtonTypes.primary" [type]="iconButtonTypes.primary"
></iqser-icon-button> ></iqser-icon-button>

View File

@ -48,7 +48,16 @@ export class EditableStructuredComponentValueComponent implements OnInit {
protected entryLabel: string; protected entryLabel: string;
protected editing = false; protected editing = false;
protected initialEntry: IComponentLogEntry; protected initialEntry: IComponentLogEntry;
readonly disabled = computed(() => {
for (let i = 0; i < this.currentEntry().componentValues.length; i++) {
if (this.currentEntry().componentValues[i].value !== this.initialEntry.componentValues[i]?.value) {
return false;
}
}
return this.currentEntry().componentValues.length === this.initialEntry.componentValues.length;
});
protected readonly iconButtonTypes = IconButtonTypes; protected readonly iconButtonTypes = IconButtonTypes;
readonly #initialEntry = computed(() => JSON.parse(JSON.stringify(this.entry())));
constructor( constructor(
readonly helpModeService: HelpModeService, readonly helpModeService: HelpModeService,
@ -56,26 +65,13 @@ export class EditableStructuredComponentValueComponent implements OnInit {
private readonly _state: FilePreviewStateService, private readonly _state: FilePreviewStateService,
) {} ) {}
get disabled() {
for (let i = 0; i < this.currentEntry().componentValues.length; i++) {
if (this.currentEntry().componentValues[i].value !== this.initialEntry.componentValues[i]?.value) {
return false;
}
}
return this.currentEntry().componentValues.length === this.initialEntry.componentValues.length;
}
get #initialEntry() {
return JSON.parse(JSON.stringify(this.entry()));
}
ngOnInit() { ngOnInit() {
this.currentEntry = signal(this.entry()); this.currentEntry = signal(this.entry());
this.reset(); this.reset();
} }
reset() { reset() {
this.initialEntry = this.#initialEntry; this.initialEntry = this.#initialEntry();
this.entryLabel = this.parseName(this.currentEntry().name); this.entryLabel = this.parseName(this.currentEntry().name);
this.deselect(); this.deselect();
} }

View File

@ -172,7 +172,7 @@
translate="file-preview.tabs.annotations.reset" translate="file-preview.tabs.annotations.reset"
></a> ></a>
{{ 'file-preview.tabs.annotations.the-filters' | translate }} {{ 'file-preview.tabs.annotations.the-filters' | translate }}
} @else if (state.componentReferenceIds?.length === 0) { } @else if (state.componentReferenceIdsSignal()?.length === 0) {
{{ 'file-preview.tabs.annotations.no-annotations' | translate }} {{ 'file-preview.tabs.annotations.no-annotations' | translate }}
} }
} }

View File

@ -1,19 +1,19 @@
<div <div
(click)="pageSelected.emit(number)" (click)="pageSelected.emit(pageNumber())"
(dblclick)="toggleReadState()" (dblclick)="toggleReadState()"
*ngIf="assigneeChanged$ | async" *ngIf="assigneeChanged$ | async"
[class.active]="isActive" [class.active]="isActive()"
[class.read]="read" [class.read]="read()"
[id]="'quick-nav-page-' + number" [id]="'quick-nav-page-' + pageNumber()"
class="page-wrapper" class="page-wrapper"
> >
<mat-icon [svgIcon]="showDottedIcon ? 'red:excluded-page' : 'red:page'"></mat-icon> <mat-icon [svgIcon]="showDottedIcon() ? 'red:excluded-page' : 'red:page'"></mat-icon>
<div class="text">{{ number }}</div> <div class="text">{{ pageNumber() }}</div>
<div *ngIf="activeSelection" class="dot"></div> <div *ngIf="activeSelection()" class="dot"></div>
<div *ngIf="pageRotationService.rotations()[number]" class="rotated"> <div *ngIf="pageRotationService.rotations()[pageNumber()]" class="rotated">
<mat-icon svgIcon="red:rotation"></mat-icon> <mat-icon svgIcon="red:rotation"></mat-icon>
</div> </div>
</div> </div>

View File

@ -1,4 +1,4 @@
import { Component, effect, EventEmitter, inject, Input, OnChanges, Output } from '@angular/core'; import { Component, computed, effect, inject, input, output, untracked } from '@angular/core';
import { PermissionsService } from '@services/permissions.service'; import { PermissionsService } from '@services/permissions.service';
import { ViewedPagesService } from '@services/files/viewed-pages.service'; import { ViewedPagesService } from '@services/files/viewed-pages.service';
import { FilePreviewStateService } from '../../services/file-preview-state.service'; import { FilePreviewStateService } from '../../services/file-preview-state.service';
@ -12,6 +12,9 @@ import { PdfViewer } from '../../../pdf-viewer/services/pdf-viewer.service';
import { NGXLogger } from 'ngx-logger'; import { NGXLogger } from 'ngx-logger';
import { AsyncPipe, NgIf } from '@angular/common'; import { AsyncPipe, NgIf } from '@angular/common';
import { MatIcon } from '@angular/material/icon'; import { MatIcon } from '@angular/material/icon';
import { MultiSelectService } from '../../services/multi-select.service';
import { AnnotationsListingService } from '../../services/annotations-listing.service';
import { toSignal } from '@angular/core/rxjs-interop';
@Component({ @Component({
selector: 'redaction-page-indicator', selector: 'redaction-page-indicator',
@ -20,16 +23,26 @@ import { MatIcon } from '@angular/material/icon';
standalone: true, standalone: true,
imports: [NgIf, AsyncPipe, MatIcon], imports: [NgIf, AsyncPipe, MatIcon],
}) })
export class PageIndicatorComponent implements OnChanges { export class PageIndicatorComponent {
readonly #multiSelectService = inject(MultiSelectService);
readonly #listingService = inject(AnnotationsListingService);
readonly #config = getConfig<AppConfig>(); readonly #config = getConfig<AppConfig>();
readonly #logger = inject(NGXLogger); readonly #logger = inject(NGXLogger);
@Input({ required: true }) number: number; readonly pageNumber = input.required<number>();
@Input() showDottedIcon = false; readonly viewedPages = input.required<ViewedPage[]>();
@Input() activeSelection = false; readonly showDottedIcon = computed(() => this._state.file().excludedPages.includes(this.pageNumber()));
@Input() read = false; readonly selectedLength = toSignal(this.#listingService.selectedLength$);
@Output() readonly pageSelected = new EventEmitter<number>(); readonly activeSelection = computed(
() =>
this.#multiSelectService.active() &&
this.selectedLength() &&
this.#listingService.selected.find(p => p.pageNumber === this.pageNumber()),
);
readonly read = computed(() => this.viewedPages().find(p => p.page === this.pageNumber()));
readonly pageSelected = output<number>();
pageReadTimeout: number = null; pageReadTimeout: number = null;
readonly assigneeChanged$: Observable<boolean>; readonly assigneeChanged$: Observable<boolean>;
isActive = computed(() => untracked(this.pageNumber) === this._pdf.currentPage());
constructor( constructor(
private readonly _viewedPagesService: ViewedPagesService, private readonly _viewedPagesService: ViewedPagesService,
@ -48,27 +61,19 @@ export class PageIndicatorComponent implements OnChanges {
); );
effect(() => { effect(() => {
if (this.isActive) { if (this.isActive()) {
this.handlePageRead(); this.handlePageRead();
} }
}); });
} }
get isActive() {
return this.number === this._pdf.currentPage();
}
ngOnChanges() {
this.handlePageRead();
}
async toggleReadState() { async toggleReadState() {
if (!this._permissionService.canMarkPagesAsViewed(this._state.file())) { if (!this._permissionService.canMarkPagesAsViewed(this._state.file())) {
this.#logger.info('[PAGES] Cannot toggle read state'); this.#logger.info('[PAGES] Cannot toggle read state');
return; return;
} }
const read = untracked(this.read);
if (this.read) { if (read) {
await this.#markPageUnread(); await this.#markPageUnread();
} else { } else {
await this.#markPageRead(); await this.#markPageRead();
@ -85,9 +90,10 @@ export class PageIndicatorComponent implements OnChanges {
clearTimeout(this.pageReadTimeout); clearTimeout(this.pageReadTimeout);
} }
if (this.isActive && !this.read) { const read = untracked(this.read);
if (this.isActive() && !read) {
this.pageReadTimeout = window.setTimeout(async () => { this.pageReadTimeout = window.setTimeout(async () => {
if (this.isActive && !this.read) { if (this.isActive() && !read) {
await this.#markPageRead(); await this.#markPageRead();
} }
}, this.#config.AUTO_READ_TIME * 1000); }, this.#config.AUTO_READ_TIME * 1000);
@ -95,19 +101,21 @@ export class PageIndicatorComponent implements OnChanges {
} }
async #markPageRead() { async #markPageRead() {
this.#logger.info('[PAGES] Mark page read', this.number); const number = untracked(this.pageNumber);
this.#logger.info('[PAGES] Mark page read', number);
const fileId = this._state.fileId; const fileId = this._state.fileId;
await this._viewedPagesService.add({ page: this.number }, this._state.dossierId, fileId); await this._viewedPagesService.add({ page: number }, this._state.dossierId, fileId);
const viewedPage = new ViewedPage({ page: this.number, fileId }); const viewedPage = new ViewedPage({ page: number, fileId });
this._viewedPagesMapService.add(fileId, viewedPage); this._viewedPagesMapService.add(fileId, viewedPage);
} }
async #markPageUnread() { async #markPageUnread() {
this.#logger.info('[PAGES] Mark page unread', this.number); const number = untracked(this.pageNumber);
this.#logger.info('[PAGES] Mark page unread', number);
const fileId = this._state.fileId; const fileId = this._state.fileId;
await this._viewedPagesService.remove(this._state.dossierId, fileId, this.number); await this._viewedPagesService.remove(this._state.dossierId, fileId, number);
this._viewedPagesMapService.delete(fileId, this.number); this._viewedPagesMapService.delete(fileId, number);
} }
} }

View File

@ -1,10 +1,9 @@
<div *ngIf="viewedPages$ | async as viewedPages" class="pages" id="pages" [attr.help-mode-key]="'workload_page_list'"> <div class="pages" id="pages" [attr.help-mode-key]="'workload_page_list'">
@for (pageNumber of pages(); track pageNumber) {
<redaction-page-indicator <redaction-page-indicator
(pageSelected)="pageSelectedByClick($event)" (pageSelected)="pageSelectedByClick($event)"
*ngFor="let pageNumber of pages; trackBy: trackBy" [pageNumber]="pageNumber"
[activeSelection]="pageHasSelection(pageNumber)" [viewedPages]="viewedPages()"
[number]="pageNumber"
[read]="!!getViewedPage(viewedPages, pageNumber)"
[showDottedIcon]="isPageExcluded(pageNumber)"
></redaction-page-indicator> ></redaction-page-indicator>
}
</div> </div>

View File

@ -1,14 +1,12 @@
import { AsyncPipe, NgForOf, NgIf } from '@angular/common'; import { AsyncPipe, NgForOf, NgIf } from '@angular/common';
import { AfterViewInit, Component, inject, Input } from '@angular/core'; import { AfterViewInit, Component, inject, input } from '@angular/core';
import { List } from '@iqser/common-ui/lib/utils'; import { List } from '@iqser/common-ui/lib/utils';
import { ViewedPage } from '@red/domain';
import { ViewedPagesMapService } from '@services/files/viewed-pages-map.service'; import { ViewedPagesMapService } from '@services/files/viewed-pages-map.service';
import scrollIntoView from 'scroll-into-view-if-needed'; import scrollIntoView from 'scroll-into-view-if-needed';
import { PdfViewer } from '../../../pdf-viewer/services/pdf-viewer.service'; import { PdfViewer } from '../../../pdf-viewer/services/pdf-viewer.service';
import { AnnotationsListingService } from '../../services/annotations-listing.service';
import { FilePreviewStateService } from '../../services/file-preview-state.service'; import { FilePreviewStateService } from '../../services/file-preview-state.service';
import { MultiSelectService } from '../../services/multi-select.service';
import { PageIndicatorComponent } from '../page-indicator/page-indicator.component'; import { PageIndicatorComponent } from '../page-indicator/page-indicator.component';
import { toSignal } from '@angular/core/rxjs-interop';
@Component({ @Component({
selector: 'redaction-pages', selector: 'redaction-pages',
@ -19,11 +17,10 @@ import { PageIndicatorComponent } from '../page-indicator/page-indicator.compone
}) })
export class PagesComponent implements AfterViewInit { export class PagesComponent implements AfterViewInit {
readonly #state = inject(FilePreviewStateService); readonly #state = inject(FilePreviewStateService);
readonly #multiSelectService = inject(MultiSelectService);
readonly #listingService = inject(AnnotationsListingService);
protected readonly _pdf = inject(PdfViewer); protected readonly _pdf = inject(PdfViewer);
@Input({ required: true }) pages: List<number>; readonly pages = input.required<List<number>>();
readonly viewedPages$ = inject(ViewedPagesMapService).get$(this.#state.fileId); readonly viewedPages$ = inject(ViewedPagesMapService).get$(this.#state.fileId);
readonly viewedPages = toSignal(this.viewedPages$);
ngAfterViewInit() { ngAfterViewInit() {
setTimeout(() => { setTimeout(() => {
@ -45,18 +42,4 @@ export class PagesComponent implements AfterViewInit {
pageSelectedByClick($event: number): void { pageSelectedByClick($event: number): void {
this._pdf.navigateTo($event); this._pdf.navigateTo($event);
} }
readonly trackBy = (_index: number, item: number) => item;
pageHasSelection(page: number) {
return this.#multiSelectService.active() && !!this.#listingService.selected.find(a => a.pageNumber === page);
}
isPageExcluded(pageNumber: number): boolean {
return this.#state.file().excludedPages.includes(pageNumber);
}
getViewedPage(viewedPages: ViewedPage[], pageNumber: number) {
return viewedPages.find(p => p.page === pageNumber);
}
} }

View File

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

View File

@ -1,4 +1,4 @@
import { Component, Input } from '@angular/core'; import { Component, computed, input } from '@angular/core';
import { CdkFixedSizeVirtualScroll, CdkVirtualForOf, CdkVirtualScrollViewport } from '@angular/cdk/scrolling'; import { CdkFixedSizeVirtualScroll, CdkVirtualForOf, CdkVirtualScrollViewport } from '@angular/cdk/scrolling';
import { NgStyle } from '@angular/common'; import { NgStyle } from '@angular/common';
@ -13,10 +13,9 @@ const MAX_ITEMS_DISPLAY = 5;
styleUrl: './selected-annotations-list.component.scss', styleUrl: './selected-annotations-list.component.scss',
}) })
export class SelectedAnnotationsListComponent { export class SelectedAnnotationsListComponent {
@Input({ required: true }) values: string[]; values = input.required<string[]>();
readonly redactedTextsAreaHeight = computed(() =>
this.values.length <= MAX_ITEMS_DISPLAY ? LIST_ITEM_SIZE * this.values.length : LIST_ITEM_SIZE * MAX_ITEMS_DISPLAY,
);
protected readonly LIST_ITEM_SIZE = LIST_ITEM_SIZE; 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

@ -16,7 +16,7 @@
<redaction-editable-structured-component-value <redaction-editable-structured-component-value
#editableComponent #editableComponent
[entry]="entry" [entry]="entry"
[canEdit]="canEdit" [canEdit]="canEdit()"
(deselectLast)="deselectLast()" (deselectLast)="deselectLast()"
(revertOverride)="revertOverride($event)" (revertOverride)="revertOverride($event)"
(overrideValue)="overrideValue($event)" (overrideValue)="overrideValue($event)"

View File

@ -1,4 +1,4 @@
import { Component, effect, Input, OnInit, signal, ViewChildren } from '@angular/core'; import { Component, computed, effect, input, OnInit, signal, viewChild } from '@angular/core';
import { List } from '@common-ui/utils'; import { List } from '@common-ui/utils';
import { IconButtonTypes, LoadingService } from '@iqser/common-ui'; import { IconButtonTypes, LoadingService } from '@iqser/common-ui';
import { ComponentLogEntry, Dictionary, File, IComponentLogEntry, WorkflowFileStatuses } from '@red/domain'; import { ComponentLogEntry, Dictionary, File, IComponentLogEntry, WorkflowFileStatuses } from '@red/domain';
@ -21,13 +21,14 @@ import { FilePreviewStateService } from '../../services/file-preview-state.servi
imports: [PopupFilterComponent, NgIf, AsyncPipe, TranslateModule, NgForOf, EditableStructuredComponentValueComponent], imports: [PopupFilterComponent, NgIf, AsyncPipe, TranslateModule, NgForOf, EditableStructuredComponentValueComponent],
}) })
export class StructuredComponentManagementComponent implements OnInit { export class StructuredComponentManagementComponent implements OnInit {
file = input<File>();
dictionaries = input<Dictionary[]>();
editableComponents = viewChild<List<EditableStructuredComponentValueComponent>>('editableComponent');
canEdit = computed(() => this.file().workflowStatus !== WorkflowFileStatuses.APPROVED);
protected readonly componentLogData = signal<ComponentLogEntry[] | undefined>(undefined); protected readonly componentLogData = signal<ComponentLogEntry[] | undefined>(undefined);
protected readonly componentLogData$ = toObservable(this.componentLogData); protected readonly componentLogData$ = toObservable(this.componentLogData);
protected readonly iconButtonTypes = IconButtonTypes; protected readonly iconButtonTypes = IconButtonTypes;
protected displayedComponents$: Observable<ComponentLogEntry[]>; protected displayedComponents$: Observable<ComponentLogEntry[]>;
@Input() file: File;
@Input() dictionaries: Dictionary[];
@ViewChildren('editableComponent') editableComponents: List<EditableStructuredComponentValueComponent>;
constructor( constructor(
private readonly _componentLogService: ComponentLogService, private readonly _componentLogService: ComponentLogService,
@ -42,17 +43,13 @@ export class StructuredComponentManagementComponent implements OnInit {
}); });
} }
get canEdit() {
return this.file.workflowStatus !== WorkflowFileStatuses.APPROVED;
}
async ngOnInit(): Promise<void> { async ngOnInit(): Promise<void> {
await this.#loadData(); await this.#loadData();
this.displayedComponents$ = this.#displayedComponents$(); this.displayedComponents$ = this.#displayedComponents$();
} }
deselectLast() { deselectLast() {
const lastSelected = this.editableComponents.find(c => c.selected); const lastSelected = this.editableComponents().find(c => c.selected);
if (lastSelected) { if (lastSelected) {
lastSelected.deselect(); lastSelected.deselect();
} }
@ -61,7 +58,9 @@ export class StructuredComponentManagementComponent implements OnInit {
async revertOverride(originalKey: string) { async revertOverride(originalKey: string) {
this._loadingService.start(); this._loadingService.start();
await firstValueFrom( await firstValueFrom(
this._componentLogService.revertOverride(this.file.dossierTemplateId, this.file.dossierId, this.file.fileId, [originalKey]), this._componentLogService.revertOverride(this.file().dossierTemplateId, this.file().dossierId, this.file().fileId, [
originalKey,
]),
); );
await this.#loadData(); await this.#loadData();
} }
@ -69,7 +68,7 @@ export class StructuredComponentManagementComponent implements OnInit {
async overrideValue(componentLogEntry: IComponentLogEntry) { async overrideValue(componentLogEntry: IComponentLogEntry) {
this._loadingService.start(); this._loadingService.start();
await firstValueFrom( await firstValueFrom(
this._componentLogService.override(this.file.dossierTemplateId, this.file.dossierId, this.file.fileId, componentLogEntry), this._componentLogService.override(this.file().dossierTemplateId, this.file().dossierId, this.file().fileId, componentLogEntry),
); );
await this.#loadData(); await this.#loadData();
} }
@ -84,10 +83,10 @@ export class StructuredComponentManagementComponent implements OnInit {
async #loadData(): Promise<void> { async #loadData(): Promise<void> {
const componentLogData = await firstValueFrom( const componentLogData = await firstValueFrom(
this._componentLogService.getComponentLogData( this._componentLogService.getComponentLogData(
this.file.dossierTemplateId, this.file().dossierTemplateId,
this.file.dossierId, this.file().dossierId,
this.file.fileId, this.file().fileId,
this.dictionaries, this.dictionaries(),
), ),
); );
this.#computeFilters(componentLogData); this.#computeFilters(componentLogData);

View File

@ -67,7 +67,7 @@
<mat-form-field> <mat-form-field>
<mat-select formControlName="dictionary" [placeholder]="'add-hint.dialog.content.type-placeholder' | translate"> <mat-select formControlName="dictionary" [placeholder]="'add-hint.dialog.content.type-placeholder' | translate">
<mat-select-trigger>{{ displayedDictionaryLabel }}</mat-select-trigger> <mat-select-trigger>{{ displayedDictionaryLabel() }}</mat-select-trigger>
<mat-option <mat-option
*ngFor="let dictionary of dictionaries" *ngFor="let dictionary of dictionaries"
[matTooltip]="dictionary.description" [matTooltip]="dictionary.description"
@ -100,7 +100,7 @@
[label]="'add-hint.dialog.actions.save' | translate" [label]="'add-hint.dialog.actions.save' | translate"
[submit]="true" [submit]="true"
[type]="iconButtonTypes.primary" [type]="iconButtonTypes.primary"
[disabled]="disabled" [disabled]="_disabled()"
> >
</iqser-icon-button> </iqser-icon-button>

View File

@ -1,6 +1,5 @@
import { NgClass, NgForOf, NgIf, NgStyle } from '@angular/common'; import { NgClass, NgForOf, NgIf, NgStyle } from '@angular/common';
import { Component, OnInit } from '@angular/core'; import { Component, computed, effect, OnInit } from '@angular/core';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { FormBuilder, ReactiveFormsModule, UntypedFormGroup } from '@angular/forms'; import { FormBuilder, ReactiveFormsModule, UntypedFormGroup } from '@angular/forms';
import { MatDialogClose } from '@angular/material/dialog'; import { MatDialogClose } from '@angular/material/dialog';
import { MatFormField } from '@angular/material/form-field'; import { MatFormField } from '@angular/material/form-field';
@ -24,10 +23,10 @@ import { DictionaryService } from '@services/entity-services/dictionary.service'
import { Roles } from '@users/roles'; import { Roles } from '@users/roles';
import { UserPreferenceService } from '@users/user-preference.service'; import { UserPreferenceService } from '@users/user-preference.service';
import { calcTextWidthInPixels, stringToBoolean } from '@utils/functions'; import { calcTextWidthInPixels, stringToBoolean } from '@utils/functions';
import { tap } from 'rxjs/operators';
import { SystemDefaultOption, SystemDefaults } from '../../../account/utils/dialog-defaults'; import { SystemDefaultOption, SystemDefaults } from '../../../account/utils/dialog-defaults';
import { getRedactOrHintOptions } from '../../utils/dialog-options'; import { getRedactOrHintOptions } from '../../utils/dialog-options';
import { AddHintData, AddHintResult, RedactOrHintOption, RedactOrHintOptions } from '../../utils/dialog-types'; import { AddHintData, AddHintResult, RedactOrHintOption, RedactOrHintOptions } from '../../utils/dialog-types';
import { formValueToSignal } from '@common-ui/utils';
const MAXIMUM_TEXT_AREA_WIDTH = 421; const MAXIMUM_TEXT_AREA_WIDTH = 421;
@ -67,11 +66,22 @@ export class AddHintDialogComponent extends IqserDialogComponent<AddHintDialogCo
readonly maximumSelectedTextWidth = 567; readonly maximumSelectedTextWidth = 567;
dictionaryRequest = false; dictionaryRequest = false;
dictionaries: Dictionary[] = []; dictionaries: Dictionary[] = [];
form!: UntypedFormGroup; form = this.#getForm();
isEditingSelectedText = false; isEditingSelectedText = false;
selectedTextRows = 1; selectedTextRows = 1;
textWidth: number; textWidth: number;
dictionary = formValueToSignal(this.form.get('dictionary'));
displayedDictionaryLabel = computed(() => {
if (this.dictionary()) {
return this.dictionaries.find(d => d.type === this.dictionary())?.label ?? null;
}
return null;
});
_disabled = computed(() => this.#isRss || !this.dictionary());
readonly optionValue = formValueToSignal<DetailsRadioOption<RedactOrHintOption>>(this.form.get('option'));
constructor( constructor(
private readonly _activeDossiersService: ActiveDossiersService, private readonly _activeDossiersService: ActiveDossiersService,
private readonly _dictionaryService: DictionaryService, private readonly _dictionaryService: DictionaryService,
@ -80,7 +90,7 @@ export class AddHintDialogComponent extends IqserDialogComponent<AddHintDialogCo
private readonly _userPreferences: UserPreferenceService, private readonly _userPreferences: UserPreferenceService,
) { ) {
super(); super();
this.#dossier = _activeDossiersService.find(this.data.dossierId); this.#dossier = this._activeDossiersService.find(this.data.dossierId);
this.#applyToAllDossiers = this.applyToAll; this.#applyToAllDossiers = this.applyToAll;
this.options = getRedactOrHintOptions( this.options = getRedactOrHintOptions(
this.#dossier, this.#dossier,
@ -92,20 +102,13 @@ export class AddHintDialogComponent extends IqserDialogComponent<AddHintDialogCo
this.#isRss, this.#isRss,
); );
this.form = this.#getForm();
this.textWidth = calcTextWidthInPixels(this.form.controls.selectedText.value); this.textWidth = calcTextWidthInPixels(this.form.controls.selectedText.value);
this.form effect(() => {
.get('option') this.dictionaryRequest = this.optionValue().value === RedactOrHintOptions.IN_DOSSIER;
.valueChanges.pipe(
tap((option: DetailsRadioOption<RedactOrHintOption>) => {
this.dictionaryRequest = option.value === RedactOrHintOptions.IN_DOSSIER;
this.#setDictionaries(); this.#setDictionaries();
this.#resetValues(); this.#resetValues();
}), });
takeUntilDestroyed(),
)
.subscribe();
} }
toggleEditingSelectedText() { toggleEditingSelectedText() {
@ -120,14 +123,6 @@ export class AddHintDialogComponent extends IqserDialogComponent<AddHintDialogCo
this.form.patchValue({ selectedText: this.initialText }); this.form.patchValue({ selectedText: this.initialText });
} }
get displayedDictionaryLabel() {
const dictType = this.form.get('dictionary').value;
if (dictType) {
return this.dictionaries.find(d => d.type === dictType)?.label ?? null;
}
return null;
}
get applyToAll() { get applyToAll() {
return this.isSystemDefault || this._userPreferences.getAddHintDefaultExtraOption() === 'undefined' return this.isSystemDefault || this._userPreferences.getAddHintDefaultExtraOption() === 'undefined'
? this.data.applyToAllDossiers ?? true ? this.data.applyToAllDossiers ?? true
@ -150,10 +145,6 @@ export class AddHintDialogComponent extends IqserDialogComponent<AddHintDialogCo
return defaultOption ?? this.options[0]; return defaultOption ?? this.options[0];
} }
get disabled() {
return this.#isRss || !this.form.get('dictionary').value;
}
async ngOnInit(): Promise<void> { async ngOnInit(): Promise<void> {
this.#setDictionaries(); this.#setDictionaries();
this.#resetValues(); this.#resetValues();

View File

@ -28,7 +28,7 @@
<input formControlName="section" name="section" type="text" /> <input formControlName="section" name="section" type="text" />
</div> </div>
<div *ngIf="this.allRectangles" class="iqser-input-group w-400"> <div *ngIf="allRectangles" class="iqser-input-group w-400">
<label [translate]="'change-legal-basis-dialog.content.classification'"></label> <label [translate]="'change-legal-basis-dialog.content.classification'"></label>
<input formControlName="classification" name="classification" type="text" /> <input formControlName="classification" name="classification" type="text" />
</div> </div>

View File

@ -34,6 +34,7 @@ import { LegalBasisOption } from '../../utils/dialog-types';
}) })
export class ChangeLegalBasisDialogComponent extends BaseDialogComponent implements OnInit { export class ChangeLegalBasisDialogComponent extends BaseDialogComponent implements OnInit {
legalOptions: LegalBasisOption[] = []; legalOptions: LegalBasisOption[] = [];
allRectangles = this._data.annotations.reduce((acc, a) => acc && a.AREA, true);
constructor( constructor(
private readonly _justificationsService: JustificationsService, private readonly _justificationsService: JustificationsService,
@ -44,10 +45,6 @@ export class ChangeLegalBasisDialogComponent extends BaseDialogComponent impleme
this.form = this._getForm(); this.form = this._getForm();
} }
get allRectangles(): boolean {
return this._data.annotations.reduce((acc, a) => acc && a.AREA, true);
}
async ngOnInit() { async ngOnInit() {
const data = await firstValueFrom(this._justificationsService.getForDossierTemplate(this._data.dossier.dossierTemplateId)); const data = await firstValueFrom(this._justificationsService.getForDossierTemplate(this._data.dossier.dossierTemplateId));

View File

@ -13,7 +13,7 @@
<mat-form-field> <mat-form-field>
<mat-select [placeholder]="'add-annotation.dialog.content.type-placeholder' | translate" formControlName="dictionary"> <mat-select [placeholder]="'add-annotation.dialog.content.type-placeholder' | translate" formControlName="dictionary">
<mat-select-trigger>{{ displayedDictionaryLabel }}</mat-select-trigger> <mat-select-trigger>{{ displayedDictionaryLabel() }}</mat-select-trigger>
<mat-option <mat-option
*ngFor="let dictionary of dictionaries" *ngFor="let dictionary of dictionaries"
[matTooltip]="dictionary.description" [matTooltip]="dictionary.description"

View File

@ -1,4 +1,4 @@
import { Component, inject, OnInit } from '@angular/core'; import { Component, computed, inject, OnInit } from '@angular/core';
import { FormBuilder, ReactiveFormsModule } from '@angular/forms'; import { FormBuilder, ReactiveFormsModule } from '@angular/forms';
import { CircleButtonComponent, HasScrollbarDirective, IconButtonComponent, IconButtonTypes, IqserDialogComponent } from '@iqser/common-ui'; import { CircleButtonComponent, HasScrollbarDirective, IconButtonComponent, IconButtonTypes, IqserDialogComponent } from '@iqser/common-ui';
import { Dictionary, IAddRedactionRequest } from '@red/domain'; import { Dictionary, IAddRedactionRequest } from '@red/domain';
@ -11,6 +11,7 @@ import { NgForOf } from '@angular/common';
import { MatTooltip } from '@angular/material/tooltip'; import { MatTooltip } from '@angular/material/tooltip';
import { TranslateModule } from '@ngx-translate/core'; import { TranslateModule } from '@ngx-translate/core';
import { MatDialogClose } from '@angular/material/dialog'; import { MatDialogClose } from '@angular/material/dialog';
import { formValueToSignal } from '@common-ui/utils';
@Component({ @Component({
templateUrl: './add-annotation-dialog.component.html', templateUrl: './add-annotation-dialog.component.html',
@ -38,6 +39,15 @@ export class AddAnnotationDialogComponent
readonly iconButtonTypes = IconButtonTypes; readonly iconButtonTypes = IconButtonTypes;
dictionaries: Dictionary[] = []; dictionaries: Dictionary[] = [];
readonly form = this.#getForm(); readonly form = this.#getForm();
readonly dictionaryType = formValueToSignal(this.form.controls.dictionary);
readonly displayedDictionaryLabel = computed(() => {
const dictType = this.dictionaryType();
if (dictType) {
return this.dictionaries.find(d => d.type === dictType)?.label ?? null;
}
return null;
});
constructor( constructor(
private readonly _dictionaryService: DictionaryService, private readonly _dictionaryService: DictionaryService,
@ -46,14 +56,6 @@ export class AddAnnotationDialogComponent
super(); super();
} }
get displayedDictionaryLabel() {
const dictType = this.form.controls.dictionary.value;
if (dictType) {
return this.dictionaries.find(d => d.type === dictType)?.label ?? null;
}
return null;
}
get disabled() { get disabled() {
return !this.form.controls.dictionary.value; return !this.form.controls.dictionary.value;
} }

View File

@ -12,7 +12,7 @@
<label [translate]="'edit-redaction.dialog.content.type'"></label> <label [translate]="'edit-redaction.dialog.content.type'"></label>
<mat-form-field> <mat-form-field>
<mat-select [placeholder]="'edit-redaction.dialog.content.unchanged' | translate" formControlName="type"> <mat-select [placeholder]="'edit-redaction.dialog.content.unchanged' | translate" formControlName="type">
<mat-select-trigger>{{ displayedDictionaryLabel }}</mat-select-trigger> <mat-select-trigger>{{ displayedDictionaryLabel() }}</mat-select-trigger>
<mat-option <mat-option
*ngFor="let dictionary of dictionaries" *ngFor="let dictionary of dictionaries"
[matTooltip]="dictionary.description" [matTooltip]="dictionary.description"

View File

@ -1,4 +1,4 @@
import { Component, OnInit } from '@angular/core'; import { Component, computed, OnInit } from '@angular/core';
import { FormBuilder, ReactiveFormsModule, UntypedFormGroup } from '@angular/forms'; import { FormBuilder, ReactiveFormsModule, UntypedFormGroup } from '@angular/forms';
import { import {
CircleButtonComponent, CircleButtonComponent,
@ -20,6 +20,7 @@ import { MatOption, MatSelect, MatSelectTrigger } from '@angular/material/select
import { MatTooltip } from '@angular/material/tooltip'; import { MatTooltip } from '@angular/material/tooltip';
import { TranslateModule } from '@ngx-translate/core'; import { TranslateModule } from '@ngx-translate/core';
import { MatDialogClose } from '@angular/material/dialog'; import { MatDialogClose } from '@angular/material/dialog';
import { formValueToSignal } from '@common-ui/utils';
@Component({ @Component({
templateUrl: 'edit-annotation-dialog.component.html', templateUrl: 'edit-annotation-dialog.component.html',
@ -50,9 +51,19 @@ export class EditAnnotationDialogComponent
readonly roles = Roles; readonly roles = Roles;
readonly iconButtonTypes = IconButtonTypes; readonly iconButtonTypes = IconButtonTypes;
readonly redactedTexts: string[]; readonly redactedTexts: string[];
readonly #dossier: Dossier = this._activeDossiersService.find(this.data.dossierId);
dictionaries: Dictionary[] = []; dictionaries: Dictionary[] = [];
form: UntypedFormGroup; form: UntypedFormGroup = this.#getForm();
readonly #dossier: Dossier; showList = this.data.annotations.every(annotation => annotation.isSkipped || annotation.isRedacted);
selectedDictionaryType = formValueToSignal(this.form.get('type'));
displayedDictionaryLabel = computed(() => {
const selectedDictionaryType = this.selectedDictionaryType();
if (selectedDictionaryType) {
return this.dictionaries.find(d => d.type === selectedDictionaryType)?.label ?? null;
}
return null;
});
constructor( constructor(
private readonly _activeDossiersService: ActiveDossiersService, private readonly _activeDossiersService: ActiveDossiersService,
@ -60,25 +71,11 @@ export class EditAnnotationDialogComponent
private readonly _formBuilder: FormBuilder, private readonly _formBuilder: FormBuilder,
) { ) {
super(); super();
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.initialFormValue = JSON.parse(JSON.stringify(this.form.getRawValue())); this.initialFormValue = JSON.parse(JSON.stringify(this.form.getRawValue()));
} }
get displayedDictionaryLabel() {
const selectedDictionaryType = this.form.get('type').value;
if (selectedDictionaryType) {
return this.dictionaries.find(d => d.type === selectedDictionaryType)?.label ?? null;
}
return null;
}
get showList() {
return this.data.annotations.every(annotation => annotation.isSkipped || annotation.isRedacted);
}
async ngOnInit(): Promise<void> { async ngOnInit(): Promise<void> {
this.#setTypes(); this.#setTypes();
} }

View File

@ -20,7 +20,7 @@
<ul *cdkVirtualFor="let text of redactedTexts; let idx = index"> <ul *cdkVirtualFor="let text of redactedTexts; let idx = index">
<li> <li>
{{ {{
(isFalsePositive (isFalsePositive()
? 'remove-annotation.dialog.content.list-item-false-positive' ? 'remove-annotation.dialog.content.list-item-false-positive'
: 'remove-annotation.dialog.content.list-item' : 'remove-annotation.dialog.content.list-item'
) )

View File

@ -1,6 +1,6 @@
import { CdkFixedSizeVirtualScroll, CdkVirtualForOf } from '@angular/cdk/scrolling'; import { CdkFixedSizeVirtualScroll, CdkVirtualForOf } from '@angular/cdk/scrolling';
import { NgIf, NgStyle } from '@angular/common'; import { NgIf, NgStyle } from '@angular/common';
import { Component } from '@angular/core'; import { Component, computed } from '@angular/core';
import { FormBuilder, ReactiveFormsModule, UntypedFormGroup } from '@angular/forms'; import { FormBuilder, ReactiveFormsModule, UntypedFormGroup } from '@angular/forms';
import { MatDialogClose } from '@angular/material/dialog'; import { MatDialogClose } from '@angular/material/dialog';
import { DetailsRadioOption } from '@common-ui/inputs/details-radio/details-radio-option'; import { DetailsRadioOption } from '@common-ui/inputs/details-radio/details-radio-option';
@ -17,6 +17,7 @@ import {
import { TranslateModule } from '@ngx-translate/core'; import { TranslateModule } from '@ngx-translate/core';
import { getRemoveRedactionOptions } from '../../../utils/dialog-options'; import { getRemoveRedactionOptions } from '../../../utils/dialog-options';
import { RemoveAnnotationData, RemoveAnnotationOption, RemoveAnnotationOptions, RemoveAnnotationResult } from '../../../utils/dialog-types'; import { RemoveAnnotationData, RemoveAnnotationOption, RemoveAnnotationOptions, RemoveAnnotationResult } from '../../../utils/dialog-types';
import { formValueToSignal } from '@common-ui/utils';
@Component({ @Component({
templateUrl: 'remove-annotation-dialog.component.html', templateUrl: 'remove-annotation-dialog.component.html',
@ -46,6 +47,8 @@ export class RemoveAnnotationDialogComponent extends IqserDialogComponent<
readonly iconButtonTypes = IconButtonTypes; readonly iconButtonTypes = IconButtonTypes;
readonly options: DetailsRadioOption<RemoveAnnotationOption>[]; readonly options: DetailsRadioOption<RemoveAnnotationOption>[];
readonly redactedTexts: string[]; readonly redactedTexts: string[];
readonly option = formValueToSignal<DetailsRadioOption<RemoveAnnotationOption>>(this.form.get('option'));
readonly isFalsePositive = computed(() => this.option().value === RemoveAnnotationOptions.FALSE_POSITIVE);
form!: UntypedFormGroup; form!: UntypedFormGroup;
@ -56,10 +59,6 @@ export class RemoveAnnotationDialogComponent extends IqserDialogComponent<
this.form = this.#getForm(); this.form = this.#getForm();
} }
get isFalsePositive(): boolean {
return this.form.get('option').value.value === RemoveAnnotationOptions.FALSE_POSITIVE;
}
get #applyToAllDossiers(): boolean { get #applyToAllDossiers(): boolean {
const selectedOption = this.form.get('option').value.value; const selectedOption = this.form.get('option').value.value;
return selectedOption === RemoveAnnotationOptions.IN_DOSSIER || selectedOption === RemoveAnnotationOptions.FALSE_POSITIVE; return selectedOption === RemoveAnnotationOptions.IN_DOSSIER || selectedOption === RemoveAnnotationOptions.FALSE_POSITIVE;

View File

@ -18,6 +18,7 @@ interface RevertValueResult {}
}) })
export class RevertValueDialogComponent extends IqserDialogComponent<RevertValueDialogComponent, RevertValueData, RevertValueResult> { export class RevertValueDialogComponent extends IqserDialogComponent<RevertValueDialogComponent, RevertValueData, RevertValueResult> {
protected readonly entry = this.data.entry; protected readonly entry = this.data.entry;
readonly valueDescription = this.#valueDescription;
constructor(private readonly _translateService: TranslateService) { constructor(private readonly _translateService: TranslateService) {
super(); super();
@ -26,7 +27,7 @@ export class RevertValueDialogComponent extends IqserDialogComponent<RevertValue
this.close(ConfirmOptions.CONFIRM); this.close(ConfirmOptions.CONFIRM);
} }
get valueDescription(): string { get #valueDescription(): string {
const componentRuleString = this._translateService.instant('revert-value-dialog.component-rule'); const componentRuleString = this._translateService.instant('revert-value-dialog.component-rule');
const valueDescription = this.entry.componentValues[0]?.valueDescription || ''; const valueDescription = this.entry.componentValues[0]?.valueDescription || '';
return `<strong>${componentRuleString}</strong> ${valueDescription}`; return `<strong>${componentRuleString}</strong> ${valueDescription}`;

View File

@ -30,7 +30,7 @@
[placeholder]="isBulkEdit ? ('edit-redaction.dialog.content.unchanged' | translate) : ''" [placeholder]="isBulkEdit ? ('edit-redaction.dialog.content.unchanged' | translate) : ''"
formControlName="type" formControlName="type"
> >
<mat-select-trigger>{{ displayedDictionaryLabel }}</mat-select-trigger> <mat-select-trigger>{{ displayedDictionaryLabel() }}</mat-select-trigger>
<mat-option <mat-option
(click)="typeChanged()" (click)="typeChanged()"
*ngFor="let dictionary of typeSelectOptions" *ngFor="let dictionary of typeSelectOptions"
@ -44,8 +44,8 @@
</mat-form-field> </mat-form-field>
</div> </div>
<ng-container *ngIf="showExtras && !hiddenReason"> <ng-container *ngIf="showExtras && !hiddenReason()">
<div [class.required]="!form.controls.reason.disabled" class="iqser-input-group w-450"> <div [class.required]="!reasonDisabled()" class="iqser-input-group w-450">
<label [translate]="'edit-redaction.dialog.content.reason'"></label> <label [translate]="'edit-redaction.dialog.content.reason'"></label>
<mat-form-field> <mat-form-field>
<mat-select <mat-select
@ -81,7 +81,7 @@
<label [translate]="'edit-redaction.dialog.content.section'"></label> <label [translate]="'edit-redaction.dialog.content.section'"></label>
<input <input
[placeholder]=" [placeholder]="
isBulkEdit && !hideParagraphPlaceholder ? ('edit-redaction.dialog.content.unchanged' | translate) : '' isBulkEdit && !hideParagraphPlaceholder() ? ('edit-redaction.dialog.content.unchanged' | translate) : ''
" "
formControlName="section" formControlName="section"
name="section" name="section"

View File

@ -1,5 +1,5 @@
import { NgForOf, NgIf } from '@angular/common'; import { NgForOf, NgIf } from '@angular/common';
import { Component, inject, OnInit } from '@angular/core'; import { Component, computed, inject, OnInit } from '@angular/core';
import { FormControl, FormGroup, ReactiveFormsModule } from '@angular/forms'; import { FormControl, FormGroup, ReactiveFormsModule } from '@angular/forms';
import { MatDialogClose } from '@angular/material/dialog'; import { MatDialogClose } from '@angular/material/dialog';
import { MatFormField } from '@angular/material/form-field'; import { MatFormField } from '@angular/material/form-field';
@ -38,6 +38,7 @@ import { DetailsRadioComponent } from '@common-ui/inputs/details-radio/details-r
import { DetailsRadioOption } from '@common-ui/inputs/details-radio/details-radio-option'; import { DetailsRadioOption } from '@common-ui/inputs/details-radio/details-radio-option';
import { validatePageRange } from '../../utils/form-validators'; import { validatePageRange } from '../../utils/form-validators';
import { parseRectanglePosition, parseSelectedPageNumbers } from '../../utils/enhance-manual-redaction-request.utils'; import { parseRectanglePosition, parseSelectedPageNumbers } from '../../utils/enhance-manual-redaction-request.utils';
import { formStatusToSignal, formValueToSignal, isJustOne } from '@common-ui/utils';
interface TypeSelectOptions { interface TypeSelectOptions {
type: string; type: string;
@ -94,10 +95,34 @@ export class EditRedactionDialogComponent
typeSelectOptions: TypeSelectOptions[] = []; typeSelectOptions: TypeSelectOptions[] = [];
readonly form = this.#getForm(); readonly form = this.#getForm();
hasTypeChanged = false; hasTypeChanged = false;
initialReasonDisabled = this.someSkipped; initialReasonDisabled = this.#someSkipped;
protected readonly roles = Roles; protected readonly roles = Roles;
readonly #dossier = inject(ActiveDossiersService).find(this.data.dossierId); readonly #dossier = inject(ActiveDossiersService).find(this.data.dossierId);
readonly formStatus = formStatusToSignal(this.form);
readonly formInvalid = computed(() => this.formStatus() === 'INVALID');
readonly dictionaryType = formValueToSignal(this.form.controls.type);
readonly reasonStatus = formStatusToSignal(this.form.controls.reason);
readonly reasonValue = formValueToSignal(this.form.controls.reason);
readonly sectionValue = formValueToSignal(this.form.controls.section);
readonly displayedDictionaryLabel = computed(() => {
const selectedDictionaryType = this.dictionaryType();
if (selectedDictionaryType) {
return this.typeSelectOptions.find(option => option.type === selectedDictionaryType)?.label ?? null;
}
return null;
});
readonly reasonDisabled = computed(() => this.reasonStatus() === 'DISABLED');
readonly hiddenReason = computed(() => this.isImage && this.reasonDisabled());
readonly hideParagraphPlaceholder = computed(
() => this.sectionValue() !== this.initialFormValue['section'] || this.#isFieldEmpty('section'),
);
readonly hideReasonPlaceholder = this.#hideReasonPlaceholder;
readonly isBulkEdit = !isJustOne(this.annotations);
readonly showExtras = (this.isImage && this.isRedacted) || !(this.isImage || this.isHint);
constructor( constructor(
private readonly _justificationsService: JustificationsService, private readonly _justificationsService: JustificationsService,
private readonly _dictionaryService: DictionaryService, private readonly _dictionaryService: DictionaryService,
@ -105,54 +130,27 @@ export class EditRedactionDialogComponent
super(); super();
} }
get displayedDictionaryLabel() {
const selectedDictionaryType = this.form.controls.type.value;
if (selectedDictionaryType) {
return this.typeSelectOptions.find(option => option.type === selectedDictionaryType)?.label ?? null;
}
return null;
}
get showExtras() {
return (this.isImage && this.isRedacted) || !(this.isImage || this.isHint);
}
get disabled() { get disabled() {
return ( return this.formInvalid() || (!this.isImage && this.showExtras && !this.reasonDisabled() ? !this.reasonValue() : false);
this.form.invalid ||
(!this.isImage && this.showExtras && !this.form.controls.reason.disabled ? !this.form.controls.reason.value : false)
);
} }
get someSkipped() { get #someSkipped() {
return this.annotations.some(annotation => annotation.isSkipped); return this.annotations.some(annotation => annotation.isSkipped);
} }
get redactBasedTypes() { get #redactBasedTypes() {
return this._dictionaryService.getRedactionTypes(this.#dossier.dossierTemplateId).map(dictionary => dictionary.type); return this._dictionaryService.getRedactionTypes(this.#dossier.dossierTemplateId).map(dictionary => dictionary.type);
} }
get isRedactBasedType() { get #isRedactBasedType() {
return this.redactBasedTypes.includes(this.form.controls.type.value); return this.#redactBasedTypes.includes(this.form.controls.type.value);
} }
get hideReasonPlaceholder() { get #hideReasonPlaceholder() {
return (this.hasTypeChanged && this.isRedactBasedType) || this.#isFieldEmpty('legalBasisValue'); return (this.hasTypeChanged && this.#isRedactBasedType) || this.#isFieldEmpty('legalBasisValue');
} }
get hideParagraphPlaceholder() { get #sameType() {
return this.form.controls.section.value !== this.initialFormValue['section'] || this.#isFieldEmpty('section');
}
get hiddenReason() {
return this.isImage && this.form.controls.reason.disabled;
}
get isBulkEdit() {
return this.annotations.length > 1;
}
get sameType() {
return this.annotations.every(annotation => annotation.type === this.annotations[0].type); return this.annotations.every(annotation => annotation.type === this.annotations[0].type);
} }
@ -187,7 +185,7 @@ export class EditRedactionDialogComponent
const selectedDictionaryType = this.form.controls.type.value; const selectedDictionaryType = this.form.controls.type.value;
const initialReason = this.form.get('type').value === this.initialFormValue.type && !this.initialReasonDisabled; const initialReason = this.form.get('type').value === this.initialFormValue.type && !this.initialReasonDisabled;
if (this.redactBasedTypes.includes(selectedDictionaryType) || initialReason) { if (this.#redactBasedTypes.includes(selectedDictionaryType) || initialReason) {
this.form.controls.reason.enable(); this.form.controls.reason.enable();
this.hasTypeChanged = true; this.hasTypeChanged = true;
if (initialReason) { if (initialReason) {
@ -230,7 +228,7 @@ export class EditRedactionDialogComponent
this.isImage, this.isImage,
this.isHint, this.isHint,
this.annotations.every(annotation => annotation.isOCR), this.annotations.every(annotation => annotation.isOCR),
this.sameType ? this.annotations[0].type : null, this.#sameType ? this.annotations[0].type : null,
); );
this.typeSelectOptions = this.dictionaries.map(dictionary => ({ this.typeSelectOptions = this.dictionaries.map(dictionary => ({
@ -248,10 +246,10 @@ export class EditRedactionDialogComponent
#getForm() { #getForm() {
const sameSection = this.annotations.every(annotation => annotation.section === this.annotations[0].section); const sameSection = this.annotations.every(annotation => annotation.section === this.annotations[0].section);
return new FormGroup({ return new FormGroup({
reason: new FormControl<LegalBasisOption>({ value: null, disabled: this.someSkipped }), reason: new FormControl<LegalBasisOption>({ value: null, disabled: this.#someSkipped }),
comment: new FormControl<string>(null), comment: new FormControl<string>(null),
type: new FormControl<string>({ type: new FormControl<string>({
value: this.sameType ? this.annotations[0].type : null, value: this.#sameType ? this.annotations[0].type : null,
disabled: this.isImported, disabled: this.isImported,
}), }),
section: new FormControl<string>({ value: sameSection ? this.annotations[0].section : null, disabled: this.isImported }), section: new FormControl<string>({ value: sameSection ? this.annotations[0].section : null, disabled: this.isImported }),

View File

@ -33,7 +33,7 @@
<div class="iqser-input-group w-400"> <div class="iqser-input-group w-400">
<label [translate]="'manual-annotation.dialog.content.legalBasis'"></label> <label [translate]="'manual-annotation.dialog.content.legalBasis'"></label>
<input [value]="form.get('reason').value?.legalBasis" disabled type="text" /> <input [value]="reasonValue().legalBasis" disabled type="text" />
</div> </div>
} }

View File

@ -30,6 +30,7 @@ import { ForceAnnotationOption, LegalBasisOption } from '../../utils/dialog-type
import { getForceAnnotationOptions } from '../../utils/dialog-options'; import { getForceAnnotationOptions } from '../../utils/dialog-options';
import { DetailsRadioComponent } from '@common-ui/inputs/details-radio/details-radio.component'; import { DetailsRadioComponent } from '@common-ui/inputs/details-radio/details-radio.component';
import { SystemDefaults } from '../../../account/utils/dialog-defaults'; import { SystemDefaults } from '../../../account/utils/dialog-defaults';
import { formValueToSignal } from '@common-ui/utils';
const DOCUMINE_LEGAL_BASIS = 'n-a.'; const DOCUMINE_LEGAL_BASIS = 'n-a.';
@ -68,6 +69,12 @@ export class ForceAnnotationDialogComponent extends BaseDialogComponent implemen
]); ]);
legalOptions: LegalBasisOption[] = []; legalOptions: LegalBasisOption[] = [];
readonly isImageHint = this._data.annotations.every(annotation => annotation.IMAGE_HINT);
readonly isHintDialog = this._data.hint;
readonly dialogTitle = this.#dialogTitle;
readonly reasonValue = formValueToSignal<ILegalBasisChangeRequest>(this.form.get('reason'));
protected readonly roles = Roles; protected readonly roles = Roles;
constructor( constructor(
@ -81,19 +88,11 @@ export class ForceAnnotationDialogComponent extends BaseDialogComponent implemen
this.form = this.#getForm(); this.form = this.#getForm();
} }
get isImageHint() {
return this._data.annotations.every(annotation => annotation.IMAGE_HINT);
}
get isHintDialog() {
return this._data.hint;
}
get disabled(): boolean { get disabled(): boolean {
return !this.valid; return !this.valid;
} }
get dialogTitle(): string { get #dialogTitle(): string {
return this.isImageHint return this.isImageHint
? _('manual-annotation.dialog.header.force-redaction-image-hint') ? _('manual-annotation.dialog.header.force-redaction-image-hint')
: this.isHintDialog : this.isHintDialog

View File

@ -9,12 +9,7 @@
<div class="iqser-input-group required w-150"> <div class="iqser-input-group required w-150">
<label translate="highlight-action-dialog.form.color.label"></label> <label translate="highlight-action-dialog.form.color.label"></label>
<input class="hex-color-input" formControlName="color" name="color" type="text" /> <input class="hex-color-input" formControlName="color" name="color" type="text" />
<div <div [colorPicker]="colorValue()" [cpDisabled]="true" [style.background]="colorValue()" class="input-icon"></div>
[colorPicker]="form.get('color').value"
[cpDisabled]="true"
[style.background]="form.get('color').value"
class="input-icon"
></div>
</div> </div>
<iqser-details-radio [displayInRow]="true" [options]="options" class="mt-25" formControlName="option"></iqser-details-radio> <iqser-details-radio [displayInRow]="true" [options]="options" class="mt-25" formControlName="option"></iqser-details-radio>

View File

@ -14,6 +14,7 @@ import { highlightsTranslations } from '@translations/highlights-translations';
import { Roles } from '@users/roles'; import { Roles } from '@users/roles';
import { ColorPickerModule } from 'ngx-color-picker'; import { ColorPickerModule } from 'ngx-color-picker';
import { firstValueFrom } from 'rxjs'; import { firstValueFrom } from 'rxjs';
import { formValueToSignal } from '@common-ui/utils';
export interface HighlightActionData { export interface HighlightActionData {
readonly operation: EarmarkOperation; readonly operation: EarmarkOperation;
@ -59,6 +60,8 @@ export class HighlightActionDialogComponent extends BaseDialogComponent {
}, },
]; ];
readonly colorValue = formValueToSignal(this.form.get('color'));
constructor( constructor(
protected readonly _dialogRef: MatDialogRef<HighlightActionDialogComponent>, protected readonly _dialogRef: MatDialogRef<HighlightActionDialogComponent>,
private readonly _textHighlightService: EarmarksService, private readonly _textHighlightService: EarmarksService,

View File

@ -28,7 +28,7 @@
<div *deny="roles.getRss" class="iqser-input-group w-450"> <div *deny="roles.getRss" class="iqser-input-group w-450">
<label [translate]="'manual-annotation.dialog.content.legalBasis'"></label> <label [translate]="'manual-annotation.dialog.content.legalBasis'"></label>
<input [value]="form.get('reason').value?.legalBasis" disabled type="text" /> <input [value]="reasonValue().legalBasis" disabled type="text" />
</div> </div>
<div class="iqser-input-group w-450"> <div class="iqser-input-group w-450">

View File

@ -9,7 +9,7 @@ import {
IqserDialogComponent, IqserDialogComponent,
Toaster, Toaster,
} from '@iqser/common-ui'; } from '@iqser/common-ui';
import { Dossier, IAddRedactionRequest, SuperTypes } from '@red/domain'; import { Dossier, IAddRedactionRequest, ILegalBasisChangeRequest, SuperTypes } from '@red/domain';
import { ActiveDossiersService } from '@services/dossiers/active-dossiers.service'; import { ActiveDossiersService } from '@services/dossiers/active-dossiers.service';
import { JustificationsService } from '@services/entity-services/justifications.service'; import { JustificationsService } from '@services/entity-services/justifications.service';
import { Roles } from '@users/roles'; import { Roles } from '@users/roles';
@ -33,6 +33,7 @@ import { getRectangleRedactOptions } from '../../utils/dialog-options';
import { DetailsRadioComponent } from '@common-ui/inputs/details-radio/details-radio.component'; import { DetailsRadioComponent } from '@common-ui/inputs/details-radio/details-radio.component';
import { SystemDefaults } from '../../../account/utils/dialog-defaults'; import { SystemDefaults } from '../../../account/utils/dialog-defaults';
import { validatePageRange } from '../../utils/form-validators'; import { validatePageRange } from '../../utils/form-validators';
import { formValueToSignal } from '@common-ui/utils';
export const NON_READABLE_CONTENT = 'non-readable content'; export const NON_READABLE_CONTENT = 'non-readable content';
@ -68,7 +69,8 @@ export class RectangleAnnotationDialog
protected readonly options: DetailsRadioOption<RectangleRedactOption>[]; protected readonly options: DetailsRadioOption<RectangleRedactOption>[];
protected legalOptions: LegalBasisOption[] = []; protected legalOptions: LegalBasisOption[] = [];
readonly form: UntypedFormGroup; readonly form: UntypedFormGroup = this.#getForm();
readonly reasonValue = formValueToSignal<ILegalBasisChangeRequest>(this.form.get('reason'));
constructor( constructor(
private readonly activeDossiersService: ActiveDossiersService, private readonly activeDossiersService: ActiveDossiersService,
@ -81,7 +83,6 @@ export class RectangleAnnotationDialog
this.options = getRectangleRedactOptions(); this.options = getRectangleRedactOptions();
this.form = this.#getForm();
this.initialFormValue = this.form.getRawValue(); this.initialFormValue = this.form.getRawValue();
} }

View File

@ -22,7 +22,7 @@
<mat-form-field> <mat-form-field>
<mat-select [placeholder]="'redact-text.dialog.content.unchanged' | translate" formControlName="dictionary"> <mat-select [placeholder]="'redact-text.dialog.content.unchanged' | translate" formControlName="dictionary">
<mat-select-trigger>{{ displayedDictionaryLabel }}</mat-select-trigger> <mat-select-trigger>{{ displayedDictionaryLabel() }}</mat-select-trigger>
<mat-option <mat-option
(click)="typeChanged()" (click)="typeChanged()"
*ngFor="let dictionary of dictionaries" *ngFor="let dictionary of dictionaries"

View File

@ -1,6 +1,5 @@
import { NgForOf, NgIf } from '@angular/common'; import { NgForOf, NgIf } from '@angular/common';
import { Component, inject, OnInit } from '@angular/core'; import { Component, computed, effect, inject, OnInit } from '@angular/core';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { FormBuilder, ReactiveFormsModule } from '@angular/forms'; import { FormBuilder, ReactiveFormsModule } from '@angular/forms';
import { MatDialogClose } from '@angular/material/dialog'; import { MatDialogClose } from '@angular/material/dialog';
import { MatFormField } from '@angular/material/form-field'; import { MatFormField } from '@angular/material/form-field';
@ -16,7 +15,6 @@ import { ActiveDossiersService } from '@services/dossiers/active-dossiers.servic
import { DictionaryService } from '@services/entity-services/dictionary.service'; import { DictionaryService } from '@services/entity-services/dictionary.service';
import { JustificationsService } from '@services/entity-services/justifications.service'; import { JustificationsService } from '@services/entity-services/justifications.service';
import { firstValueFrom } from 'rxjs'; import { firstValueFrom } from 'rxjs';
import { tap } from 'rxjs/operators';
import { import {
SelectedAnnotationsTableComponent, SelectedAnnotationsTableComponent,
ValueColumn, ValueColumn,
@ -30,6 +28,7 @@ import {
RedactRecommendationResult, RedactRecommendationResult,
ResizeOptions, ResizeOptions,
} from '../../utils/dialog-types'; } from '../../utils/dialog-types';
import { formValueToSignal } from '@common-ui/utils';
@Component({ @Component({
templateUrl: './redact-recommendation-dialog.component.html', templateUrl: './redact-recommendation-dialog.component.html',
@ -81,6 +80,17 @@ export class RedactRecommendationDialogComponent
{ label: redaction.typeLabel }, { label: redaction.typeLabel },
]); ]);
readonly dictionaryType = formValueToSignal(this.form.controls.dictionary);
readonly optionValue = formValueToSignal<DetailsRadioOption<RedactOrHintOption>>(this.form.controls.option);
readonly displayedDictionaryLabel = computed(() => {
const dictType = this.dictionaryType();
if (dictType) {
return this.dictionaries.find(d => d.type === dictType)?.label ?? null;
}
return null;
});
constructor( constructor(
private readonly _justificationsService: JustificationsService, private readonly _justificationsService: JustificationsService,
private readonly _dictionaryService: DictionaryService, private readonly _dictionaryService: DictionaryService,
@ -88,32 +98,19 @@ export class RedactRecommendationDialogComponent
super(); super();
this.options = getRedactOrHintOptions(this.#dossier, this.#applyToAllDossiers, this.data.isApprover, false, true); this.options = getRedactOrHintOptions(this.#dossier, this.#applyToAllDossiers, this.data.isApprover, false, true);
this.form.controls.option.valueChanges effect(() => {
.pipe( this.dictionaryRequest = this.optionValue().value === RedactOrHintOptions.IN_DOSSIER;
tap((option: DetailsRadioOption<RedactOrHintOption>) => {
this.dictionaryRequest = option.value === RedactOrHintOptions.IN_DOSSIER;
this.#setDictionaries(); this.#setDictionaries();
this.#resetValues(); this.#resetValues();
}), });
takeUntilDestroyed(),
)
.subscribe();
this.form.controls.option.setValue(this.options[0]); this.form.controls.option.setValue(this.options[0]);
} }
get isBulkLocal(): boolean { get #isBulkLocal(): boolean {
return this.form.controls.option.value.value === ResizeOptions.IN_DOCUMENT; return this.form.controls.option.value.value === ResizeOptions.IN_DOCUMENT;
} }
get displayedDictionaryLabel() {
const dictType = this.form.controls.dictionary.value;
if (dictType) {
return this.dictionaries.find(d => d.type === dictType)?.label ?? null;
}
return null;
}
get disabled() { get disabled() {
return !this.form.controls.dictionary.value; return !this.form.controls.dictionary.value;
} }
@ -157,7 +154,7 @@ export class RedactRecommendationDialogComponent
this.close({ this.close({
redaction, redaction,
isMulti: this.isMulti, isMulti: this.isMulti,
bulkLocal: this.isBulkLocal, bulkLocal: this.#isBulkLocal,
}); });
} }
@ -185,7 +182,7 @@ export class RedactRecommendationDialogComponent
} }
const commentValue = this.form.controls.comment.value; const commentValue = this.form.controls.comment.value;
addRedactionRequest.comment = commentValue ? (this.isBulkLocal ? commentValue : { text: commentValue }) : null; addRedactionRequest.comment = commentValue ? (this.#isBulkLocal ? commentValue : { text: commentValue }) : null;
addRedactionRequest.addToAllDossiers = this.data.isApprover && this.dictionaryRequest && this.#applyToAllDossiers; addRedactionRequest.addToAllDossiers = this.data.isApprover && this.dictionaryRequest && this.#applyToAllDossiers;
return addRedactionRequest; return addRedactionRequest;
} }

View File

@ -91,7 +91,7 @@
<mat-form-field> <mat-form-field>
<mat-select [placeholder]="'redact-text.dialog.content.type-placeholder' | translate" formControlName="dictionary"> <mat-select [placeholder]="'redact-text.dialog.content.type-placeholder' | translate" formControlName="dictionary">
<mat-select-trigger>{{ displayedDictionaryLabel$ | async }}</mat-select-trigger> <mat-select-trigger>{{ displayedDictionaryLabel() }}</mat-select-trigger>
<mat-option <mat-option
(click)="typeChanged()" (click)="typeChanged()"
*ngFor="let dictionary of dictionaries" *ngFor="let dictionary of dictionaries"
@ -120,7 +120,7 @@
<div class="dialog-actions"> <div class="dialog-actions">
<iqser-icon-button <iqser-icon-button
[disabled]="!form.valid" [disabled]="!isFormValid()"
[label]="'redact-text.dialog.actions.save' | translate" [label]="'redact-text.dialog.actions.save' | translate"
[submit]="true" [submit]="true"
[type]="iconButtonTypes.primary" [type]="iconButtonTypes.primary"

View File

@ -1,6 +1,5 @@
import { AsyncPipe, NgClass, NgForOf, NgIf, NgStyle } from '@angular/common'; import { AsyncPipe, NgClass, NgForOf, NgIf, NgStyle } from '@angular/common';
import { Component, inject, OnInit } from '@angular/core'; import { Component, computed, effect, inject, OnInit } from '@angular/core';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { FormBuilder, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms'; import { FormBuilder, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms';
import { MatDialogClose } from '@angular/material/dialog'; import { MatDialogClose } from '@angular/material/dialog';
import { MatFormField } from '@angular/material/form-field'; import { MatFormField } from '@angular/material/form-field';
@ -17,8 +16,7 @@ import { JustificationsService } from '@services/entity-services/justifications.
import { Roles } from '@users/roles'; import { Roles } from '@users/roles';
import { UserPreferenceService } from '@users/user-preference.service'; import { UserPreferenceService } from '@users/user-preference.service';
import { calcTextWidthInPixels, stringToBoolean } from '@utils/functions'; import { calcTextWidthInPixels, stringToBoolean } from '@utils/functions';
import { firstValueFrom, Observable } from 'rxjs'; import { firstValueFrom } from 'rxjs';
import { map, tap } from 'rxjs/operators';
import { SystemDefaultOption, SystemDefaults } from '../../../account/utils/dialog-defaults'; import { SystemDefaultOption, SystemDefaults } from '../../../account/utils/dialog-defaults';
import { getRedactOrHintOptions } from '../../utils/dialog-options'; import { getRedactOrHintOptions } from '../../utils/dialog-options';
import { import {
@ -30,6 +28,7 @@ import {
ResizeOptions, ResizeOptions,
} from '../../utils/dialog-types'; } from '../../utils/dialog-types';
import { enhanceManualRedactionRequest, EnhanceRequestData } from '../../utils/enhance-manual-redaction-request.utils'; import { enhanceManualRedactionRequest, EnhanceRequestData } from '../../utils/enhance-manual-redaction-request.utils';
import { formStatusToSignal, formValueToSignal } from '@common-ui/utils';
const MAXIMUM_TEXT_AREA_WIDTH = 421; const MAXIMUM_TEXT_AREA_WIDTH = 421;
@ -63,19 +62,33 @@ export class RedactTextDialogComponent
{ {
readonly #dossier = inject(ActiveDossiersService).find(this.data.dossierId); readonly #dossier = inject(ActiveDossiersService).find(this.data.dossierId);
readonly #manualRedactionTypeExists = inject(DictionaryService).hasManualType(this.#dossier.dossierTemplateId); readonly #manualRedactionTypeExists = inject(DictionaryService).hasManualType(this.#dossier.dossierTemplateId);
#applyToAllDossiers = this.applyToAll; #applyToAllDossiers = this.#applyToAll;
readonly roles = Roles; readonly roles = Roles;
readonly iconButtonTypes = IconButtonTypes; readonly iconButtonTypes = IconButtonTypes;
readonly initialText = this.data?.manualRedactionEntryWrapper?.manualRedactionEntry?.value; readonly initialText = this.data?.manualRedactionEntryWrapper?.manualRedactionEntry?.value;
readonly form: FormGroup; readonly options: DetailsRadioOption<RedactOrHintOption>[] = getRedactOrHintOptions(
this.#dossier,
this.#applyToAllDossiers,
this.data.isApprover,
this.data.isPageExcluded,
);
readonly form: FormGroup = this.#getForm();
readonly formStatus = formStatusToSignal(this.form);
readonly isFormValid = computed(() => this.formStatus() === 'VALID');
readonly dictionaryType = formValueToSignal(this.form.controls.dictionary);
readonly displayedDictionaryLabel = computed(() => {
return this.dictionaries.find(d => d.type === this.dictionaryType())?.label ?? null;
});
readonly optionValue = formValueToSignal<DetailsRadioOption<RedactOrHintOption>>(this.form.controls.option);
dictionaryRequest = false; dictionaryRequest = false;
legalOptions: LegalBasisOption[] = []; legalOptions: LegalBasisOption[] = [];
dictionaries: Dictionary[] = []; dictionaries: Dictionary[] = [];
isEditingSelectedText = false; isEditingSelectedText = false;
modifiedText = this.initialText; modifiedText = this.initialText;
selectedTextRows = 1; selectedTextRows = 1;
readonly options: DetailsRadioOption<RedactOrHintOption>[];
readonly displayedDictionaryLabel$: Observable<string>;
readonly maximumTextAreaWidth = MAXIMUM_TEXT_AREA_WIDTH; readonly maximumTextAreaWidth = MAXIMUM_TEXT_AREA_WIDTH;
readonly maximumSelectedTextWidth = 567; readonly maximumSelectedTextWidth = 567;
@ -88,14 +101,11 @@ export class RedactTextDialogComponent
private readonly _userPreferences: UserPreferenceService, private readonly _userPreferences: UserPreferenceService,
) { ) {
super(); super();
this.options = getRedactOrHintOptions(this.#dossier, this.#applyToAllDossiers, this.data.isApprover, this.data.isPageExcluded);
this.form = this.#getForm();
this.#setupValidators(this.dictionaryRequest ? RedactOrHintOptions.IN_DOSSIER : RedactOrHintOptions.ONLY_HERE); this.#setupValidators(this.dictionaryRequest ? RedactOrHintOptions.IN_DOSSIER : RedactOrHintOptions.ONLY_HERE);
this.textWidth = calcTextWidthInPixels(this.form.controls.selectedText.value); this.textWidth = calcTextWidthInPixels(this.form.controls.selectedText.value);
this.form.controls.option.valueChanges
.pipe( effect(() => {
tap((option: DetailsRadioOption<RedactOrHintOption>) => { this.dictionaryRequest = this.optionValue().value === RedactOrHintOptions.IN_DOSSIER;
this.dictionaryRequest = option.value === RedactOrHintOptions.IN_DOSSIER;
if (this.dictionaryRequest) { if (this.dictionaryRequest) {
this.#setDictionaries(); this.#setDictionaries();
this.form.patchValue({ selectedText: this.modifiedText }); this.form.patchValue({ selectedText: this.modifiedText });
@ -104,28 +114,21 @@ export class RedactTextDialogComponent
this.modifiedText = this.form.controls.selectedText.value; this.modifiedText = this.form.controls.selectedText.value;
this.form.patchValue({ selectedText: this.initialText }, { emitEvent: true }); this.form.patchValue({ selectedText: this.initialText }, { emitEvent: true });
} }
this.#setupValidators(option.value); this.#setupValidators(this.optionValue().value);
this.#resetValues(); this.#resetValues();
}), });
takeUntilDestroyed(),
)
.subscribe();
this.displayedDictionaryLabel$ = this.form.controls.dictionary.valueChanges.pipe(
map(dictionary => this.dictionaries.find(d => d.type === dictionary)?.label ?? null),
);
} }
get isBulkLocal() { get #isBulkLocal() {
return this.form.controls.option.value.value === ResizeOptions.IN_DOCUMENT; return this.form.controls.option.value.value === ResizeOptions.IN_DOCUMENT;
} }
get isSystemDefault(): boolean { get #isSystemDefault(): boolean {
return this._userPreferences.getAddRedactionDefaultOption() === SystemDefaultOption.SYSTEM_DEFAULT; return this._userPreferences.getAddRedactionDefaultOption() === SystemDefaultOption.SYSTEM_DEFAULT;
} }
get defaultOption() { get #defaultOption() {
const defaultOption = this.isSystemDefault const defaultOption = this.#isSystemDefault
? this.#getOption(SystemDefaults.ADD_REDACTION_DEFAULT) ? this.#getOption(SystemDefaults.ADD_REDACTION_DEFAULT)
: this.#getOption(this._userPreferences.getAddRedactionDefaultOption() as RedactOrHintOption); : this.#getOption(this._userPreferences.getAddRedactionDefaultOption() as RedactOrHintOption);
this.dictionaryRequest = defaultOption.value === RedactOrHintOptions.IN_DOSSIER; this.dictionaryRequest = defaultOption.value === RedactOrHintOptions.IN_DOSSIER;
@ -136,8 +139,8 @@ export class RedactTextDialogComponent
return defaultOption ?? this.options[0]; return defaultOption ?? this.options[0];
} }
get applyToAll() { get #applyToAll() {
return this.isSystemDefault || this._userPreferences.getAddRedactionDefaultExtraOption() === 'undefined' return this.#isSystemDefault || this._userPreferences.getAddRedactionDefaultExtraOption() === 'undefined'
? this.data.applyToAllDossiers ?? true ? this.data.applyToAllDossiers ?? true
: stringToBoolean(this._userPreferences.getAddRedactionDefaultExtraOption()); : stringToBoolean(this._userPreferences.getAddRedactionDefaultExtraOption());
} }
@ -186,7 +189,7 @@ export class RedactTextDialogComponent
this.close({ this.close({
redaction, redaction,
dictionary: this.dictionaries.find(d => d.type === this.form.controls.dictionary.value), dictionary: this.dictionaries.find(d => d.type === this.form.controls.dictionary.value),
bulkLocal: this.isBulkLocal, bulkLocal: this.#isBulkLocal,
}); });
} }
@ -228,7 +231,7 @@ export class RedactTextDialogComponent
reason: [null as LegalBasisOption], reason: [null as LegalBasisOption],
comment: [null], comment: [null],
dictionary: [this.#manualRedactionTypeExists ? SuperTypes.ManualRedaction : null], dictionary: [this.#manualRedactionTypeExists ? SuperTypes.ManualRedaction : null],
option: this.defaultOption, option: this.#defaultOption,
}); });
} }
@ -239,7 +242,7 @@ export class RedactTextDialogComponent
} }
#resetValues() { #resetValues() {
this.#applyToAllDossiers = this.applyToAll; this.#applyToAllDossiers = this.#applyToAll;
this.options[2].additionalCheck.checked = this.#applyToAllDossiers; this.options[2].additionalCheck.checked = this.#applyToAllDossiers;
if (this.dictionaryRequest) { if (this.dictionaryRequest) {
this.form.controls.reason.setValue(null); this.form.controls.reason.setValue(null);
@ -263,7 +266,7 @@ export class RedactTextDialogComponent
comment: this.form.controls.comment.value, comment: this.form.controls.comment.value,
isApprover: this.data.isApprover, isApprover: this.data.isApprover,
applyToAllDossiers: this.#applyToAllDossiers, applyToAllDossiers: this.#applyToAllDossiers,
bulkLocal: this.isBulkLocal, bulkLocal: this.#isBulkLocal,
}; };
} }
} }

View File

@ -1,6 +1,5 @@
import { NgStyle } from '@angular/common'; import { NgStyle } from '@angular/common';
import { Component, computed } from '@angular/core'; import { Component, computed } from '@angular/core';
import { toSignal } from '@angular/core/rxjs-interop';
import { FormBuilder, ReactiveFormsModule, UntypedFormGroup } from '@angular/forms'; import { FormBuilder, ReactiveFormsModule, UntypedFormGroup } from '@angular/forms';
import { MatDialogClose } from '@angular/material/dialog'; import { MatDialogClose } from '@angular/material/dialog';
import { DetailsRadioOption } from '@common-ui/inputs/details-radio/details-radio-option'; import { DetailsRadioOption } from '@common-ui/inputs/details-radio/details-radio-option';
@ -18,7 +17,6 @@ import { TranslateModule } from '@ngx-translate/core';
import { Roles } from '@users/roles'; import { Roles } from '@users/roles';
import { UserPreferenceService } from '@users/user-preference.service'; import { UserPreferenceService } from '@users/user-preference.service';
import { stringToBoolean } from '@utils/functions'; import { stringToBoolean } from '@utils/functions';
import { map } from 'rxjs/operators';
import { SystemDefaultOption, SystemDefaults } from '../../../account/utils/dialog-defaults'; import { SystemDefaultOption, SystemDefaults } from '../../../account/utils/dialog-defaults';
import { import {
SelectedAnnotationsTableComponent, SelectedAnnotationsTableComponent,
@ -34,7 +32,7 @@ import {
RemoveRedactionResult, RemoveRedactionResult,
ResizeOptions, ResizeOptions,
} from '../../utils/dialog-types'; } from '../../utils/dialog-types';
import { isJustOne } from '@common-ui/utils'; import { formValueToSignal, isJustOne } from '@common-ui/utils';
import { validatePageRange } from '../../utils/form-validators'; import { validatePageRange } from '../../utils/form-validators';
import { parseRectanglePosition, parseSelectedPageNumbers } from '../../utils/enhance-manual-redaction-request.utils'; import { parseRectanglePosition, parseSelectedPageNumbers } from '../../utils/enhance-manual-redaction-request.utils';
@ -112,11 +110,13 @@ export class RemoveRedactionDialogComponent extends IqserDialogComponent<
readonly redactedTexts = this.data.redactions.map(annotation => annotation.value); readonly redactedTexts = this.data.redactions.map(annotation => annotation.value);
form: UntypedFormGroup = this._formBuilder.group({ form: UntypedFormGroup = this._formBuilder.group({
comment: [null], comment: [null],
option: [this.defaultOption, validatePageRange(true)], option: [this.#defaultOption, validatePageRange(true)],
}); });
readonly selectedOption = toSignal(this.form.get('option').valueChanges.pipe(map(value => value.value))); readonly optionValue = formValueToSignal<DetailsRadioOption<RectangleRedactOption | RemoveRedactionOption>>(this.form.controls.option);
readonly isFalsePositive = computed(() => this.selectedOption() === RemoveRedactionOptions.FALSE_POSITIVE); readonly isFalsePositive = computed(() => this.optionValue().value === RemoveRedactionOptions.FALSE_POSITIVE);
readonly hasFalsePositiveOption = !!this.options.find(option => option.value === RemoveRedactionOptions.FALSE_POSITIVE);
readonly tableColumns = computed<ValueColumn[]>(() => [ readonly tableColumns = computed<ValueColumn[]>(() => [
{ label: 'Value', width: '25%' }, { label: 'Value', width: '25%' },
{ label: 'Type', width: '25%' }, { label: 'Type', width: '25%' },
@ -135,6 +135,11 @@ export class RemoveRedactionDialogComponent extends IqserDialogComponent<
]), ]),
); );
readonly isBulk = !isJustOne(this.data.redactions);
readonly redactedTextsAreaHeight = this.redactedTexts.length <= 10 ? 18 * this.redactedTexts.length : 180;
readonly dialogContentHeight = this.options.length * 75 + 230;
readonly typeTranslationArg = { type: this.annotationsType };
constructor( constructor(
private readonly _formBuilder: FormBuilder, private readonly _formBuilder: FormBuilder,
private readonly _userPreferences: UserPreferenceService, private readonly _userPreferences: UserPreferenceService,
@ -142,32 +147,12 @@ export class RemoveRedactionDialogComponent extends IqserDialogComponent<
super(); super();
} }
get hasFalsePositiveOption() { get #defaultOption() {
return !!this.options.find(option => option.value === RemoveRedactionOptions.FALSE_POSITIVE);
}
get defaultOption() {
const removeDefaultOption = this.#getOption(this.defaultOptionPreference as RemoveRedactionOption); const removeDefaultOption = this.#getOption(this.defaultOptionPreference as RemoveRedactionOption);
if (!!removeDefaultOption && !removeDefaultOption.disabled) return removeDefaultOption; if (!!removeDefaultOption && !removeDefaultOption.disabled) return removeDefaultOption;
return this.options[0]; return this.options[0];
} }
get typeTranslationArg() {
return { type: this.annotationsType };
}
get isBulk() {
return !isJustOne(this.data.redactions);
}
get redactedTextsAreaHeight() {
return this.redactedTexts.length <= 10 ? 18 * this.redactedTexts.length : 180;
}
get dialogContentHeight() {
return this.options.length * 75 + 230;
}
extraOptionChanged(option: DetailsRadioOption<RemoveRedactionOption | RectangleRedactOption>): void { extraOptionChanged(option: DetailsRadioOption<RemoveRedactionOption | RectangleRedactOption>): void {
if (option.value === RectangleRedactOptions.MULTIPLE_PAGES) { if (option.value === RectangleRedactOptions.MULTIPLE_PAGES) {
setTimeout(() => { setTimeout(() => {

View File

@ -3,7 +3,7 @@
<div [innerHTML]="'resize-redaction.dialog.header' | translate: { type: dialogHeaderType }" class="dialog-header heading-l"></div> <div [innerHTML]="'resize-redaction.dialog.header' | translate: { type: dialogHeaderType }" class="dialog-header heading-l"></div>
<div class="dialog-content redaction"> <div class="dialog-content redaction">
<ng-container *ngIf="!redaction.isImage && !redaction.AREA"> <ng-container *ngIf="!isImage && !redaction.AREA">
<div class="flex-start"> <div class="flex-start">
<label [translate]="'resize-redaction.dialog.content.original-text'"></label> <label [translate]="'resize-redaction.dialog.content.original-text'"></label>
<span class="multi-line-ellipsis" <span class="multi-line-ellipsis"
@ -26,7 +26,7 @@
<mat-form-field> <mat-form-field>
<mat-select formControlName="dictionary"> <mat-select formControlName="dictionary">
<mat-select-trigger>{{ displayedDictionaryLabel }}</mat-select-trigger> <mat-select-trigger>{{ displayedDictionaryLabel() }}</mat-select-trigger>
<mat-option [value]="entity?.type"> <mat-option [value]="entity?.type">
<span> {{ redaction.typeLabel }} </span> <span> {{ redaction.typeLabel }} </span>
</mat-option> </mat-option>

View File

@ -1,5 +1,5 @@
import { NgIf } from '@angular/common'; import { NgIf } from '@angular/common';
import { Component, inject } from '@angular/core'; import { Component, computed, inject } from '@angular/core';
import { FormBuilder, FormControl, FormGroup, ReactiveFormsModule } from '@angular/forms'; import { FormBuilder, FormControl, FormGroup, ReactiveFormsModule } from '@angular/forms';
import { MatDialogClose } from '@angular/material/dialog'; import { MatDialogClose } from '@angular/material/dialog';
import { MatFormField } from '@angular/material/form-field'; import { MatFormField } from '@angular/material/form-field';
@ -12,6 +12,13 @@ import { ActiveDossiersService } from '@services/dossiers/active-dossiers.servic
import { DictionariesMapService } from '@services/entity-services/dictionaries-map.service'; import { DictionariesMapService } from '@services/entity-services/dictionaries-map.service';
import { getResizeRedactionOptions } from '../../utils/dialog-options'; import { getResizeRedactionOptions } from '../../utils/dialog-options';
import { ResizeOptions, ResizeRedactionData, ResizeRedactionOption, ResizeRedactionResult } from '../../utils/dialog-types'; import { ResizeOptions, ResizeRedactionData, ResizeRedactionOption, ResizeRedactionResult } from '../../utils/dialog-types';
import { AsControl, formValueToSignal } from '@common-ui/utils';
interface ResizeForm {
comment: string;
dictionary: string;
option: DetailsRadioOption<ResizeRedactionOption>;
}
@Component({ @Component({
templateUrl: './resize-redaction-dialog.component.html', templateUrl: './resize-redaction-dialog.component.html',
@ -44,11 +51,17 @@ export class ResizeRedactionDialogComponent extends IqserDialogComponent<
readonly dictionaries = inject(DictionariesMapService).get(this.#dossier.dossierTemplateId); readonly dictionaries = inject(DictionariesMapService).get(this.#dossier.dossierTemplateId);
readonly entity = this.dictionaries.find(d => d.type === this.data.redaction.type); readonly entity = this.dictionaries.find(d => d.type === this.data.redaction.type);
readonly redaction = this.data.redaction; readonly redaction = this.data.redaction;
readonly form: FormGroup<{ readonly isImage = this.redaction.isImage;
comment: FormControl<string>; readonly form: FormGroup<AsControl<ResizeForm>> = this.#getForm();
dictionary: FormControl<string>; readonly dialogHeaderType = this.data.redaction.HINT ? 'hint' : this.data.redaction.isSkippedImageHint ? 'image' : 'redaction';
option: FormControl<DetailsRadioOption<ResizeRedactionOption>>; readonly dictionaryType = formValueToSignal(this.form.get('dictionary'));
}>; readonly displayedDictionaryLabel = computed(() => {
const dictType = this.dictionaryType();
if (dictType) {
return this.dictionaries.find(d => d.type === dictType)?.label ?? null;
}
return null;
});
constructor(private readonly _formBuilder: FormBuilder) { constructor(private readonly _formBuilder: FormBuilder) {
super(); super();
@ -60,19 +73,6 @@ export class ResizeRedactionDialogComponent extends IqserDialogComponent<
this.data.isApprover, this.data.isApprover,
this.data.permissions.canResizeInDictionary, this.data.permissions.canResizeInDictionary,
); );
this.form = this.#getForm();
}
get dialogHeaderType() {
return this.data.redaction.HINT ? 'hint' : this.data.redaction.isSkippedImageHint ? 'image' : 'redaction';
}
get displayedDictionaryLabel() {
const dictType = this.form.get('dictionary').value;
if (dictType) {
return this.dictionaries.find(d => d.type === dictType)?.label ?? null;
}
return null;
} }
save() { save() {

View File

@ -5,11 +5,12 @@
<div class="content-inner"> <div class="content-inner">
<div class="content-container"> <div class="content-container">
@if (isDocumine) {
<redaction-structured-component-management <redaction-structured-component-management
*ngIf="isDocumine"
[file]="file" [file]="file"
[dictionaries]="state.dictionaries" [dictionaries]="state.dictionaries()"
></redaction-structured-component-management> ></redaction-structured-component-management>
}
</div> </div>
<div class="right-container" [class.documine-container]="isDocumine"> <div class="right-container" [class.documine-container]="isDocumine">

View File

@ -1,4 +1,4 @@
import { Injectable } from '@angular/core'; import { Injectable, untracked } from '@angular/core';
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker'; import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
import { getConfig } from '@iqser/common-ui'; import { getConfig } from '@iqser/common-ui';
import { Filter, handleCheckedValue, IFilter, INestedFilter, NestedFilter } from '@iqser/common-ui/lib/filtering'; import { Filter, handleCheckedValue, IFilter, INestedFilter, NestedFilter } from '@iqser/common-ui/lib/filtering';
@ -78,7 +78,8 @@ export class AnnotationProcessingService {
const filters: INestedFilter[] = []; const filters: INestedFilter[] = [];
this._fileDataService.all?.forEach(a => { this._fileDataService.all?.forEach(a => {
const dictionary = this._state.dictionaries.find(dictionary => dictionary.type === a.type); const dictionaries = untracked(this._state.dictionaries);
const dictionary = dictionaries.find(dictionary => dictionary.type === a.type);
const doesTypeExist = !!dictionary; const doesTypeExist = !!dictionary;
if ( if (
(this.#isDocumine && !this.#devMode && a.isOCR) || (this.#isDocumine && !this.#devMode && a.isOCR) ||

View File

@ -1,4 +1,4 @@
import { effect, inject, Injectable, Signal, signal } from '@angular/core'; import { effect, inject, Injectable, Signal, signal, untracked } from '@angular/core';
import { toObservable } from '@angular/core/rxjs-interop'; import { toObservable } from '@angular/core/rxjs-interop';
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker'; import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
import { EntitiesService, getConfig, Toaster } from '@iqser/common-ui'; import { EntitiesService, getConfig, Toaster } from '@iqser/common-ui';
@ -180,7 +180,7 @@ export class FileDataService extends EntitiesService<AnnotationWrapper, Annotati
const file = this._state.file(); const file = this._state.file();
let annotations: AnnotationWrapper[] = []; let annotations: AnnotationWrapper[] = [];
const defaultColors = this._defaultColorsService.find(this._state.dossierTemplateId); const defaultColors = this._defaultColorsService.find(this._state.dossierTemplateId);
let dictionaries = this._state.dictionaries; let dictionaries = untracked(this._state.dictionaries);
let checkDictionary = true; let checkDictionary = true;
for (const entry of entityLog.entityLogEntry) { for (const entry of entityLog.entityLogEntry) {

View File

@ -43,12 +43,14 @@ export class FilePreviewStateService {
readonly blob$: Observable<Blob>; readonly blob$: Observable<Blob>;
readonly componentReferenceIds$: Observable<string[] | null>; readonly componentReferenceIds$: Observable<string[] | null>;
readonly #componentReferenceIds$ = new BehaviorSubject<string[] | null>(null); readonly #componentReferenceIds$ = new BehaviorSubject<string[] | null>(null);
readonly componentReferenceIdsSignal: Signal<string[] | null>;
readonly dossierId = getParam(DOSSIER_ID); readonly dossierId = getParam(DOSSIER_ID);
readonly dossierTemplateId = getParam(DOSSIER_TEMPLATE_ID); readonly dossierTemplateId = getParam(DOSSIER_TEMPLATE_ID);
readonly fileId = getParam(FILE_ID); readonly fileId = getParam(FILE_ID);
readonly excludedPages: WritableSignal<number[]>; readonly excludedPages: WritableSignal<number[]>;
readonly updateExcludedPagesStyle = computed(() => this.excludedPages()); readonly updateExcludedPagesStyle = computed(() => this.excludedPages());
readonly isEditingReviewer = signal(false); readonly isEditingReviewer = signal(false);
readonly dictionaries: Signal<Dictionary[]>;
constructor( constructor(
private readonly _permissionsService: PermissionsService, private readonly _permissionsService: PermissionsService,
@ -76,6 +78,14 @@ export class FilePreviewStateService {
this.blob$ = this.#blob$; this.blob$ = this.#blob$;
this.dossierDictionary = toSignal(inject(DossierDictionariesMapService).watch$(this.dossierId, 'dossier_redaction')); this.dossierDictionary = toSignal(inject(DossierDictionariesMapService).watch$(this.dossierId, 'dossier_redaction'));
this.dictionaries = computed(() => {
const dictionaries = this._dictionariesMapService.get(this.dossierTemplateId);
if (this.dossierDictionary()) {
dictionaries.push(this.dossierDictionary());
}
return dictionaries;
});
this.#dossierFilesChange$ this.#dossierFilesChange$
.pipe( .pipe(
switchMap(() => this._filesService.loadAll(this.dossierId)), switchMap(() => this._filesService.loadAll(this.dossierId)),
@ -92,21 +102,14 @@ export class FilePreviewStateService {
}, },
{ allowSignalWrites: true }, { allowSignalWrites: true },
); );
this.componentReferenceIdsSignal = toSignal(this.componentReferenceIds$);
} }
set componentReferenceIds(ids: string[]) { set componentReferenceIds(ids: string[]) {
this.#componentReferenceIds$.next(ids); this.#componentReferenceIds$.next(ids);
} }
get dictionaries(): Dictionary[] {
const dictionaries = this._dictionariesMapService.get(this.dossierTemplateId);
if (this.dossierDictionary()) {
dictionaries.push(this.dossierDictionary());
}
return dictionaries;
}
get blob(): Promise<Blob> { get blob(): Promise<Blob> {
return firstValueFrom(this.blob$); return firstValueFrom(this.blob$);
} }
@ -133,10 +136,6 @@ export class FilePreviewStateService {
); );
} }
get componentReferenceIds() {
return this.#componentReferenceIds$.getValue();
}
reloadBlob(): void { reloadBlob(): void {
this.#reloadBlob$.next(true); this.#reloadBlob$.next(true);
} }

View File

@ -1,4 +1,4 @@
import { inject, Injectable, NgZone } from '@angular/core'; import { inject, Injectable, NgZone, untracked } from '@angular/core';
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker'; import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
import { getConfig, IqserPermissionsService } from '@iqser/common-ui'; import { getConfig, IqserPermissionsService } from '@iqser/common-ui';
import { AnnotationPermissions } from '@models/file/annotation.permissions'; import { AnnotationPermissions } from '@models/file/annotation.permissions';
@ -135,7 +135,7 @@ export class PdfAnnotationActionsService {
#getAnnotationsPermissions(annotations: AnnotationWrapper[]): AnnotationPermissions { #getAnnotationsPermissions(annotations: AnnotationWrapper[]): AnnotationPermissions {
const dossier = this.#state.dossier(); const dossier = this.#state.dossier();
const isApprover = this.#permissionsService.isApprover(dossier); const isApprover = this.#permissionsService.isApprover(dossier);
const dictionaries = this.#state.dictionaries; const dictionaries = untracked(this.#state.dictionaries);
const autoAnalysisDisabled = this.#state.file().excludedFromAutomaticAnalysis; const autoAnalysisDisabled = this.#state.file().excludedFromAutomaticAnalysis;
const permissions = annotations.map(a => const permissions = annotations.map(a =>
AnnotationPermissions.forUser(isApprover, a, dictionaries, this.#iqserPermissionsService, autoAnalysisDisabled), AnnotationPermissions.forUser(isApprover, a, dictionaries, this.#iqserPermissionsService, autoAnalysisDisabled),

View File

@ -2,8 +2,6 @@ import { ITrackable } from '@iqser/common-ui';
import type { List } from '@iqser/common-ui/lib/utils'; import type { List } from '@iqser/common-ui/lib/utils';
import type { AnnotationWrapper } from '@models/file/annotation.wrapper'; import type { AnnotationWrapper } from '@models/file/annotation.wrapper';
import { Dayjs } from 'dayjs'; import { Dayjs } from 'dayjs';
import { FormControl } from '@angular/forms';
import { toSignal } from '@angular/core/rxjs-interop';
export function hexToRgb(hex: string) { export function hexToRgb(hex: string) {
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex); const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
@ -145,7 +143,3 @@ export function urlFileId() {
const fileId = splitUrl[splitUrl.length - 1]; const fileId = splitUrl[splitUrl.length - 1];
return fileId.split('?')[0]; return fileId.split('?')[0];
} }
export function formControlToSignal<T>(control: FormControl<T>) {
return toSignal(control.valueChanges, { initialValue: control.value });
}

@ -1 +1 @@
Subproject commit ba85260cc4a3e780a37b3f41be26b83d045a1dcb Subproject commit 3f214d9726e17cd204acae8a4ef95749260b3c9a