Compare commits
133 Commits
master
...
release/4.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
45b82eadcf | ||
|
|
e0418c792d | ||
|
|
db7d1f24f5 | ||
|
|
2fe21f0481 | ||
|
|
fffcc80201 | ||
|
|
f30151e195 | ||
|
|
4b71abb42f | ||
|
|
3c5217c317 | ||
|
|
5a582ce9b4 | ||
|
|
3a469a4ab6 | ||
|
|
616a9ec087 | ||
|
|
da8c9fdd0a | ||
|
|
f452bdb9ae | ||
|
|
cf11093e7e | ||
|
|
a2f31c7cd6 | ||
|
|
227dc8965a | ||
|
|
2dacfe1674 | ||
|
|
fdcf25a3e3 | ||
|
|
d9e28e147a | ||
|
|
6c62c6060e | ||
|
|
d20d99530a | ||
|
|
9d173f57bf | ||
|
|
f3767cb692 | ||
|
|
ccc0012b68 | ||
|
|
4e1c0b9f40 | ||
|
|
7d9fdee26a | ||
|
|
9891b51e83 | ||
|
|
440384fa2d | ||
|
|
3ab5ccfaec | ||
|
|
d730875a45 | ||
|
|
87f7a8f394 | ||
|
|
b68127b35f | ||
|
|
e4bd2eac67 | ||
|
|
8cba3658f7 | ||
|
|
883bda2efa | ||
|
|
b19c152d0c | ||
|
|
d67e1ec285 | ||
|
|
8dba17266c | ||
|
|
0a352821cf | ||
|
|
eddb70e116 | ||
|
|
259d07f834 | ||
|
|
1d4deacf1b | ||
|
|
1e00979f08 | ||
|
|
332476ec46 | ||
|
|
2267af3a55 | ||
|
|
ab2af31f28 | ||
|
|
1d61d07305 | ||
|
|
1325031ff7 | ||
|
|
0bb5a2158d | ||
|
|
b05024cb99 | ||
|
|
e9bd45c7cc | ||
|
|
d673348def | ||
|
|
0d639b96a2 | ||
|
|
740d4cf071 | ||
|
|
33441d16c9 | ||
|
|
a48c457615 | ||
|
|
9484ce2377 | ||
|
|
8871e24660 | ||
|
|
e8c40353a5 | ||
|
|
6fb5af1820 | ||
|
|
493b40cdac | ||
|
|
27981baa81 | ||
|
|
c2b27765d3 | ||
|
|
c7f7ba28b6 | ||
|
|
a8941e2602 | ||
|
|
e05af05c4a | ||
|
|
6ff4d2acfa | ||
|
|
e036deb490 | ||
|
|
4f9dd026ab | ||
|
|
9bc95b8eee | ||
|
|
e084cb6c3b | ||
|
|
5a8f97ca8a | ||
|
|
e802ef3f7e | ||
|
|
b2e40b8f86 | ||
|
|
4dc7ecee78 | ||
|
|
416b0c925c | ||
|
|
9f7ff828c9 | ||
|
|
a513a93927 | ||
|
|
c206cb9b8f | ||
|
|
11b6912999 | ||
|
|
f1680de597 | ||
|
|
08b6e1ff8c | ||
|
|
66cc2a249f | ||
|
|
baa0f74b92 | ||
|
|
1b7ad118d4 | ||
|
|
c977f95f1e | ||
|
|
1da7b41692 | ||
|
|
6100c59a87 | ||
|
|
860146cd1a | ||
|
|
0b8a0f08d2 | ||
|
|
e167e94171 | ||
|
|
315bd225af | ||
|
|
6a9f440b8a | ||
|
|
0ba173b4b9 | ||
|
|
6d74ab60cc | ||
|
|
1f36b0be04 | ||
|
|
40d6718e8e | ||
|
|
cc22fdf538 | ||
|
|
4c6bb84567 | ||
|
|
41a3dce600 | ||
|
|
53cfc669d6 | ||
|
|
540649edbd | ||
|
|
32369d7121 | ||
|
|
a9a935b90d | ||
|
|
491977cb49 | ||
|
|
b620605613 | ||
|
|
a28f5fd727 | ||
|
|
2e2eaf476d | ||
|
|
b0d38e1b0f | ||
|
|
d4991f6806 | ||
|
|
9d18113f56 | ||
|
|
d04f1b7b0b | ||
|
|
b1948622fe | ||
|
|
eb4368c3ce | ||
|
|
9772145239 | ||
|
|
183b19c9da | ||
|
|
4c9b487c78 | ||
|
|
ba311ad8e3 | ||
|
|
62d4d18eaa | ||
|
|
7f8dca3098 | ||
|
|
289ee7f61d | ||
|
|
bf1f25c0dd | ||
|
|
773f2bfe9f | ||
|
|
ac1780ade4 | ||
|
|
5f309bffe0 | ||
|
|
611f293e64 | ||
|
|
bc7919551e | ||
|
|
4e3e64f2eb | ||
|
|
1233761ac3 | ||
|
|
fd3b99a785 | ||
|
|
f9361a2e82 | ||
|
|
4acb4d4eb7 | ||
|
|
7d4d2889da |
@ -50,7 +50,7 @@
|
|||||||
{
|
{
|
||||||
"glob": "**/*",
|
"glob": "**/*",
|
||||||
"input": "node_modules/@pdftron/webviewer/public/",
|
"input": "node_modules/@pdftron/webviewer/public/",
|
||||||
"output": "/assets/wv-resources/10.10.1/"
|
"output": "/assets/wv-resources/11.0.0/"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"glob": "**/*",
|
"glob": "**/*",
|
||||||
@ -73,7 +73,7 @@
|
|||||||
"stylePreprocessorOptions": {
|
"stylePreprocessorOptions": {
|
||||||
"includePaths": ["./apps/red-ui/src/assets/styles", "./libs/common-ui/src/assets/styles"]
|
"includePaths": ["./apps/red-ui/src/assets/styles", "./libs/common-ui/src/assets/styles"]
|
||||||
},
|
},
|
||||||
"scripts": ["node_modules/@pdftron/webviewer/webviewer.min.js", "node_modules/chart.js/auto/auto.cjs"],
|
"scripts": ["node_modules/chart.js/auto/auto.cjs"],
|
||||||
"extractLicenses": false,
|
"extractLicenses": false,
|
||||||
"sourceMap": true,
|
"sourceMap": true,
|
||||||
"optimization": false,
|
"optimization": false,
|
||||||
|
|||||||
@ -258,6 +258,7 @@ export const appModuleFactory = (config: AppConfig) => {
|
|||||||
provide: MAT_TOOLTIP_DEFAULT_OPTIONS,
|
provide: MAT_TOOLTIP_DEFAULT_OPTIONS,
|
||||||
useValue: {
|
useValue: {
|
||||||
disableTooltipInteractivity: true,
|
disableTooltipInteractivity: true,
|
||||||
|
showDelay: 1,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
BaseDatePipe,
|
BaseDatePipe,
|
||||||
|
|||||||
@ -22,7 +22,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.mat-mdc-menu-item.notification {
|
.mat-mdc-menu-item.notification {
|
||||||
padding: 8px 26px 10px 8px;
|
padding: 8px 26px 10px 8px !important;
|
||||||
margin: 2px 0 0 0;
|
margin: 2px 0 0 0;
|
||||||
height: fit-content;
|
height: fit-content;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|||||||
@ -11,7 +11,9 @@ import { DictionaryService } from '@services/entity-services/dictionary.service'
|
|||||||
import { DefaultColorsService } from '@services/entity-services/default-colors.service';
|
import { DefaultColorsService } from '@services/entity-services/default-colors.service';
|
||||||
import { WatermarkService } from '@services/entity-services/watermark.service';
|
import { WatermarkService } from '@services/entity-services/watermark.service';
|
||||||
import { FileAttributesService } from '@services/entity-services/file-attributes.service';
|
import { FileAttributesService } from '@services/entity-services/file-attributes.service';
|
||||||
import { getConfig } from '@iqser/common-ui';
|
import { getConfig, Toaster } from '@iqser/common-ui';
|
||||||
|
import { RulesService } from '../modules/admin/services/rules.service';
|
||||||
|
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
||||||
|
|
||||||
export function templateExistsWhenEnteringAdmin(): CanActivateFn {
|
export function templateExistsWhenEnteringAdmin(): CanActivateFn {
|
||||||
return async function (route: ActivatedRouteSnapshot): Promise<boolean> {
|
return async function (route: ActivatedRouteSnapshot): Promise<boolean> {
|
||||||
@ -21,12 +23,14 @@ export function templateExistsWhenEnteringAdmin(): CanActivateFn {
|
|||||||
const defaultColorsService = inject(DefaultColorsService);
|
const defaultColorsService = inject(DefaultColorsService);
|
||||||
const watermarksService = inject(WatermarkService);
|
const watermarksService = inject(WatermarkService);
|
||||||
const router = inject(Router);
|
const router = inject(Router);
|
||||||
|
const rulesService = inject(RulesService);
|
||||||
const isDocumine = getConfig().IS_DOCUMINE;
|
const isDocumine = getConfig().IS_DOCUMINE;
|
||||||
|
|
||||||
const dossierTemplate = inject(DossierTemplateStatsService).get(dossierTemplateId);
|
const dossierTemplate = inject(DossierTemplateStatsService).get(dossierTemplateId);
|
||||||
await firstValueFrom(fileAttributesService.loadFileAttributesConfig(dossierTemplateId));
|
await firstValueFrom(fileAttributesService.loadFileAttributesConfig(dossierTemplateId));
|
||||||
await firstValueFrom(dictionaryService.loadDictionaryDataForDossierTemplate(dossierTemplateId));
|
await firstValueFrom(dictionaryService.loadDictionaryDataForDossierTemplate(dossierTemplateId));
|
||||||
await firstValueFrom(defaultColorsService.loadForDossierTemplate(dossierTemplateId));
|
await firstValueFrom(defaultColorsService.loadForDossierTemplate(dossierTemplateId));
|
||||||
|
await firstValueFrom(rulesService.getFor(dossierTemplateId));
|
||||||
if (!isDocumine) {
|
if (!isDocumine) {
|
||||||
await firstValueFrom(watermarksService.loadForDossierTemplate(dossierTemplateId));
|
await firstValueFrom(watermarksService.loadForDossierTemplate(dossierTemplateId));
|
||||||
}
|
}
|
||||||
@ -50,6 +54,8 @@ export function templateExistsWhenEnteringDossierList(): CanActivateFn {
|
|||||||
const dictionaryService = inject(DictionaryService);
|
const dictionaryService = inject(DictionaryService);
|
||||||
const defaultColorsService = inject(DefaultColorsService);
|
const defaultColorsService = inject(DefaultColorsService);
|
||||||
const watermarksService = inject(WatermarkService);
|
const watermarksService = inject(WatermarkService);
|
||||||
|
const rulesService = inject(RulesService);
|
||||||
|
const toaster = inject(Toaster);
|
||||||
const isDocumine = getConfig().IS_DOCUMINE;
|
const isDocumine = getConfig().IS_DOCUMINE;
|
||||||
|
|
||||||
await firstValueFrom(dashboardStatsService.loadForTemplate(dossierTemplateId));
|
await firstValueFrom(dashboardStatsService.loadForTemplate(dossierTemplateId));
|
||||||
@ -64,6 +70,10 @@ export function templateExistsWhenEnteringDossierList(): CanActivateFn {
|
|||||||
await firstValueFrom(fileAttributesService.loadFileAttributesConfig(dossierTemplateId));
|
await firstValueFrom(fileAttributesService.loadFileAttributesConfig(dossierTemplateId));
|
||||||
await firstValueFrom(dictionaryService.loadDictionaryDataForDossierTemplate(dossierTemplateId));
|
await firstValueFrom(dictionaryService.loadDictionaryDataForDossierTemplate(dossierTemplateId));
|
||||||
await firstValueFrom(defaultColorsService.loadForDossierTemplate(dossierTemplateId));
|
await firstValueFrom(defaultColorsService.loadForDossierTemplate(dossierTemplateId));
|
||||||
|
const rules = await firstValueFrom(rulesService.getFor(dossierTemplateId));
|
||||||
|
if (rules.timeoutDetected) {
|
||||||
|
toaster.error(_('dossier-listing.rules.timeoutError'));
|
||||||
|
}
|
||||||
if (!isDocumine) {
|
if (!isDocumine) {
|
||||||
await firstValueFrom(watermarksService.loadForDossierTemplate(dossierTemplateId));
|
await firstValueFrom(watermarksService.loadForDossierTemplate(dossierTemplateId));
|
||||||
}
|
}
|
||||||
|
|||||||
@ -16,11 +16,7 @@ export const canForceRedaction = (annotation: AnnotationWrapper, canAddRedaction
|
|||||||
export const canAcceptRecommendation = (annotation: AnnotationWrapper) => annotation.isRecommendation && !annotation.pending;
|
export const canAcceptRecommendation = (annotation: AnnotationWrapper) => annotation.isRecommendation && !annotation.pending;
|
||||||
|
|
||||||
export const canMarkAsFalsePositive = (annotation: AnnotationWrapper, annotationEntity: Dictionary) =>
|
export const canMarkAsFalsePositive = (annotation: AnnotationWrapper, annotationEntity: Dictionary) =>
|
||||||
annotation.canBeMarkedAsFalsePositive &&
|
annotation.canBeMarkedAsFalsePositive && !annotation.hasBeenResizedLocally && annotationEntity?.hasDictionary;
|
||||||
!annotation.hasBeenResizedLocally &&
|
|
||||||
!annotation.isRemovedLocally &&
|
|
||||||
!annotation.hasBeenForcedRedaction &&
|
|
||||||
annotationEntity?.hasDictionary;
|
|
||||||
|
|
||||||
export const canRemoveOnlyHere = (annotation: AnnotationWrapper, canAddRedaction: boolean, autoAnalysisDisabled: boolean) =>
|
export const canRemoveOnlyHere = (annotation: AnnotationWrapper, canAddRedaction: boolean, autoAnalysisDisabled: boolean) =>
|
||||||
canAddRedaction &&
|
canAddRedaction &&
|
||||||
|
|||||||
@ -40,7 +40,7 @@ export class AnnotationWrapper implements IListable {
|
|||||||
typeLabel?: string;
|
typeLabel?: string;
|
||||||
color: string;
|
color: string;
|
||||||
numberOfComments = 0;
|
numberOfComments = 0;
|
||||||
firstTopLeftPoint: IPoint;
|
firstBottomLeftPoint: IPoint;
|
||||||
shortContent: string;
|
shortContent: string;
|
||||||
content: AnnotationContent;
|
content: AnnotationContent;
|
||||||
value: string;
|
value: string;
|
||||||
@ -84,7 +84,10 @@ export class AnnotationWrapper implements IListable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
get isRedactedImageHint() {
|
get isRedactedImageHint() {
|
||||||
return this.IMAGE_HINT && this.superType === SuperTypes.Redaction;
|
return (
|
||||||
|
(this.IMAGE_HINT && this.superType === SuperTypes.Redaction) ||
|
||||||
|
(this.IMAGE_HINT && this.superType === SuperTypes.ManualRedaction)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
get isSkippedImageHint() {
|
get isSkippedImageHint() {
|
||||||
@ -109,7 +112,10 @@ export class AnnotationWrapper implements IListable {
|
|||||||
|
|
||||||
get canBeMarkedAsFalsePositive() {
|
get canBeMarkedAsFalsePositive() {
|
||||||
return (
|
return (
|
||||||
(this.isRecommendation || this.superType === SuperTypes.Redaction || (this.isSkipped && this.isDictBased)) &&
|
(this.isRecommendation ||
|
||||||
|
this.superType === SuperTypes.Redaction ||
|
||||||
|
(this.isSkipped && this.isDictBased) ||
|
||||||
|
(this.isRemovedLocally && this.isDictBased)) &&
|
||||||
!this.isImage &&
|
!this.isImage &&
|
||||||
!this.imported &&
|
!this.imported &&
|
||||||
!this.pending &&
|
!this.pending &&
|
||||||
@ -193,11 +199,11 @@ export class AnnotationWrapper implements IListable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
get x() {
|
get x() {
|
||||||
return this.firstTopLeftPoint.x;
|
return this.firstBottomLeftPoint.x;
|
||||||
}
|
}
|
||||||
|
|
||||||
get y() {
|
get y() {
|
||||||
return this.firstTopLeftPoint.y;
|
return this.firstBottomLeftPoint.y;
|
||||||
}
|
}
|
||||||
|
|
||||||
get legalBasis() {
|
get legalBasis() {
|
||||||
@ -222,7 +228,7 @@ export class AnnotationWrapper implements IListable {
|
|||||||
annotationWrapper.value = 'Imported';
|
annotationWrapper.value = 'Imported';
|
||||||
annotationWrapper.color = earmark.hexColor;
|
annotationWrapper.color = earmark.hexColor;
|
||||||
annotationWrapper.positions = earmark.positions;
|
annotationWrapper.positions = earmark.positions;
|
||||||
annotationWrapper.firstTopLeftPoint = earmark.positions[0]?.topLeft;
|
annotationWrapper.firstBottomLeftPoint = earmark.positions[0]?.topLeft;
|
||||||
annotationWrapper.superTypeLabel = annotationTypesTranslations[annotationWrapper.superType];
|
annotationWrapper.superTypeLabel = annotationTypesTranslations[annotationWrapper.superType];
|
||||||
|
|
||||||
return annotationWrapper;
|
return annotationWrapper;
|
||||||
@ -243,7 +249,7 @@ export class AnnotationWrapper implements IListable {
|
|||||||
annotationWrapper.isChangeLogEntry = logEntry.state === EntryStates.REMOVED || !!changeLogType;
|
annotationWrapper.isChangeLogEntry = logEntry.state === EntryStates.REMOVED || !!changeLogType;
|
||||||
annotationWrapper.type = logEntry.type;
|
annotationWrapper.type = logEntry.type;
|
||||||
annotationWrapper.value = logEntry.value;
|
annotationWrapper.value = logEntry.value;
|
||||||
annotationWrapper.firstTopLeftPoint = { x: logEntry.positions[0].rectangle[0], y: logEntry.positions[0].rectangle[1] };
|
annotationWrapper.firstBottomLeftPoint = { x: logEntry.positions[0].rectangle[0], y: logEntry.positions[0].rectangle[1] };
|
||||||
annotationWrapper.pageNumber = logEntry.positions[0].pageNumber;
|
annotationWrapper.pageNumber = logEntry.positions[0].pageNumber;
|
||||||
annotationWrapper.positions = logEntry.positions.map(p => ({
|
annotationWrapper.positions = logEntry.positions.map(p => ({
|
||||||
page: p.pageNumber,
|
page: p.pageNumber,
|
||||||
|
|||||||
@ -8,10 +8,11 @@
|
|||||||
<div class="content-container full-height">
|
<div class="content-container full-height">
|
||||||
<div class="overlay-shadow"></div>
|
<div class="overlay-shadow"></div>
|
||||||
<div [ngClass]="!isWarningsScreen && 'dialog'">
|
<div [ngClass]="!isWarningsScreen && 'dialog'">
|
||||||
<div *ngIf="!isWarningsScreen" class="dialog-header">
|
@if (!isWarningsScreen) {
|
||||||
<div class="heading-l" [translate]="translations[path]"></div>
|
<div class="dialog-header">
|
||||||
</div>
|
<div class="heading-l" [translate]="translations[path]"></div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
<router-outlet></router-outlet>
|
<router-outlet></router-outlet>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -16,14 +16,16 @@
|
|||||||
<input formControlName="lastName" name="lastName" type="text" />
|
<input formControlName="lastName" name="lastName" type="text" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div *ngIf="devMode" class="iqser-input-group">
|
<div class="iqser-input-group">
|
||||||
<label [translate]="'top-bar.navigation-items.my-account.children.language.label'"></label>
|
<label [translate]="'top-bar.navigation-items.my-account.children.language.label'"></label>
|
||||||
<mat-form-field>
|
<mat-form-field>
|
||||||
<mat-select formControlName="language">
|
<mat-select formControlName="language">
|
||||||
<mat-select-trigger>{{ languageSelectLabel() | translate }}</mat-select-trigger>
|
<mat-select-trigger>{{ languageSelectLabel() | translate }}</mat-select-trigger>
|
||||||
<mat-option *ngFor="let language of languages" [value]="language">
|
@for (language of languages; track language) {
|
||||||
{{ translations[language] | translate }}
|
<mat-option [value]="language">
|
||||||
</mat-option>
|
{{ translations[language] | translate }}
|
||||||
|
</mat-option>
|
||||||
|
}
|
||||||
</mat-select>
|
</mat-select>
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
</div>
|
</div>
|
||||||
@ -32,11 +34,13 @@
|
|||||||
<a (click)="resetPassword()" target="_blank"> {{ 'user-profile-screen.actions.change-password' | translate }}</a>
|
<a (click)="resetPassword()" target="_blank"> {{ 'user-profile-screen.actions.change-password' | translate }}</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div *ngIf="devMode" class="iqser-input-group">
|
@if (devMode) {
|
||||||
<mat-slide-toggle color="primary" formControlName="darkTheme">
|
<div class="iqser-input-group">
|
||||||
{{ 'user-profile-screen.form.dark-theme' | translate }}
|
<mat-slide-toggle color="primary" formControlName="darkTheme">
|
||||||
</mat-slide-toggle>
|
{{ 'user-profile-screen.form.dark-theme' | translate }}
|
||||||
</div>
|
</mat-slide-toggle>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@ -23,7 +23,7 @@
|
|||||||
|
|
||||||
<div class="mt-44">
|
<div class="mt-44">
|
||||||
<redaction-donut-chart
|
<redaction-donut-chart
|
||||||
[config]="chartConfig"
|
[config]="chartConfig()"
|
||||||
[radius]="63"
|
[radius]="63"
|
||||||
[strokeWidth]="15"
|
[strokeWidth]="15"
|
||||||
[subtitles]="['user-stats.chart.users' | translate]"
|
[subtitles]="['user-stats.chart.users' | translate]"
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { Component, EventEmitter, Input, Output } from '@angular/core';
|
import { Component, EventEmitter, input, Input, output, Output } from '@angular/core';
|
||||||
import { DonutChartConfig } from '@red/domain';
|
import { DonutChartConfig } from '@red/domain';
|
||||||
import { CircleButtonComponent } from '@iqser/common-ui';
|
import { CircleButtonComponent } from '@iqser/common-ui';
|
||||||
import { TranslateModule } from '@ngx-translate/core';
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
@ -12,6 +12,6 @@ import { DonutChartComponent } from '@shared/components/donut-chart/donut-chart.
|
|||||||
imports: [CircleButtonComponent, TranslateModule, DonutChartComponent],
|
imports: [CircleButtonComponent, TranslateModule, DonutChartComponent],
|
||||||
})
|
})
|
||||||
export class UsersStatsComponent {
|
export class UsersStatsComponent {
|
||||||
@Output() toggleCollapse = new EventEmitter();
|
readonly chartConfig = input.required<DonutChartConfig[]>();
|
||||||
@Input() chartConfig: DonutChartConfig[];
|
readonly toggleCollapse = output();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -32,7 +32,7 @@
|
|||||||
[formControlName]="role"
|
[formControlName]="role"
|
||||||
color="primary"
|
color="primary"
|
||||||
>
|
>
|
||||||
{{ translations[role] | translate }}
|
{{ translations[role] | translate: { count: 1 } }}
|
||||||
</mat-checkbox>
|
</mat-checkbox>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -3,10 +3,8 @@ import { ReactiveFormsModule, UntypedFormBuilder, UntypedFormGroup, Validators }
|
|||||||
import { AdminDialogService } from '../../../services/admin-dialog.service';
|
import { AdminDialogService } from '../../../services/admin-dialog.service';
|
||||||
import { BaseFormComponent, IconButtonComponent, LoadingService, Toaster } from '@iqser/common-ui';
|
import { BaseFormComponent, IconButtonComponent, LoadingService, Toaster } from '@iqser/common-ui';
|
||||||
import { rolesTranslations } from '@translations/roles-translations';
|
import { rolesTranslations } from '@translations/roles-translations';
|
||||||
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
|
||||||
import { User } from '@red/domain';
|
import { User } from '@red/domain';
|
||||||
import { UserService } from '@users/user.service';
|
import { UserService } from '@users/user.service';
|
||||||
import { HttpStatusCode } from '@angular/common/http';
|
|
||||||
import { firstValueFrom } from 'rxjs';
|
import { firstValueFrom } from 'rxjs';
|
||||||
import { IProfileUpdateRequest } from '@iqser/common-ui/lib/users';
|
import { IProfileUpdateRequest } from '@iqser/common-ui/lib/users';
|
||||||
import { TranslateModule } from '@ngx-translate/core';
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
@ -101,11 +99,7 @@ export class UserDetailsComponent extends BaseFormComponent implements OnInit {
|
|||||||
this.closeDialog.emit(true);
|
this.closeDialog.emit(true);
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
if (error.status === HttpStatusCode.Conflict) {
|
this._toaster.error(null, { error });
|
||||||
this._toaster.error(_('add-edit-user.error.email-already-used'));
|
|
||||||
} else {
|
|
||||||
this._toaster.error(_('add-edit-user.error.generic'));
|
|
||||||
}
|
|
||||||
this._loadingService.stop();
|
this._loadingService.stop();
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@ -17,7 +17,7 @@ import { RouterHistoryService } from '@services/router-history.service';
|
|||||||
import { auditCategoriesTranslations } from '@translations/audit-categories-translations';
|
import { auditCategoriesTranslations } from '@translations/audit-categories-translations';
|
||||||
import { Roles } from '@users/roles';
|
import { Roles } from '@users/roles';
|
||||||
import { applyIntervalConstraints } from '@utils/date-inputs-utils';
|
import { applyIntervalConstraints } from '@utils/date-inputs-utils';
|
||||||
import { Dayjs } from 'dayjs';
|
import dayjs, { Dayjs } from 'dayjs';
|
||||||
import { firstValueFrom } from 'rxjs';
|
import { firstValueFrom } from 'rxjs';
|
||||||
import { AdminDialogService } from '../../services/admin-dialog.service';
|
import { AdminDialogService } from '../../services/admin-dialog.service';
|
||||||
import { AuditService } from '../../services/audit.service';
|
import { AuditService } from '../../services/audit.service';
|
||||||
@ -139,16 +139,9 @@ export class AuditScreenComponent extends ListingComponent<Audit> implements OnI
|
|||||||
const promises = [];
|
const promises = [];
|
||||||
const category = this.form.get('category').value;
|
const category = this.form.get('category').value;
|
||||||
const userId = this.form.get('userId').value;
|
const userId = this.form.get('userId').value;
|
||||||
const from = this.form.get('from').value;
|
const from = this.form.get('from').value ? dayjs(this.form.get('from').value).startOf('day').toISOString() : null;
|
||||||
let to = this.form.get('to').value;
|
const to = this.form.get('to').value ? dayjs(this.form.get('to').value).endOf('day').toISOString() : null;
|
||||||
if (to) {
|
|
||||||
const hoursLeft = new Date(to).getHours();
|
|
||||||
const minutesLeft = new Date(to).getMinutes();
|
|
||||||
to = to
|
|
||||||
.clone()
|
|
||||||
.add(24 - hoursLeft - 1, 'h')
|
|
||||||
.add(60 - minutesLeft - 1);
|
|
||||||
}
|
|
||||||
const logsRequestBody: IAuditSearchRequest = {
|
const logsRequestBody: IAuditSearchRequest = {
|
||||||
pageSize: PAGE_SIZE,
|
pageSize: PAGE_SIZE,
|
||||||
page: page,
|
page: page,
|
||||||
|
|||||||
@ -42,6 +42,15 @@
|
|||||||
type="text"
|
type="text"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="iqser-input-group required w-150">
|
||||||
|
<label translate="add-edit-component-mapping.form.quote-char"></label>
|
||||||
|
<input
|
||||||
|
[placeholder]="'add-edit-component-mapping.form.quote-char-placeholder' | translate"
|
||||||
|
formControlName="quoteChar"
|
||||||
|
name="quoteChar"
|
||||||
|
type="text"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="iqser-input-group required w-150">
|
<div class="iqser-input-group required w-150">
|
||||||
<label translate="add-edit-component-mapping.form.encoding-type"></label>
|
<label translate="add-edit-component-mapping.form.encoding-type"></label>
|
||||||
|
|||||||
@ -17,12 +17,14 @@ interface DialogData {
|
|||||||
dossierTemplateId: string;
|
dossierTemplateId: string;
|
||||||
mapping: IComponentMapping;
|
mapping: IComponentMapping;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface DialogResult {
|
interface DialogResult {
|
||||||
id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
file: Blob;
|
file: Blob;
|
||||||
encoding: string;
|
encoding: string;
|
||||||
delimiter: string;
|
delimiter: string;
|
||||||
|
quoteChar: string;
|
||||||
fileName?: string;
|
fileName?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -72,14 +74,14 @@ export class AddEditComponentMappingDialogComponent
|
|||||||
const file = new Blob([fileContent.body as Blob], { type: 'text/csv' });
|
const file = new Blob([fileContent.body as Blob], { type: 'text/csv' });
|
||||||
this.form.get('file').setValue(file);
|
this.form.get('file').setValue(file);
|
||||||
this.initialFormValue = this.form.getRawValue();
|
this.initialFormValue = this.form.getRawValue();
|
||||||
this.#disableEncodingAndDelimiter();
|
this.#disableEncodingAndQuoteCharAndDelimiter();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
changeFile(file: File) {
|
changeFile(file: File) {
|
||||||
this.form.get('file').setValue(file);
|
this.form.get('file').setValue(file);
|
||||||
this.form.get('fileName').setValue(file?.name);
|
this.form.get('fileName').setValue(file?.name);
|
||||||
this.#enableEncodingAndDelimiter();
|
this.#enableEncodingAndQuoteCharAndDelimiter();
|
||||||
}
|
}
|
||||||
|
|
||||||
save() {
|
save() {
|
||||||
@ -93,16 +95,19 @@ export class AddEditComponentMappingDialogComponent
|
|||||||
fileName: [this.data?.mapping?.fileName, Validators.required],
|
fileName: [this.data?.mapping?.fileName, Validators.required],
|
||||||
encoding: this.encodingTypeOptions.find(e => e === this.data?.mapping?.encoding) ?? this.encodingTypeOptions[0],
|
encoding: this.encodingTypeOptions.find(e => e === this.data?.mapping?.encoding) ?? this.encodingTypeOptions[0],
|
||||||
delimiter: [this.data?.mapping?.delimiter ?? ',', Validators.required],
|
delimiter: [this.data?.mapping?.delimiter ?? ',', Validators.required],
|
||||||
|
quoteChar: [this.data?.mapping?.quoteChar ?? '"', Validators.required],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
#disableEncodingAndDelimiter() {
|
#disableEncodingAndQuoteCharAndDelimiter() {
|
||||||
this.form.get('encoding').disable();
|
this.form.get('encoding').disable();
|
||||||
this.form.get('delimiter').disable();
|
this.form.get('delimiter').disable();
|
||||||
|
this.form.get('quoteChar').disable();
|
||||||
}
|
}
|
||||||
|
|
||||||
#enableEncodingAndDelimiter() {
|
#enableEncodingAndQuoteCharAndDelimiter() {
|
||||||
this.form.get('encoding').enable();
|
this.form.get('encoding').enable();
|
||||||
this.form.get('delimiter').enable();
|
this.form.get('delimiter').enable();
|
||||||
|
this.form.get('quoteChar').enable();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -99,8 +99,8 @@ export default class ComponentMappingsScreenComponent extends ListingComponent<C
|
|||||||
const result = await dialog.result();
|
const result = await dialog.result();
|
||||||
if (result) {
|
if (result) {
|
||||||
this._loadingService.start();
|
this._loadingService.start();
|
||||||
const { id, name, encoding, delimiter, fileName } = result;
|
const { id, name, encoding, delimiter, fileName, quoteChar } = result;
|
||||||
const newMapping = { id, name, encoding, delimiter, fileName };
|
const newMapping = { id, name, encoding, delimiter, fileName, quoteChar };
|
||||||
await firstValueFrom(
|
await firstValueFrom(
|
||||||
this._componentMappingService.createUpdateComponentMapping(this.#dossierTemplateId, newMapping, result.file),
|
this._componentMappingService.createUpdateComponentMapping(this.#dossierTemplateId, newMapping, result.file),
|
||||||
);
|
);
|
||||||
|
|||||||
@ -87,6 +87,7 @@
|
|||||||
[routerLink]="dict.routerLink"
|
[routerLink]="dict.routerLink"
|
||||||
[tooltip]="'entities-listing.action.edit' | translate"
|
[tooltip]="'entities-listing.action.edit' | translate"
|
||||||
icon="iqser:edit"
|
icon="iqser:edit"
|
||||||
|
iqserStopPropagation
|
||||||
></iqser-circle-button>
|
></iqser-circle-button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -11,6 +11,7 @@ import {
|
|||||||
ListingComponent,
|
ListingComponent,
|
||||||
listingProvidersFactory,
|
listingProvidersFactory,
|
||||||
LoadingService,
|
LoadingService,
|
||||||
|
StopPropagationDirective,
|
||||||
TableColumnConfig,
|
TableColumnConfig,
|
||||||
} from '@iqser/common-ui';
|
} from '@iqser/common-ui';
|
||||||
import { getParam } from '@iqser/common-ui/lib/utils';
|
import { getParam } from '@iqser/common-ui/lib/utils';
|
||||||
@ -41,6 +42,7 @@ import { AdminDialogService } from '../../services/admin-dialog.service';
|
|||||||
AnnotationIconComponent,
|
AnnotationIconComponent,
|
||||||
AsyncPipe,
|
AsyncPipe,
|
||||||
RouterLink,
|
RouterLink,
|
||||||
|
StopPropagationDirective,
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
export class EntitiesListingScreenComponent extends ListingComponent<Dictionary> implements OnInit {
|
export class EntitiesListingScreenComponent extends ListingComponent<Dictionary> implements OnInit {
|
||||||
|
|||||||
@ -1,6 +1,5 @@
|
|||||||
<div class="dialog-header">
|
<div class="dialog-header">
|
||||||
<div class="heading-l" translate="general-config-screen.general.title"></div>
|
<div class="heading-l" translate="general-config-screen.general.title"></div>
|
||||||
<div translate="general-config-screen.general.subtitle"></div>
|
|
||||||
</div>
|
</div>
|
||||||
<form (submit)="save()" *ngIf="form" [formGroup]="form">
|
<form (submit)="save()" *ngIf="form" [formGroup]="form">
|
||||||
<div class="dialog-content">
|
<div class="dialog-content">
|
||||||
|
|||||||
@ -34,6 +34,20 @@
|
|||||||
<span [innerHTML]="'dossier-template-info-screen.created-on' | translate: { date: createdOn }"></span>
|
<span [innerHTML]="'dossier-template-info-screen.created-on' | translate: { date: createdOn }"></span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div *ngIf="areRulesLocked()">
|
||||||
|
<mat-icon
|
||||||
|
(click)="resetRules()"
|
||||||
|
[matTooltip]="
|
||||||
|
currentUser.isAdmin
|
||||||
|
? ('dossier-template-info-screen.rules-reset.tooltip' | translate)
|
||||||
|
: ('dossier-template-info-screen.rules-reset.disabled-action' | translate)
|
||||||
|
"
|
||||||
|
[class.action-icon]="currentUser.isAdmin"
|
||||||
|
svgIcon="iqser:alert-circle"
|
||||||
|
></mat-icon>
|
||||||
|
<span class="error">{{ 'dossier-template-info-screen.rules-reset.label' | translate }}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<mat-icon svgIcon="red:entries"></mat-icon>
|
<mat-icon svgIcon="red:entries"></mat-icon>
|
||||||
{{ 'dossier-template-info-screen.entries' | translate: { count: ctx.stats.numberOfEntries } }}
|
{{ 'dossier-template-info-screen.entries' | translate: { count: ctx.stats.numberOfEntries } }}
|
||||||
|
|||||||
@ -18,3 +18,11 @@
|
|||||||
padding-right: 24px;
|
padding-right: 24px;
|
||||||
margin-right: 24px;
|
margin-right: 24px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.error {
|
||||||
|
color: var(--iqser-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-icon {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { Component, Input, OnInit } from '@angular/core';
|
import { Component, computed, Input, OnInit } from '@angular/core';
|
||||||
import { ContextComponent } from '@iqser/common-ui/lib/utils';
|
import { ContextComponent } from '@iqser/common-ui/lib/utils';
|
||||||
import { type DossierTemplate, type DossierTemplateStats } from '@red/domain';
|
import { type DossierTemplate, type DossierTemplateStats } from '@red/domain';
|
||||||
import { DossierTemplatesService } from '@services/dossier-templates/dossier-templates.service';
|
import { DossierTemplatesService } from '@services/dossier-templates/dossier-templates.service';
|
||||||
@ -9,6 +9,12 @@ import { MatIcon } from '@angular/material/icon';
|
|||||||
import { TranslateModule } from '@ngx-translate/core';
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
import { InitialsAvatarComponent } from '@common-ui/users';
|
import { InitialsAvatarComponent } from '@common-ui/users';
|
||||||
import { DatePipe } from '@shared/pipes/date.pipe';
|
import { DatePipe } from '@shared/pipes/date.pipe';
|
||||||
|
import { RulesService } from '../../../services/rules.service';
|
||||||
|
import { Toaster } from '@iqser/common-ui';
|
||||||
|
import { MatTooltip } from '@angular/material/tooltip';
|
||||||
|
import { firstValueFrom } from 'rxjs';
|
||||||
|
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
||||||
|
import { getCurrentUser } from '@users/user.service';
|
||||||
|
|
||||||
interface Context {
|
interface Context {
|
||||||
readonly dossierTemplate: DossierTemplate;
|
readonly dossierTemplate: DossierTemplate;
|
||||||
@ -20,16 +26,21 @@ interface Context {
|
|||||||
templateUrl: './dossier-template-details.component.html',
|
templateUrl: './dossier-template-details.component.html',
|
||||||
styleUrls: ['./dossier-template-details.component.scss'],
|
styleUrls: ['./dossier-template-details.component.scss'],
|
||||||
standalone: true,
|
standalone: true,
|
||||||
imports: [NgIf, AsyncPipe, MatIcon, TranslateModule, DatePipe, InitialsAvatarComponent],
|
imports: [NgIf, AsyncPipe, MatIcon, TranslateModule, DatePipe, InitialsAvatarComponent, MatTooltip],
|
||||||
})
|
})
|
||||||
export class DossierTemplateDetailsComponent extends ContextComponent<Context> implements OnInit {
|
export class DossierTemplateDetailsComponent extends ContextComponent<Context> implements OnInit {
|
||||||
readonly translations = dossierTemplateStatusTranslations;
|
readonly translations = dossierTemplateStatusTranslations;
|
||||||
|
|
||||||
@Input({ required: true }) dossierTemplateId: string;
|
@Input({ required: true }) dossierTemplateId: string;
|
||||||
|
readonly areRulesLocked = computed(() => {
|
||||||
|
return this._rulesService.currentTemplateRules().timeoutDetected;
|
||||||
|
});
|
||||||
|
readonly currentUser = getCurrentUser();
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private readonly _dossierTemplatesService: DossierTemplatesService,
|
private readonly _dossierTemplatesService: DossierTemplatesService,
|
||||||
private readonly _dossierTemplateStatsService: DossierTemplateStatsService,
|
private readonly _dossierTemplateStatsService: DossierTemplateStatsService,
|
||||||
|
private readonly _rulesService: RulesService,
|
||||||
|
private readonly _toaster: Toaster,
|
||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
}
|
}
|
||||||
@ -40,4 +51,15 @@ export class DossierTemplateDetailsComponent extends ContextComponent<Context> i
|
|||||||
stats: this._dossierTemplateStatsService.watch$(this.dossierTemplateId),
|
stats: this._dossierTemplateStatsService.watch$(this.dossierTemplateId),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async resetRules() {
|
||||||
|
if (!this.currentUser.isAdmin) return;
|
||||||
|
try {
|
||||||
|
await firstValueFrom(this._rulesService.reset(this.dossierTemplateId));
|
||||||
|
this._toaster.success(_('dossier-template-info-screen.rules-reset.success'));
|
||||||
|
await firstValueFrom(this._rulesService.getFor(this.dossierTemplateId));
|
||||||
|
} catch (error) {
|
||||||
|
this._toaster.rawError(error.error.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -17,6 +17,15 @@
|
|||||||
type="text"
|
type="text"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="iqser-input-group">
|
||||||
|
<label translate="add-edit-entity.form.technical-name"></label>
|
||||||
|
<div class="technical-name">{{ this.technicalName() || '-' }}</div>
|
||||||
|
<span
|
||||||
|
[translateParams]="{ type: data.justification ? 'edit' : 'create' }"
|
||||||
|
[translate]="'add-edit-entity.form.technical-name-hint'"
|
||||||
|
class="hint"
|
||||||
|
></span>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="iqser-input-group required w-400">
|
<div class="iqser-input-group required w-400">
|
||||||
<label translate="add-edit-justification.form.reason"></label>
|
<label translate="add-edit-justification.form.reason"></label>
|
||||||
|
|||||||
@ -1,11 +1,13 @@
|
|||||||
import { ChangeDetectionStrategy, Component, Inject } from '@angular/core';
|
import { ChangeDetectionStrategy, Component, computed, Inject, untracked } from '@angular/core';
|
||||||
import { ReactiveFormsModule, UntypedFormGroup, Validators } from '@angular/forms';
|
import { ReactiveFormsModule, UntypedFormGroup, Validators } from '@angular/forms';
|
||||||
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
|
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
|
||||||
import { Justification } from '@red/domain';
|
import { Justification } from '@red/domain';
|
||||||
import { JustificationsService } from '@services/entity-services/justifications.service';
|
import { JustificationsService } from '@services/entity-services/justifications.service';
|
||||||
import { BaseDialogComponent, CircleButtonComponent, IconButtonComponent } from '@iqser/common-ui';
|
import { BaseDialogComponent, CircleButtonComponent, HasScrollbarDirective, IconButtonComponent } from '@iqser/common-ui';
|
||||||
import { firstValueFrom } from 'rxjs';
|
import { firstValueFrom } from 'rxjs';
|
||||||
import { TranslateModule } from '@ngx-translate/core';
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
|
import { formControlToSignal } from '@utils/functions';
|
||||||
|
import { toSignal } from '@angular/core/rxjs-interop';
|
||||||
|
|
||||||
interface DialogData {
|
interface DialogData {
|
||||||
justification?: Justification;
|
justification?: Justification;
|
||||||
@ -16,9 +18,29 @@ interface DialogData {
|
|||||||
templateUrl: './add-edit-justification-dialog.component.html',
|
templateUrl: './add-edit-justification-dialog.component.html',
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
standalone: true,
|
standalone: true,
|
||||||
imports: [TranslateModule, ReactiveFormsModule, IconButtonComponent, CircleButtonComponent],
|
imports: [TranslateModule, ReactiveFormsModule, IconButtonComponent, CircleButtonComponent, HasScrollbarDirective],
|
||||||
})
|
})
|
||||||
export class AddEditJustificationDialogComponent extends BaseDialogComponent {
|
export class AddEditJustificationDialogComponent extends BaseDialogComponent {
|
||||||
|
readonly form = this.#getForm();
|
||||||
|
readonly name = formControlToSignal(this.form.controls['name']);
|
||||||
|
readonly allJustifications = toSignal(this._justificationService.all$);
|
||||||
|
readonly technicalName = computed(() => {
|
||||||
|
if (this.data.justification) {
|
||||||
|
return this.data.justification.technicalName;
|
||||||
|
}
|
||||||
|
if (!this.name()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
let currentTechnicalName = Justification.toTechnicalName(this.name());
|
||||||
|
const existingTechnicalNames = untracked(this.allJustifications).map(justification => justification.technicalName);
|
||||||
|
let suffix = 1;
|
||||||
|
while (existingTechnicalNames.includes(currentTechnicalName)) {
|
||||||
|
currentTechnicalName =
|
||||||
|
currentTechnicalName === '_' ? `${currentTechnicalName}${suffix++}` : [currentTechnicalName, suffix++].join('_');
|
||||||
|
}
|
||||||
|
return currentTechnicalName;
|
||||||
|
});
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private readonly _justificationService: JustificationsService,
|
private readonly _justificationService: JustificationsService,
|
||||||
protected readonly _dialogRef: MatDialogRef<AddEditJustificationDialogComponent>,
|
protected readonly _dialogRef: MatDialogRef<AddEditJustificationDialogComponent>,
|
||||||
@ -26,7 +48,6 @@ export class AddEditJustificationDialogComponent extends BaseDialogComponent {
|
|||||||
) {
|
) {
|
||||||
super(_dialogRef, !!data.justification);
|
super(_dialogRef, !!data.justification);
|
||||||
|
|
||||||
this.form = this._getForm();
|
|
||||||
this.initialFormValue = this.form.getRawValue();
|
this.initialFormValue = this.form.getRawValue();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -34,7 +55,8 @@ export class AddEditJustificationDialogComponent extends BaseDialogComponent {
|
|||||||
const dossierTemplateId = this.data.dossierTemplateId;
|
const dossierTemplateId = this.data.dossierTemplateId;
|
||||||
this._loadingService.start();
|
this._loadingService.start();
|
||||||
try {
|
try {
|
||||||
await firstValueFrom(this._justificationService.createOrUpdate(this.form.getRawValue() as Justification, dossierTemplateId));
|
const formValue = { ...this.form.getRawValue(), technicalName: this.technicalName() };
|
||||||
|
await firstValueFrom(this._justificationService.createOrUpdate(formValue as Justification, dossierTemplateId));
|
||||||
await firstValueFrom(this._justificationService.loadAll(dossierTemplateId));
|
await firstValueFrom(this._justificationService.loadAll(dossierTemplateId));
|
||||||
this._dialogRef.close(true);
|
this._dialogRef.close(true);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@ -43,11 +65,12 @@ export class AddEditJustificationDialogComponent extends BaseDialogComponent {
|
|||||||
this._loadingService.stop();
|
this._loadingService.stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
private _getForm(): UntypedFormGroup {
|
#getForm(): UntypedFormGroup {
|
||||||
return this._formBuilder.group({
|
return this._formBuilder.group({
|
||||||
name: [{ value: this.data.justification?.name, disabled: !!this.data.justification }, Validators.required],
|
name: [{ value: this.data.justification?.name, disabled: !!this.data.justification }, Validators.required],
|
||||||
reason: [this.data.justification?.reason, Validators.required],
|
reason: [this.data.justification?.reason, Validators.required],
|
||||||
description: [this.data.justification?.description, Validators.required],
|
description: [this.data.justification?.description, Validators.required],
|
||||||
|
technicalName: [this.data.justification?.technicalName ?? null],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -126,7 +126,7 @@ export class UserListingScreenComponent extends ListingComponent<User> implement
|
|||||||
|
|
||||||
getDisplayRoles(user: User) {
|
getDisplayRoles(user: User) {
|
||||||
const oldRedRoles = user.roles.filter(role => role.startsWith('RED_'));
|
const oldRedRoles = user.roles.filter(role => role.startsWith('RED_'));
|
||||||
const translatedRoles = oldRedRoles.map(role => this._translateService.instant(this.translations[role]));
|
const translatedRoles = oldRedRoles.map(role => this._translateService.instant(this.translations[role], { count: 1 }));
|
||||||
return translatedRoles.join(', ') || this._translateService.instant(this.translations['NO_ROLE']);
|
return translatedRoles.join(', ') || this._translateService.instant(this.translations['NO_ROLE']);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -246,14 +246,15 @@ export class WatermarkScreenComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async #loadViewer() {
|
async #loadViewer() {
|
||||||
this.instance = await WebViewer(
|
this.instance = await WebViewer.Iframe(
|
||||||
{
|
{
|
||||||
licenseKey: this._licenseService.activeLicenseKey,
|
licenseKey: this._licenseService.activeLicenseKey,
|
||||||
path: this.#convertPath('/assets/wv-resources/10.10.1'),
|
path: this.#convertPath('/assets/wv-resources/11.0.0'),
|
||||||
css: this.#convertPath('/assets/pdftron/stylesheet.css'),
|
css: this.#convertPath('/assets/pdftron/stylesheet.css'),
|
||||||
fullAPI: true,
|
fullAPI: true,
|
||||||
isReadOnly: true,
|
isReadOnly: true,
|
||||||
backendType: 'ems',
|
backendType: 'ems',
|
||||||
|
ui: 'legacy',
|
||||||
},
|
},
|
||||||
// use nativeElement instead of document.getElementById('viwer')
|
// use nativeElement instead of document.getElementById('viwer')
|
||||||
// because WebViewer works better with this approach
|
// because WebViewer works better with this approach
|
||||||
@ -269,7 +270,7 @@ export class WatermarkScreenComponent implements OnInit {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (environment.production) {
|
if (environment.production) {
|
||||||
this.instance.Core.setCustomFontURL('https://' + window.location.host + this.#convertPath('/assets/pdftron'));
|
this.instance.Core.setCustomFontURL(window.location.origin + this.#convertPath('/assets/pdftron/fonts'));
|
||||||
}
|
}
|
||||||
|
|
||||||
this.#disableElements();
|
this.#disableElements();
|
||||||
|
|||||||
@ -1,9 +1,25 @@
|
|||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { GenericService } from '@iqser/common-ui';
|
import { EntitiesService, QueryParam } from '@iqser/common-ui';
|
||||||
import { IRules } from '@red/domain';
|
import { IRules, Rules } from '@red/domain';
|
||||||
|
import { map, Observable, tap } from 'rxjs';
|
||||||
|
import { List } from '@common-ui/utils';
|
||||||
|
import { toSignal } from '@angular/core/rxjs-interop';
|
||||||
|
import { distinctUntilChanged, filter } from 'rxjs/operators';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable({ providedIn: 'root' })
|
||||||
export class RulesService extends GenericService<IRules> {
|
export class RulesService extends EntitiesService<IRules, Rules> {
|
||||||
|
readonly currentTemplateRules = toSignal(
|
||||||
|
this.all$.pipe(
|
||||||
|
filter(all => !!all.length),
|
||||||
|
map(rules => rules[0]),
|
||||||
|
distinctUntilChanged(
|
||||||
|
(prev, curr) =>
|
||||||
|
prev.rules === curr.rules &&
|
||||||
|
prev.timeoutDetected === curr.timeoutDetected &&
|
||||||
|
prev.dossierTemplateId === curr.dossierTemplateId,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
protected readonly _defaultModelPath = 'rules';
|
protected readonly _defaultModelPath = 'rules';
|
||||||
|
|
||||||
download(dossierTemplateId: string, ruleFileType: IRules['ruleFileType'] = 'ENTITY') {
|
download(dossierTemplateId: string, ruleFileType: IRules['ruleFileType'] = 'ENTITY') {
|
||||||
@ -13,4 +29,12 @@ export class RulesService extends GenericService<IRules> {
|
|||||||
uploadRules(body: IRules) {
|
uploadRules(body: IRules) {
|
||||||
return this._post<unknown>({ ...body, ruleFileType: body.ruleFileType ?? 'ENTITY' });
|
return this._post<unknown>({ ...body, ruleFileType: body.ruleFileType ?? 'ENTITY' });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getFor<R = IRules>(entityId: string, queryParams?: List<QueryParam>): Observable<R> {
|
||||||
|
return super.getFor<R>(entityId, queryParams).pipe(tap(rules => this.setEntities([rules as Rules])));
|
||||||
|
}
|
||||||
|
|
||||||
|
reset(dossierTemplateId: string, ruleFileType: IRules['ruleFileType'] = 'ENTITY') {
|
||||||
|
return this._put(null, `${this._defaultModelPath}/${dossierTemplateId}/${ruleFileType}/reset`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,8 +1,8 @@
|
|||||||
<ng-container (longPress)="forceReanalysisAction($event)" *ngIf="selectedFiles.length" redactionLongPress>
|
<ng-container (longPress)="forceReanalysisAction($event)" *ngIf="selectedFiles().length" redactionLongPress>
|
||||||
<redaction-expandable-file-actions
|
<redaction-expandable-file-actions
|
||||||
[actions]="buttons"
|
[actions]="buttons()"
|
||||||
[buttonType]="buttonType"
|
[buttonType]="buttonType()"
|
||||||
[maxWidth]="maxWidth"
|
[maxWidth]="maxWidth()"
|
||||||
[tooltipPosition]="IqserTooltipPositions.above"
|
[tooltipPosition]="IqserTooltipPositions.above"
|
||||||
>
|
>
|
||||||
</redaction-expandable-file-actions>
|
</redaction-expandable-file-actions>
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import { Component, Input, OnChanges } from '@angular/core';
|
import { Component, computed, input, signal } from '@angular/core';
|
||||||
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
||||||
import { CircleButtonType, CircleButtonTypes } from '@iqser/common-ui';
|
import { CircleButtonType, CircleButtonTypes, Toaster } from '@iqser/common-ui';
|
||||||
import { Action, ActionTypes, Dossier, File, ProcessingFileStatuses } from '@red/domain';
|
import { Action, ActionTypes, Dossier, File, ProcessingFileStatuses } from '@red/domain';
|
||||||
import { PermissionsService } from '@services/permissions.service';
|
import { PermissionsService } from '@services/permissions.service';
|
||||||
import { LongPressDirective, LongPressEvent } from '@shared/directives/long-press.directive';
|
import { LongPressDirective, LongPressEvent } from '@shared/directives/long-press.directive';
|
||||||
@ -9,6 +9,8 @@ import { BulkActionsService } from '../../services/bulk-actions.service';
|
|||||||
import { ExpandableFileActionsComponent } from '@shared/components/expandable-file-actions/expandable-file-actions.component';
|
import { ExpandableFileActionsComponent } from '@shared/components/expandable-file-actions/expandable-file-actions.component';
|
||||||
import { IqserTooltipPositions } from '@common-ui/utils';
|
import { IqserTooltipPositions } from '@common-ui/utils';
|
||||||
import { NgIf } from '@angular/common';
|
import { NgIf } from '@angular/common';
|
||||||
|
import { RulesService } from '../../../admin/services/rules.service';
|
||||||
|
import { firstValueFrom } from 'rxjs';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'redaction-dossier-overview-bulk-actions [dossier] [selectedFiles]',
|
selector: 'redaction-dossier-overview-bulk-actions [dossier] [selectedFiles]',
|
||||||
@ -17,218 +19,211 @@ import { NgIf } from '@angular/common';
|
|||||||
standalone: true,
|
standalone: true,
|
||||||
imports: [LongPressDirective, ExpandableFileActionsComponent, NgIf],
|
imports: [LongPressDirective, ExpandableFileActionsComponent, NgIf],
|
||||||
})
|
})
|
||||||
export class DossierOverviewBulkActionsComponent implements OnChanges {
|
export class DossierOverviewBulkActionsComponent {
|
||||||
#analysisForced: boolean;
|
readonly dossier = input<Dossier>();
|
||||||
#canAssignToSelf: boolean;
|
readonly selectedFiles = input<File[]>();
|
||||||
#canAssign: boolean;
|
readonly buttonType = input<CircleButtonType>(CircleButtonTypes.default);
|
||||||
#canDelete: boolean;
|
readonly maxWidth = input<number>();
|
||||||
#canReanalyse: boolean;
|
readonly buttons = computed(() => this.#buttons);
|
||||||
#canDisableAutoAnalysis: boolean;
|
|
||||||
#canEnableAutoAnalysis: boolean;
|
|
||||||
#canOcr: boolean;
|
|
||||||
#canSetToNew: boolean;
|
|
||||||
#canSetToUnderReview: boolean;
|
|
||||||
#canSetToUnderApproval: boolean;
|
|
||||||
#isReadyForApproval: boolean;
|
|
||||||
#canApprove: boolean;
|
|
||||||
#canUndoApproval: boolean;
|
|
||||||
#canToggleAnalysis: boolean;
|
|
||||||
#assignTooltip: string;
|
|
||||||
#toggleAnalysisTooltip: string;
|
|
||||||
#allFilesAreExcluded: boolean;
|
|
||||||
#canMoveToSameState: boolean;
|
|
||||||
@Input() dossier: Dossier;
|
|
||||||
@Input() selectedFiles: File[];
|
|
||||||
@Input() buttonType: CircleButtonType = CircleButtonTypes.default;
|
|
||||||
@Input() maxWidth: number;
|
|
||||||
buttons: Action[];
|
|
||||||
readonly IqserTooltipPositions = IqserTooltipPositions;
|
readonly IqserTooltipPositions = IqserTooltipPositions;
|
||||||
|
readonly #areFilesInErrorState = computed(() => this.selectedFiles().some(file => file.isError));
|
||||||
|
readonly #areRulesLocked = computed(() => this._rulesService.currentTemplateRules().timeoutDetected);
|
||||||
|
readonly #allFilesAreUnderReviewOrUnassigned = computed(() =>
|
||||||
|
this.selectedFiles().reduce((acc, file) => acc && (file.isUnderReview || file.isNew), true),
|
||||||
|
);
|
||||||
|
readonly #allFilesAreUnderApproval = computed(() => this.selectedFiles().reduce((acc, file) => acc && file.isUnderApproval, true));
|
||||||
|
readonly #allFilesAreExcluded = computed(() => this.selectedFiles().reduce((acc, file) => acc && file.excluded, true));
|
||||||
|
readonly #allFilesAreApproved = computed(() => this.selectedFiles().reduce((acc, file) => acc && file.isApproved, true));
|
||||||
|
readonly #canMoveToSameState = computed(
|
||||||
|
() => this.#allFilesAreUnderReviewOrUnassigned() || this.#allFilesAreUnderApproval() || this.#allFilesAreApproved(),
|
||||||
|
);
|
||||||
|
readonly #canAssign = computed(
|
||||||
|
() =>
|
||||||
|
this.#canMoveToSameState() &&
|
||||||
|
(this._permissionsService.canAssignUser(this.selectedFiles(), this.dossier()) ||
|
||||||
|
this._permissionsService.canUnassignUser(this.selectedFiles(), this.dossier())),
|
||||||
|
);
|
||||||
|
readonly #canAssignToSelf = computed(
|
||||||
|
() => this.#canMoveToSameState() && this._permissionsService.canAssignToSelf(this.selectedFiles(), this.dossier()),
|
||||||
|
);
|
||||||
|
readonly #canDelete = computed(() => this._permissionsService.canSoftDeleteFile(this.selectedFiles(), this.dossier()));
|
||||||
|
readonly #canReanalyse = computed(() => this._permissionsService.canReanalyseFile(this.selectedFiles(), this.dossier()));
|
||||||
|
readonly #canDisableAutoAnalysis = computed(
|
||||||
|
() => this._permissionsService.canDisableAutoAnalysis(this.selectedFiles(), this.dossier()) && !this.#areFilesInErrorState(),
|
||||||
|
);
|
||||||
|
readonly #canEnableAutoAnalysis = computed(
|
||||||
|
() => this._permissionsService.canEnableAutoAnalysis(this.selectedFiles(), this.dossier()) && !this.#areFilesInErrorState(),
|
||||||
|
);
|
||||||
|
readonly #canToggleAnalysis = computed(() => this._permissionsService.canToggleAnalysis(this.selectedFiles(), this.dossier()));
|
||||||
|
readonly #canOcr = computed(
|
||||||
|
() => this._permissionsService.canOcrFile(this.selectedFiles(), this.dossier()) && !this.#areFilesInErrorState(),
|
||||||
|
);
|
||||||
|
readonly #canSetToNew = computed(
|
||||||
|
() => this._permissionsService.canSetToNew(this.selectedFiles(), this.dossier()) && !this.#areFilesInErrorState(),
|
||||||
|
);
|
||||||
|
readonly #canSetToUnderReview = computed(
|
||||||
|
() => this._permissionsService.canSetUnderReview(this.selectedFiles(), this.dossier()) && !this.#areFilesInErrorState(),
|
||||||
|
);
|
||||||
|
readonly #canSetToUnderApproval = computed(
|
||||||
|
() => this._permissionsService.canSetUnderApproval(this.selectedFiles(), this.dossier()) && !this.#areFilesInErrorState(),
|
||||||
|
);
|
||||||
|
readonly #isReadyForApproval = computed(
|
||||||
|
() => this._permissionsService.isReadyForApproval(this.selectedFiles(), this.dossier()) && !this.#areFilesInErrorState(),
|
||||||
|
);
|
||||||
|
readonly #canApprove = computed(
|
||||||
|
() => this._permissionsService.canBeApproved(this.selectedFiles(), this.dossier()) && !this.#areFilesInErrorState(),
|
||||||
|
);
|
||||||
|
readonly #canUndoApproval = computed(
|
||||||
|
() => this._permissionsService.canUndoApproval(this.selectedFiles(), this.dossier()) && !this.#areFilesInErrorState(),
|
||||||
|
);
|
||||||
|
readonly #assignTooltip = computed(() =>
|
||||||
|
this.#allFilesAreUnderApproval() ? _('dossier-overview.assign-approver') : _('dossier-overview.assign-reviewer'),
|
||||||
|
);
|
||||||
|
readonly #toggleAnalysisTooltip = computed(() =>
|
||||||
|
this.#allFilesAreExcluded() ? _('file-preview.toggle-analysis.enable') : _('file-preview.toggle-analysis.disable'),
|
||||||
|
);
|
||||||
|
readonly #analysisForced = signal(false);
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private readonly _permissionsService: PermissionsService,
|
private readonly _permissionsService: PermissionsService,
|
||||||
private readonly _userPreferenceService: UserPreferenceService,
|
private readonly _userPreferenceService: UserPreferenceService,
|
||||||
private readonly _bulkActionsService: BulkActionsService,
|
private readonly _bulkActionsService: BulkActionsService,
|
||||||
|
private readonly _rulesService: RulesService,
|
||||||
|
private readonly _toaster: Toaster,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
private get _buttons(): Action[] {
|
get #buttons(): Action[] {
|
||||||
const actions: Action[] = [
|
const actions: Action[] = [
|
||||||
{
|
{
|
||||||
id: 'delete-files-btn',
|
id: 'delete-files-btn',
|
||||||
type: ActionTypes.circleBtn,
|
type: ActionTypes.circleBtn,
|
||||||
action: () => this._bulkActionsService.delete(this.selectedFiles),
|
action: () => this._bulkActionsService.delete(this.selectedFiles()),
|
||||||
tooltip: _('dossier-overview.bulk.delete'),
|
tooltip: _('dossier-overview.bulk.delete'),
|
||||||
icon: 'iqser:trash',
|
icon: 'iqser:trash',
|
||||||
show: this.#canDelete,
|
show: this.#canDelete(),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'assign-files-btn',
|
id: 'assign-files-btn',
|
||||||
type: ActionTypes.circleBtn,
|
type: ActionTypes.circleBtn,
|
||||||
action: () => this._bulkActionsService.assign(this.selectedFiles),
|
action: () => this._bulkActionsService.assign(this.selectedFiles()),
|
||||||
tooltip: this.#assignTooltip,
|
tooltip: this.#assignTooltip(),
|
||||||
icon: 'red:assign',
|
icon: 'red:assign',
|
||||||
show: this.#canAssign,
|
show: this.#canAssign(),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'assign-files-to-me-btn',
|
id: 'assign-files-to-me-btn',
|
||||||
type: ActionTypes.circleBtn,
|
type: ActionTypes.circleBtn,
|
||||||
action: () => this._bulkActionsService.assignToMe(this.selectedFiles),
|
action: () => this._bulkActionsService.assignToMe(this.selectedFiles()),
|
||||||
tooltip: _('dossier-overview.assign-me'),
|
tooltip: _('dossier-overview.assign-me'),
|
||||||
icon: 'red:assign-me',
|
icon: 'red:assign-me',
|
||||||
show: this.#canAssignToSelf,
|
show: this.#canAssignToSelf(),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'to-new-btn',
|
id: 'to-new-btn',
|
||||||
type: ActionTypes.circleBtn,
|
type: ActionTypes.circleBtn,
|
||||||
action: () => this._bulkActionsService.setToNew(this.selectedFiles),
|
action: () => this._bulkActionsService.setToNew(this.selectedFiles()),
|
||||||
tooltip: _('dossier-overview.back-to-new'),
|
tooltip: _('dossier-overview.back-to-new'),
|
||||||
icon: 'red:undo',
|
icon: 'red:undo',
|
||||||
show: this.#canSetToNew,
|
show: this.#canSetToNew(),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'to-under-approval-btn',
|
id: 'to-under-approval-btn',
|
||||||
type: ActionTypes.circleBtn,
|
type: ActionTypes.circleBtn,
|
||||||
action: () => this._bulkActionsService.setToUnderApproval(this.selectedFiles),
|
action: () => this._bulkActionsService.setToUnderApproval(this.selectedFiles()),
|
||||||
tooltip: _('dossier-overview.under-approval'),
|
tooltip: _('dossier-overview.under-approval'),
|
||||||
icon: 'red:ready-for-approval',
|
icon: 'red:ready-for-approval',
|
||||||
show: this.#canSetToUnderApproval,
|
show: this.#canSetToUnderApproval(),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'to-under-review-btn',
|
id: 'to-under-review-btn',
|
||||||
type: ActionTypes.circleBtn,
|
type: ActionTypes.circleBtn,
|
||||||
action: () => this._bulkActionsService.backToUnderReview(this.selectedFiles),
|
action: () => this._bulkActionsService.backToUnderReview(this.selectedFiles()),
|
||||||
tooltip: _('dossier-overview.under-review'),
|
tooltip: _('dossier-overview.under-review'),
|
||||||
icon: 'red:undo',
|
icon: 'red:undo',
|
||||||
show: this.#canSetToUnderReview,
|
show: this.#canSetToUnderReview(),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'download-files-btn',
|
id: 'download-files-btn',
|
||||||
type: ActionTypes.downloadBtn,
|
type: ActionTypes.downloadBtn,
|
||||||
show: !this.selectedFiles.some(file => file.processingStatus === ProcessingFileStatuses.ERROR || !file.lastProcessed),
|
show: !this.selectedFiles().some(file => file.processingStatus === ProcessingFileStatuses.ERROR || !file.lastProcessed),
|
||||||
files: this.selectedFiles,
|
files: this.selectedFiles(),
|
||||||
dossier: this.dossier,
|
dossier: this.dossier(),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'approve-files-btn',
|
id: 'approve-files-btn',
|
||||||
type: ActionTypes.circleBtn,
|
type: ActionTypes.circleBtn,
|
||||||
action: () => this._bulkActionsService.approve(this.selectedFiles),
|
action: () => this._bulkActionsService.approve(this.selectedFiles()),
|
||||||
disabled: !this.#canApprove,
|
disabled: !this.#canApprove,
|
||||||
tooltip: this.#canApprove ? _('dossier-overview.approve') : _('dossier-overview.approve-disabled'),
|
tooltip: this.#canApprove ? _('dossier-overview.approve') : _('dossier-overview.approve-disabled'),
|
||||||
icon: 'red:approved',
|
icon: 'red:approved',
|
||||||
show: this.#isReadyForApproval,
|
show: this.#isReadyForApproval(),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'set-under-approval-btn',
|
id: 'set-under-approval-btn',
|
||||||
type: ActionTypes.circleBtn,
|
type: ActionTypes.circleBtn,
|
||||||
action: () => this._bulkActionsService.setToUnderApproval(this.selectedFiles),
|
action: () => this._bulkActionsService.setToUnderApproval(this.selectedFiles()),
|
||||||
tooltip: _('dossier-overview.under-approval'),
|
tooltip: _('dossier-overview.under-approval'),
|
||||||
icon: 'red:undo',
|
icon: 'red:undo',
|
||||||
show: this.#canUndoApproval,
|
show: this.#canUndoApproval(),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'ocr-files-btn',
|
id: 'ocr-files-btn',
|
||||||
type: ActionTypes.circleBtn,
|
type: ActionTypes.circleBtn,
|
||||||
action: () => this._bulkActionsService.ocr(this.selectedFiles),
|
action: () => this._bulkActionsService.ocr(this.selectedFiles()),
|
||||||
tooltip: _('dossier-overview.ocr-file'),
|
tooltip: _('dossier-overview.ocr-file'),
|
||||||
icon: 'iqser:ocr',
|
icon: 'iqser:ocr',
|
||||||
show: this.#canOcr,
|
show: this.#canOcr(),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'reanalyse-files-btn',
|
id: 'reanalyse-files-btn',
|
||||||
type: ActionTypes.circleBtn,
|
type: ActionTypes.circleBtn,
|
||||||
action: () => this._bulkActionsService.reanalyse(this.selectedFiles),
|
action: () => this.#reanalyseBulk(this.selectedFiles()),
|
||||||
tooltip: _('dossier-overview.bulk.reanalyse'),
|
tooltip: this.#areRulesLocked() ? _('dossier-listing.rules.timeoutError') : _('dossier-overview.bulk.reanalyse'),
|
||||||
icon: 'iqser:refresh',
|
icon: 'iqser:refresh',
|
||||||
|
disabled: this.#areRulesLocked(),
|
||||||
show:
|
show:
|
||||||
this.#canReanalyse &&
|
this.#canReanalyse() &&
|
||||||
(this.#analysisForced || this.#canEnableAutoAnalysis || this.selectedFiles.every(file => file.isError)),
|
(this.#analysisForced() || this.#canEnableAutoAnalysis() || this.selectedFiles().every(file => file.isError)),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'stop-automatic-analysis-btn',
|
id: 'stop-automatic-analysis-btn',
|
||||||
type: ActionTypes.circleBtn,
|
type: ActionTypes.circleBtn,
|
||||||
action: () => this._bulkActionsService.toggleAutomaticAnalysis(this.selectedFiles),
|
action: () => this._bulkActionsService.toggleAutomaticAnalysis(this.selectedFiles()),
|
||||||
tooltip: _('dossier-overview.stop-auto-analysis'),
|
tooltip: _('dossier-overview.stop-auto-analysis'),
|
||||||
icon: 'red:disable-analysis',
|
icon: 'red:disable-analysis',
|
||||||
show: this.#canDisableAutoAnalysis,
|
show: this.#canDisableAutoAnalysis(),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'start-automatic-analysis-btn',
|
id: 'start-automatic-analysis-btn',
|
||||||
type: ActionTypes.circleBtn,
|
type: ActionTypes.circleBtn,
|
||||||
action: () => this._bulkActionsService.toggleAutomaticAnalysis(this.selectedFiles),
|
action: () => this._bulkActionsService.toggleAutomaticAnalysis(this.selectedFiles()),
|
||||||
tooltip: _('dossier-overview.start-auto-analysis'),
|
tooltip: _('dossier-overview.start-auto-analysis'),
|
||||||
icon: 'red:enable-analysis',
|
icon: 'red:enable-analysis',
|
||||||
show: this.#canEnableAutoAnalysis,
|
show: this.#canEnableAutoAnalysis(),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'toggle-analysis-btn',
|
id: 'toggle-analysis-btn',
|
||||||
type: ActionTypes.toggle,
|
type: ActionTypes.toggle,
|
||||||
action: () => this._bulkActionsService.toggleAnalysis(this.selectedFiles, !this.#allFilesAreExcluded),
|
action: () => this._bulkActionsService.toggleAnalysis(this.selectedFiles(), !this.#allFilesAreExcluded()),
|
||||||
tooltip: this.#toggleAnalysisTooltip,
|
tooltip: this.#toggleAnalysisTooltip(),
|
||||||
checked: !this.#allFilesAreExcluded,
|
checked: !this.#allFilesAreExcluded(),
|
||||||
show: this.#canToggleAnalysis,
|
show: this.#canToggleAnalysis(),
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
return actions.filter(btn => btn.show);
|
return actions.filter(btn => btn.show);
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnChanges() {
|
|
||||||
this._setup();
|
|
||||||
}
|
|
||||||
|
|
||||||
forceReanalysisAction($event: LongPressEvent) {
|
forceReanalysisAction($event: LongPressEvent) {
|
||||||
this.#analysisForced = !$event.touchEnd && this._userPreferenceService.isIqserDevMode;
|
this.#analysisForced.set(!$event.touchEnd && this._userPreferenceService.isIqserDevMode);
|
||||||
this._setup();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private _setup() {
|
async #reanalyseBulk(selectedFiles: File[]) {
|
||||||
if (!this.selectedFiles.length) {
|
const rules = await firstValueFrom(this._rulesService.getFor(this.dossier().dossierTemplateId));
|
||||||
|
if (rules.timeoutDetected) {
|
||||||
|
this._toaster.error(_('dossier-listing.rules.timeoutError'));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const allFilesAreUnderReviewOrUnassigned = this.selectedFiles.reduce(
|
await this._bulkActionsService.reanalyse(selectedFiles);
|
||||||
(acc, file) => acc && (file.isUnderReview || file.isNew),
|
|
||||||
true,
|
|
||||||
);
|
|
||||||
const allFilesAreUnderApproval = this.selectedFiles.reduce((acc, file) => acc && file.isUnderApproval, true);
|
|
||||||
const allFilesAreApproved = this.selectedFiles.reduce((acc, file) => acc && file.isApproved, true);
|
|
||||||
this.#allFilesAreExcluded = this.selectedFiles.reduce((acc, file) => acc && file.excluded, true);
|
|
||||||
this.#canMoveToSameState = allFilesAreUnderReviewOrUnassigned || allFilesAreUnderApproval || allFilesAreApproved;
|
|
||||||
|
|
||||||
this.#canAssign =
|
|
||||||
this.#canMoveToSameState &&
|
|
||||||
(this._permissionsService.canAssignUser(this.selectedFiles, this.dossier) ||
|
|
||||||
this._permissionsService.canUnassignUser(this.selectedFiles, this.dossier));
|
|
||||||
this.#canAssignToSelf = this.#canMoveToSameState && this._permissionsService.canAssignToSelf(this.selectedFiles, this.dossier);
|
|
||||||
|
|
||||||
this.#canDelete = this._permissionsService.canSoftDeleteFile(this.selectedFiles, this.dossier);
|
|
||||||
|
|
||||||
this.#canReanalyse = this._permissionsService.canReanalyseFile(this.selectedFiles, this.dossier);
|
|
||||||
|
|
||||||
this.#canDisableAutoAnalysis = this._permissionsService.canDisableAutoAnalysis(this.selectedFiles, this.dossier);
|
|
||||||
|
|
||||||
this.#canEnableAutoAnalysis = this._permissionsService.canEnableAutoAnalysis(this.selectedFiles, this.dossier);
|
|
||||||
|
|
||||||
this.#canToggleAnalysis = this._permissionsService.canToggleAnalysis(this.selectedFiles, this.dossier);
|
|
||||||
|
|
||||||
this.#canOcr = this._permissionsService.canOcrFile(this.selectedFiles, this.dossier);
|
|
||||||
|
|
||||||
this.#canSetToNew = this._permissionsService.canSetToNew(this.selectedFiles, this.dossier);
|
|
||||||
|
|
||||||
this.#canSetToUnderReview = this._permissionsService.canSetUnderReview(this.selectedFiles, this.dossier);
|
|
||||||
|
|
||||||
this.#canSetToUnderApproval = this._permissionsService.canSetUnderApproval(this.selectedFiles, this.dossier);
|
|
||||||
|
|
||||||
this.#isReadyForApproval = this._permissionsService.isReadyForApproval(this.selectedFiles, this.dossier);
|
|
||||||
|
|
||||||
this.#canApprove = this._permissionsService.canBeApproved(this.selectedFiles, this.dossier);
|
|
||||||
|
|
||||||
this.#canUndoApproval = this._permissionsService.canUndoApproval(this.selectedFiles, this.dossier);
|
|
||||||
|
|
||||||
this.#assignTooltip = allFilesAreUnderApproval ? _('dossier-overview.assign-approver') : _('dossier-overview.assign-reviewer');
|
|
||||||
|
|
||||||
this.#toggleAnalysisTooltip = this.#allFilesAreExcluded
|
|
||||||
? _('file-preview.toggle-analysis.enable')
|
|
||||||
: _('file-preview.toggle-analysis.disable');
|
|
||||||
|
|
||||||
this.buttons = this._buttons;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -61,7 +61,8 @@
|
|||||||
*ngFor="let config of statusConfig"
|
*ngFor="let config of statusConfig"
|
||||||
[attr.help-mode-key]="'dashboard_in_dossier'"
|
[attr.help-mode-key]="'dashboard_in_dossier'"
|
||||||
[config]="config"
|
[config]="config"
|
||||||
filterKey="processingTypeFilters"
|
[class.indent]="!!PendingTypes[config.id]"
|
||||||
|
[filterKey]="PendingTypes[config.id] ? 'pendingTypeFilters' : 'processingTypeFilters'"
|
||||||
></iqser-progress-bar>
|
></iqser-progress-bar>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@ -45,3 +45,7 @@
|
|||||||
iqser-progress-bar:not(:last-child) {
|
iqser-progress-bar:not(:last-child) {
|
||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.indent {
|
||||||
|
margin-left: 32px;
|
||||||
|
}
|
||||||
|
|||||||
@ -21,7 +21,9 @@ import {
|
|||||||
DossierAttributeWithValue,
|
DossierAttributeWithValue,
|
||||||
DossierStats,
|
DossierStats,
|
||||||
File,
|
File,
|
||||||
|
FileErrorCodes,
|
||||||
IDossierRequest,
|
IDossierRequest,
|
||||||
|
PendingTypes,
|
||||||
ProcessingTypes,
|
ProcessingTypes,
|
||||||
StatusSorter,
|
StatusSorter,
|
||||||
User,
|
User,
|
||||||
@ -74,6 +76,7 @@ export class DossierDetailsComponent extends ContextComponent<DossierDetailsCont
|
|||||||
#currentChartSubtitleIndex = 0;
|
#currentChartSubtitleIndex = 0;
|
||||||
readonly #dossierId = getParam(DOSSIER_ID);
|
readonly #dossierId = getParam(DOSSIER_ID);
|
||||||
protected readonly circleButtonTypes = CircleButtonTypes;
|
protected readonly circleButtonTypes = CircleButtonTypes;
|
||||||
|
protected readonly PendingTypes = PendingTypes;
|
||||||
@Input() dossierAttributes: DossierAttributeWithValue[];
|
@Input() dossierAttributes: DossierAttributeWithValue[];
|
||||||
@Output() readonly toggleCollapse = new EventEmitter();
|
@Output() readonly toggleCollapse = new EventEmitter();
|
||||||
editingOwner = false;
|
editingOwner = false;
|
||||||
@ -97,13 +100,17 @@ export class DossierDetailsComponent extends ContextComponent<DossierDetailsCont
|
|||||||
super();
|
super();
|
||||||
const dossier$ = _dossiersService.getEntityChanged$(this.#dossierId).pipe(shareLast());
|
const dossier$ = _dossiersService.getEntityChanged$(this.#dossierId).pipe(shareLast());
|
||||||
const filesChanged$ = _filesMapService.watchChanged$(this.#dossierId).pipe(shareLast());
|
const filesChanged$ = _filesMapService.watchChanged$(this.#dossierId).pipe(shareLast());
|
||||||
|
const files$ = _filesMapService.get$(this.#dossierId).pipe(shareLast());
|
||||||
const dossierStats$ = dossierStatsService.watch$(this.#dossierId).pipe(shareLast());
|
const dossierStats$ = dossierStatsService.watch$(this.#dossierId).pipe(shareLast());
|
||||||
const dossierStatsWithEffects$ = dossierStats$.pipe(
|
const dossierStatsWithEffects$ = dossierStats$.pipe(
|
||||||
combineLatestWith(filesChanged$),
|
combineLatestWith(filesChanged$),
|
||||||
tap(([stats]) => this.#calculateChartConfig(stats)),
|
tap(([stats]) => this.#calculateChartConfig(stats)),
|
||||||
map(([stats]) => stats),
|
map(([stats]) => stats),
|
||||||
);
|
);
|
||||||
const statusConfig$ = dossierStats$.pipe(map(stats => this.#calculateStatusConfig(stats)));
|
const statusConfig$ = dossierStats$.pipe(
|
||||||
|
combineLatestWith(files$),
|
||||||
|
map(([stats, files]) => this.#calculateStatusConfig(stats, files)),
|
||||||
|
);
|
||||||
|
|
||||||
super._initContext({
|
super._initContext({
|
||||||
needsWorkFilters: filterService.getFilterModels$('needsWorkFilters'),
|
needsWorkFilters: filterService.getFilterModels$('needsWorkFilters'),
|
||||||
@ -152,7 +159,8 @@ export class DossierDetailsComponent extends ContextComponent<DossierDetailsCont
|
|||||||
.reduce((sum: number, file: File) => sum + file.numberOfPages, 0);
|
.reduce((sum: number, file: File) => sum + file.numberOfPages, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
#calculateStatusConfig(stats: DossierStats): ProgressBarConfigModel[] {
|
#calculateStatusConfig(stats: DossierStats, files: File[]): ProgressBarConfigModel[] {
|
||||||
|
const numberOfTimeoutFiles = files.filter(file => file.errorCode === FileErrorCodes.RULES_EXECUTION_TIMEOUT).length;
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
id: ProcessingTypes.pending,
|
id: ProcessingTypes.pending,
|
||||||
@ -161,6 +169,13 @@ export class DossierDetailsComponent extends ContextComponent<DossierDetailsCont
|
|||||||
count: stats.processingStats.pending,
|
count: stats.processingStats.pending,
|
||||||
icon: 'red:reanalyse',
|
icon: 'red:reanalyse',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: PendingTypes.timeout,
|
||||||
|
label: _('processing-status.pending-timeout'),
|
||||||
|
total: stats.numberOfFiles,
|
||||||
|
count: numberOfTimeoutFiles,
|
||||||
|
icon: 'red:reanalyse',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
id: ProcessingTypes.ocr,
|
id: ProcessingTypes.ocr,
|
||||||
label: _('processing-status.ocr'),
|
label: _('processing-status.ocr'),
|
||||||
|
|||||||
@ -9,7 +9,7 @@
|
|||||||
<mat-icon *ngIf="!fileAttribute.editable" [matTooltip]="'readonly' | translate" svgIcon="red:read-only"></mat-icon>
|
<mat-icon *ngIf="!fileAttribute.editable" [matTooltip]="'readonly' | translate" svgIcon="red:read-only"></mat-icon>
|
||||||
<span
|
<span
|
||||||
*ngIf="!isDate; else date"
|
*ngIf="!isDate; else date"
|
||||||
[style.max-width]="attributeValueWidth"
|
[style.max-width]="attributeValueWidth()"
|
||||||
[matTooltip]="fileAttributeValue"
|
[matTooltip]="fileAttributeValue"
|
||||||
[ngClass]="{ hide: isInEditMode, 'clamp-3': mode !== 'workflow' }"
|
[ngClass]="{ hide: isInEditMode, 'clamp-3': mode !== 'workflow' }"
|
||||||
>
|
>
|
||||||
@ -56,13 +56,12 @@
|
|||||||
class="edit-input"
|
class="edit-input"
|
||||||
iqserStopPropagation
|
iqserStopPropagation
|
||||||
>
|
>
|
||||||
<form [formGroup]="form">
|
<form [formGroup]="form" [style.width]="inputFormWidth()">
|
||||||
<iqser-dynamic-input
|
<iqser-dynamic-input
|
||||||
(closedDatepicker)="closedDatepicker = $event"
|
(closedDatepicker)="closedDatepicker = $event"
|
||||||
(keyup.enter)="form.valid && save()"
|
(keyup.enter)="form.valid && save()"
|
||||||
(keydown.escape)="close()"
|
(keydown.escape)="close()"
|
||||||
[style.max-width]="editFieldWidth"
|
[style.width]="inputFieldWidth()"
|
||||||
[style.min-width]="editFieldWidth"
|
|
||||||
[formControlName]="fileAttribute.id"
|
[formControlName]="fileAttribute.id"
|
||||||
[id]="fileAttribute.id"
|
[id]="fileAttribute.id"
|
||||||
[ngClass]="{ 'workflow-input': mode === 'workflow' || fileNameColumn, 'file-name-input': fileNameColumn }"
|
[ngClass]="{ 'workflow-input': mode === 'workflow' || fileNameColumn, 'file-name-input': fileNameColumn }"
|
||||||
|
|||||||
@ -97,7 +97,7 @@
|
|||||||
iqser-circle-button {
|
iqser-circle-button {
|
||||||
margin-left: 15px;
|
margin-left: 15px;
|
||||||
|
|
||||||
@media screen and (max-width: 1395px) {
|
@media screen and (max-width: 1745px) {
|
||||||
margin-left: 6px;
|
margin-left: 6px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { AsyncPipe, NgClass, NgIf, NgTemplateOutlet } from '@angular/common';
|
import { AsyncPipe, NgClass, NgIf } from '@angular/common';
|
||||||
import { Component, computed, effect, HostListener, Input, OnDestroy } from '@angular/core';
|
import { Component, computed, effect, HostListener, input, Input, OnDestroy } from '@angular/core';
|
||||||
import { AbstractControl, FormBuilder, FormsModule, ReactiveFormsModule, UntypedFormGroup, ValidatorFn } from '@angular/forms';
|
import { AbstractControl, FormBuilder, FormsModule, ReactiveFormsModule, UntypedFormGroup, ValidatorFn } from '@angular/forms';
|
||||||
import { MatIconModule } from '@angular/material/icon';
|
import { MatIconModule } from '@angular/material/icon';
|
||||||
import { MatTooltipModule } from '@angular/material/tooltip';
|
import { MatTooltipModule } from '@angular/material/tooltip';
|
||||||
@ -14,7 +14,7 @@ import {
|
|||||||
StopPropagationDirective,
|
StopPropagationDirective,
|
||||||
Toaster,
|
Toaster,
|
||||||
} from '@iqser/common-ui';
|
} from '@iqser/common-ui';
|
||||||
import { Debounce, log } from '@iqser/common-ui/lib/utils';
|
import { Debounce } from '@iqser/common-ui/lib/utils';
|
||||||
import { TranslateModule } from '@ngx-translate/core';
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
import { Dossier, File, FileAttributeConfigTypes, IFileAttributeConfig } from '@red/domain';
|
import { Dossier, File, FileAttributeConfigTypes, IFileAttributeConfig } from '@red/domain';
|
||||||
import { FileAttributesService } from '@services/entity-services/file-attributes.service';
|
import { FileAttributesService } from '@services/entity-services/file-attributes.service';
|
||||||
@ -42,14 +42,12 @@ import { ConfigService } from '../../config.service';
|
|||||||
ReactiveFormsModule,
|
ReactiveFormsModule,
|
||||||
DynamicInputComponent,
|
DynamicInputComponent,
|
||||||
CircleButtonComponent,
|
CircleButtonComponent,
|
||||||
NgTemplateOutlet,
|
|
||||||
TranslateModule,
|
TranslateModule,
|
||||||
StopPropagationDirective,
|
StopPropagationDirective,
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
export class FileAttributeComponent extends BaseFormComponent implements OnDestroy {
|
export class FileAttributeComponent extends BaseFormComponent implements OnDestroy {
|
||||||
readonly #subscriptions = new Subscription();
|
readonly #subscriptions = new Subscription();
|
||||||
#widthFactor = window.innerWidth >= 1800 ? 0.85 : 0.7;
|
|
||||||
isInEditMode = false;
|
isInEditMode = false;
|
||||||
closedDatepicker = true;
|
closedDatepicker = true;
|
||||||
@Input({ required: true }) fileAttribute!: IFileAttributeConfig;
|
@Input({ required: true }) fileAttribute!: IFileAttributeConfig;
|
||||||
@ -66,7 +64,10 @@ export class FileAttributeComponent extends BaseFormComponent implements OnDestr
|
|||||||
@Input({ required: true }) dossier!: Dossier;
|
@Input({ required: true }) dossier!: Dossier;
|
||||||
@Input() fileNameColumn = false;
|
@Input() fileNameColumn = false;
|
||||||
readonlyAttrs: string[] = [];
|
readonlyAttrs: string[] = [];
|
||||||
@Input() width?: number;
|
readonly width = input<number>();
|
||||||
|
readonly inputFormWidth = computed(() => (this.width() ? this.width() + 'px' : 'unset'));
|
||||||
|
readonly inputFieldWidth = computed(() => (this.width() ? this.width() - 50 + 'px' : 'unset'));
|
||||||
|
readonly attributeValueWidth = computed(() => (this.width() ? `${this.width() * 0.9}px` : 'unset'));
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
router: Router,
|
router: Router,
|
||||||
@ -99,14 +100,6 @@ export class FileAttributeComponent extends BaseFormComponent implements OnDestr
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
get editFieldWidth(): string {
|
|
||||||
return this.width ? `${this.width * this.#widthFactor}px` : 'unset';
|
|
||||||
}
|
|
||||||
|
|
||||||
get attributeValueWidth(): string {
|
|
||||||
return this.width ? `${this.width * 0.9}px` : 'unset';
|
|
||||||
}
|
|
||||||
|
|
||||||
get isDate(): boolean {
|
get isDate(): boolean {
|
||||||
return this.fileAttribute.type === FileAttributeConfigTypes.DATE;
|
return this.fileAttribute.type === FileAttributeConfigTypes.DATE;
|
||||||
}
|
}
|
||||||
@ -123,15 +116,6 @@ export class FileAttributeComponent extends BaseFormComponent implements OnDestr
|
|||||||
return this.file.fileAttributes.attributeIdToValue[this.fileAttribute.id];
|
return this.file.fileAttributes.attributeIdToValue[this.fileAttribute.id];
|
||||||
}
|
}
|
||||||
|
|
||||||
@HostListener('window:resize')
|
|
||||||
onResize() {
|
|
||||||
if (window.innerWidth >= 1800) {
|
|
||||||
this.#widthFactor = 0.85;
|
|
||||||
} else {
|
|
||||||
this.#widthFactor = 0.7;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Debounce(60)
|
@Debounce(60)
|
||||||
@HostListener('document:click', ['$event'])
|
@HostListener('document:click', ['$event'])
|
||||||
clickOutside($event: MouseEvent) {
|
clickOutside($event: MouseEvent) {
|
||||||
@ -154,12 +138,14 @@ export class FileAttributeComponent extends BaseFormComponent implements OnDestr
|
|||||||
}
|
}
|
||||||
|
|
||||||
handleFieldClick($event: MouseEvent) {
|
handleFieldClick($event: MouseEvent) {
|
||||||
|
$event.preventDefault();
|
||||||
if (!this.fileNameColumn) {
|
if (!this.fileNameColumn) {
|
||||||
this.editFileAttribute($event);
|
this.editFileAttribute($event);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
editFileAttribute($event: MouseEvent) {
|
editFileAttribute($event: MouseEvent) {
|
||||||
|
$event.preventDefault();
|
||||||
if (
|
if (
|
||||||
!this.file.isInitialProcessing &&
|
!this.file.isInitialProcessing &&
|
||||||
this.permissionsService.canEditFileAttributes(this.file, this.dossier) &&
|
this.permissionsService.canEditFileAttributes(this.file, this.dossier) &&
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
<iqser-page-header
|
<iqser-page-header
|
||||||
(closeAction)="router.navigate([dossier.dossiersListRouterLink])"
|
(closeAction)="router.navigate([dossier().dossiersListRouterLink])"
|
||||||
[actionConfigs]="actionConfigs"
|
[actionConfigs]="actionConfigs()"
|
||||||
[helpModeKey]="'document'"
|
[helpModeKey]="'document'"
|
||||||
[showCloseButton]="true"
|
[showCloseButton]="true"
|
||||||
[viewModeSelection]="viewModeSelection"
|
[viewModeSelection]="viewModeSelection"
|
||||||
@ -10,36 +10,43 @@
|
|||||||
[attr.help-mode-key]="isDocumine ? 'dossier_download_dossier' : 'download_dossier_in_dossier'"
|
[attr.help-mode-key]="isDocumine ? 'dossier_download_dossier' : 'download_dossier_in_dossier'"
|
||||||
[buttonId]="'download-files-btn'"
|
[buttonId]="'download-files-btn'"
|
||||||
[disabled]="downloadFilesDisabled$ | async"
|
[disabled]="downloadFilesDisabled$ | async"
|
||||||
[dossier]="dossier"
|
[dossier]="dossier()"
|
||||||
[files]="entitiesService.all$ | async"
|
[files]="entitiesService.all$ | async"
|
||||||
|
[iqserDisableStopPropagation]="shouldDisableStopPropagation()"
|
||||||
dossierDownload
|
dossierDownload
|
||||||
></redaction-file-download-btn>
|
></redaction-file-download-btn>
|
||||||
|
|
||||||
<iqser-circle-button
|
<iqser-circle-button
|
||||||
(action)="downloadDossierAsCSV()"
|
(action)="downloadDossierAsCSV()"
|
||||||
*ngIf="permissionsService.canDownloadCsvReport(dossier)"
|
*ngIf="permissionsService.canDownloadCsvReport(dossier())"
|
||||||
[attr.help-mode-key]="'download_csv'"
|
[attr.help-mode-key]="'download_csv'"
|
||||||
[disabled]="listingService.areSomeSelected$ | async"
|
[disabled]="listingService.areSomeSelected$ | async"
|
||||||
[icon]="'iqser:csv'"
|
[icon]="'iqser:csv'"
|
||||||
|
[iqserDisableStopPropagation]="shouldDisableStopPropagation()"
|
||||||
[tooltip]="'dossier-overview.header-actions.download-csv' | translate"
|
[tooltip]="'dossier-overview.header-actions.download-csv' | translate"
|
||||||
></iqser-circle-button>
|
></iqser-circle-button>
|
||||||
|
|
||||||
<iqser-circle-button
|
<iqser-circle-button
|
||||||
(action)="reanalyseDossier()"
|
(action)="reanalyseDossier()"
|
||||||
*ngIf="permissionsService.displayReanalyseBtn(dossier)"
|
*ngIf="permissionsService.displayReanalyseBtn(dossier())"
|
||||||
[disabled]="listingService.areSomeSelected$ | async"
|
[disabled]="(listingService.areSomeSelected$ | async) || areRulesLocked()"
|
||||||
[icon]="'iqser:refresh'"
|
[icon]="'iqser:refresh'"
|
||||||
|
[iqserDisableStopPropagation]="shouldDisableStopPropagation()"
|
||||||
[tooltipClass]="'small warn'"
|
[tooltipClass]="'small warn'"
|
||||||
[tooltip]="'dossier-overview.new-rule.toast.actions.reanalyse-all' | translate"
|
[tooltip]="
|
||||||
|
(areRulesLocked() ? 'dossier-listing.rules.timeoutError' : 'dossier-overview.new-rule.toast.actions.reanalyse-all')
|
||||||
|
| translate
|
||||||
|
"
|
||||||
[type]="circleButtonTypes.warn"
|
[type]="circleButtonTypes.warn"
|
||||||
></iqser-circle-button>
|
></iqser-circle-button>
|
||||||
|
|
||||||
<iqser-circle-button
|
<iqser-circle-button
|
||||||
(action)="upload.emit()"
|
(action)="upload.emit()"
|
||||||
*ngIf="permissionsService.canUploadFiles(dossier)"
|
*ngIf="permissionsService.canUploadFiles(dossier())"
|
||||||
[attr.help-mode-key]="'upload_document'"
|
[attr.help-mode-key]="'upload_document'"
|
||||||
[buttonId]="'upload-document-btn'"
|
[buttonId]="'upload-document-btn'"
|
||||||
[icon]="'iqser:upload'"
|
[icon]="'iqser:upload'"
|
||||||
|
[iqserDisableStopPropagation]="shouldDisableStopPropagation()"
|
||||||
[tooltip]="'dossier-overview.header-actions.upload-document' | translate"
|
[tooltip]="'dossier-overview.header-actions.upload-document' | translate"
|
||||||
[type]="circleButtonTypes.primary"
|
[type]="circleButtonTypes.primary"
|
||||||
class="ml-14"
|
class="ml-14"
|
||||||
|
|||||||
@ -1,12 +1,10 @@
|
|||||||
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
|
import { ChangeDetectionStrategy, Component, computed, EventEmitter, input, Output } from '@angular/core';
|
||||||
import {
|
import {
|
||||||
ActionConfig,
|
|
||||||
CircleButtonComponent,
|
CircleButtonComponent,
|
||||||
CircleButtonTypes,
|
CircleButtonTypes,
|
||||||
DisableStopPropagationDirective,
|
DisableStopPropagationDirective,
|
||||||
EntitiesService,
|
EntitiesService,
|
||||||
getConfig,
|
getConfig,
|
||||||
IqserAllowDirective,
|
|
||||||
IqserListingModule,
|
IqserListingModule,
|
||||||
ListingService,
|
ListingService,
|
||||||
LoadingService,
|
LoadingService,
|
||||||
@ -24,13 +22,14 @@ import { PrimaryFileAttributeService } from '@services/primary-file-attribute.se
|
|||||||
import { Router } from '@angular/router';
|
import { Router } from '@angular/router';
|
||||||
import { Roles } from '@users/roles';
|
import { Roles } from '@users/roles';
|
||||||
import { SortingService } from '@iqser/common-ui/lib/sorting';
|
import { SortingService } from '@iqser/common-ui/lib/sorting';
|
||||||
import { List, some } from '@iqser/common-ui/lib/utils';
|
import { some } from '@iqser/common-ui/lib/utils';
|
||||||
import { ComponentLogService } from '@services/files/component-log.service';
|
|
||||||
import { MatMenu, MatMenuItem, MatMenuTrigger } from '@angular/material/menu';
|
|
||||||
import { AsyncPipe, NgIf } from '@angular/common';
|
import { AsyncPipe, NgIf } from '@angular/common';
|
||||||
import { TranslateModule } from '@ngx-translate/core';
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
import { FileDownloadBtnComponent } from '@shared/components/buttons/file-download-btn/file-download-btn.component';
|
import { FileDownloadBtnComponent } from '@shared/components/buttons/file-download-btn/file-download-btn.component';
|
||||||
import { ViewModeSelectionComponent } from '../view-mode-selection/view-mode-selection.component';
|
import { ViewModeSelectionComponent } from '../view-mode-selection/view-mode-selection.component';
|
||||||
|
import { RulesService } from '../../../admin/services/rules.service';
|
||||||
|
import { toSignal } from '@angular/core/rxjs-interop';
|
||||||
|
import { FileAttributesService } from '@services/entity-services/file-attributes.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'redaction-dossier-overview-screen-header [dossier] [upload]',
|
selector: 'redaction-dossier-overview-screen-header [dossier] [upload]',
|
||||||
@ -39,27 +38,29 @@ import { ViewModeSelectionComponent } from '../view-mode-selection/view-mode-sel
|
|||||||
imports: [
|
imports: [
|
||||||
IqserListingModule,
|
IqserListingModule,
|
||||||
CircleButtonComponent,
|
CircleButtonComponent,
|
||||||
MatMenuTrigger,
|
|
||||||
IqserAllowDirective,
|
|
||||||
AsyncPipe,
|
AsyncPipe,
|
||||||
TranslateModule,
|
TranslateModule,
|
||||||
FileDownloadBtnComponent,
|
FileDownloadBtnComponent,
|
||||||
NgIf,
|
NgIf,
|
||||||
ViewModeSelectionComponent,
|
ViewModeSelectionComponent,
|
||||||
DisableStopPropagationDirective,
|
DisableStopPropagationDirective,
|
||||||
MatMenu,
|
|
||||||
MatMenuItem,
|
|
||||||
],
|
],
|
||||||
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
})
|
})
|
||||||
export class DossierOverviewScreenHeaderComponent implements OnInit {
|
export class DossierOverviewScreenHeaderComponent {
|
||||||
@Input() dossier: Dossier;
|
readonly dossier = input<Dossier>();
|
||||||
@Output() readonly upload = new EventEmitter<void>();
|
@Output() readonly upload = new EventEmitter<void>();
|
||||||
readonly circleButtonTypes = CircleButtonTypes;
|
readonly circleButtonTypes = CircleButtonTypes;
|
||||||
readonly roles = Roles;
|
readonly roles = Roles;
|
||||||
actionConfigs: List<ActionConfig>;
|
|
||||||
readonly downloadFilesDisabled$: Observable<boolean>;
|
readonly downloadFilesDisabled$: Observable<boolean>;
|
||||||
readonly downloadComponentLogsDisabled$: Observable<boolean>;
|
readonly downloadComponentLogsDisabled$: Observable<boolean>;
|
||||||
readonly isDocumine = getConfig().IS_DOCUMINE;
|
readonly isDocumine = getConfig().IS_DOCUMINE;
|
||||||
|
readonly areSomeSelected = toSignal(this.listingService.areSomeSelected$);
|
||||||
|
readonly actionConfigs = computed(() => this.configService.actionConfig(this.dossier().id, this.areSomeSelected()));
|
||||||
|
readonly areRulesLocked = computed(() => {
|
||||||
|
return this._rulesService.currentTemplateRules().timeoutDetected;
|
||||||
|
});
|
||||||
|
readonly shouldDisableStopPropagation = computed(() => this._fileAttributesService.isEditingFileAttribute());
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private readonly _toaster: Toaster,
|
private readonly _toaster: Toaster,
|
||||||
@ -73,6 +74,8 @@ export class DossierOverviewScreenHeaderComponent implements OnInit {
|
|||||||
private readonly _reanalysisService: ReanalysisService,
|
private readonly _reanalysisService: ReanalysisService,
|
||||||
private readonly _loadingService: LoadingService,
|
private readonly _loadingService: LoadingService,
|
||||||
private readonly _primaryFileAttributeService: PrimaryFileAttributeService,
|
private readonly _primaryFileAttributeService: PrimaryFileAttributeService,
|
||||||
|
private readonly _rulesService: RulesService,
|
||||||
|
private readonly _fileAttributesService: FileAttributesService,
|
||||||
) {
|
) {
|
||||||
const someNotProcessed$ = this.entitiesService.all$.pipe(some(file => !file.lastProcessed));
|
const someNotProcessed$ = this.entitiesService.all$.pipe(some(file => !file.lastProcessed));
|
||||||
this.downloadFilesDisabled$ = combineLatest([this.listingService.areSomeSelected$, someNotProcessed$]).pipe(
|
this.downloadFilesDisabled$ = combineLatest([this.listingService.areSomeSelected$, someNotProcessed$]).pipe(
|
||||||
@ -83,14 +86,15 @@ export class DossierOverviewScreenHeaderComponent implements OnInit {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit() {
|
|
||||||
this.actionConfigs = this.configService.actionConfig(this.dossier.id, this.listingService.areSomeSelected$);
|
|
||||||
}
|
|
||||||
|
|
||||||
async reanalyseDossier() {
|
async reanalyseDossier() {
|
||||||
this._loadingService.start();
|
this._loadingService.start();
|
||||||
|
const rules = await firstValueFrom(this._rulesService.getFor(this.dossier().dossierTemplateId));
|
||||||
|
if (rules.timeoutDetected) {
|
||||||
|
this._toaster.error(_('dossier-listing.rules.timeoutError'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
await this._reanalysisService.reanalyzeDossier(this.dossier, true);
|
await this._reanalysisService.reanalyzeDossier(this.dossier(), true);
|
||||||
this._toaster.success(_('dossier-overview.reanalyse-dossier.success'));
|
this._toaster.success(_('dossier-overview.reanalyse-dossier.success'));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
this._toaster.error(_('dossier-overview.reanalyse-dossier.error'));
|
this._toaster.error(_('dossier-overview.reanalyse-dossier.error'));
|
||||||
@ -101,12 +105,12 @@ export class DossierOverviewScreenHeaderComponent implements OnInit {
|
|||||||
async downloadDossierAsCSV() {
|
async downloadDossierAsCSV() {
|
||||||
const displayedEntities = await firstValueFrom(this.listingService.displayed$);
|
const displayedEntities = await firstValueFrom(this.listingService.displayed$);
|
||||||
const entities = this.sortingService.defaultSort(displayedEntities);
|
const entities = this.sortingService.defaultSort(displayedEntities);
|
||||||
const fileName = this.dossier.dossierName + '.export.csv';
|
const fileName = this.dossier().dossierName + '.export.csv';
|
||||||
const mapper = (file?: File) => ({
|
const mapper = (file?: File) => ({
|
||||||
...file,
|
...file,
|
||||||
hasAnnotations: file.hasRedactions,
|
hasAnnotations: file.hasRedactions,
|
||||||
assignee: this._userService.getName(file.assignee) || '-',
|
assignee: this._userService.getName(file.assignee) || '-',
|
||||||
primaryAttribute: this._primaryFileAttributeService.getPrimaryFileAttributeValue(file, this.dossier.dossierTemplateId),
|
primaryAttribute: this._primaryFileAttributeService.getPrimaryFileAttributeValue(file, this.dossier().dossierTemplateId),
|
||||||
});
|
});
|
||||||
const documineOnlyFields = ['hasAnnotations'];
|
const documineOnlyFields = ['hasAnnotations'];
|
||||||
const redactionOnlyFields = ['hasHints', 'hasImages', 'hasUpdates', 'hasRedactions'];
|
const redactionOnlyFields = ['hasHints', 'hasImages', 'hasUpdates', 'hasRedactions'];
|
||||||
|
|||||||
@ -2,18 +2,33 @@
|
|||||||
<redaction-annotation-icon
|
<redaction-annotation-icon
|
||||||
*ngIf="file.analysisRequired"
|
*ngIf="file.analysisRequired"
|
||||||
[color]="analysisColor$ | async"
|
[color]="analysisColor$ | async"
|
||||||
label="A"
|
[label]="(workloadTranslations['analysis'] | translate)[0]"
|
||||||
|
type="square"
|
||||||
|
></redaction-annotation-icon>
|
||||||
|
<redaction-annotation-icon
|
||||||
|
*ngIf="updated"
|
||||||
|
[color]="updatedColor$ | async"
|
||||||
|
[label]="(workloadTranslations['updated'] | translate)[0]"
|
||||||
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
|
<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
|
||||||
<redaction-annotation-icon *ngIf="file.hintsOnly" [color]="hintColor$ | async" label="H" type="circle"></redaction-annotation-icon>
|
*ngIf="file.hasImages"
|
||||||
|
[color]="imageColor$ | async"
|
||||||
|
[label]="(workloadTranslations['image'] | translate)[0]"
|
||||||
|
type="square"
|
||||||
|
></redaction-annotation-icon>
|
||||||
|
<redaction-annotation-icon
|
||||||
|
*ngIf="file.hintsOnly"
|
||||||
|
[color]="hintColor$ | async"
|
||||||
|
[label]="(workloadTranslations['hint'] | translate)[0]"
|
||||||
|
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>
|
||||||
|
|||||||
@ -10,6 +10,7 @@ import { AnnotationIconComponent } from '@shared/components/annotation-icon/anno
|
|||||||
import { AsyncPipe, NgIf } from '@angular/common';
|
import { AsyncPipe, NgIf } from '@angular/common';
|
||||||
import { TranslateModule } from '@ngx-translate/core';
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
import { MatIcon } from '@angular/material/icon';
|
import { MatIcon } from '@angular/material/icon';
|
||||||
|
import { workloadTranslations } from '@translations/workload-translations';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'redaction-file-workload',
|
selector: 'redaction-file-workload',
|
||||||
@ -27,6 +28,7 @@ export class FileWorkloadComponent implements OnInit {
|
|||||||
analysisColor$: Observable<string>;
|
analysisColor$: Observable<string>;
|
||||||
hintColor$: Observable<string>;
|
hintColor$: Observable<string>;
|
||||||
redactionColor$: Observable<string>;
|
redactionColor$: Observable<string>;
|
||||||
|
readonly workloadTranslations = workloadTranslations;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private readonly _userService: UserService,
|
private readonly _userService: UserService,
|
||||||
|
|||||||
@ -23,11 +23,13 @@
|
|||||||
<div class="cell" *ngIf="!isDocumine">
|
<div class="cell" *ngIf="!isDocumine">
|
||||||
<redaction-file-workload *ngIf="!file.excluded" [file]="file"></redaction-file-workload>
|
<redaction-file-workload *ngIf="!file.excluded" [file]="file"></redaction-file-workload>
|
||||||
</div>
|
</div>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
<div class="user-column cell">
|
<div class="user-column cell">
|
||||||
<iqser-initials-avatar [user]="file.assignee" [withName]="true"></iqser-initials-avatar>
|
<iqser-initials-avatar [user]="file.assignee" [withName]="true"></iqser-initials-avatar>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<ng-container *ngIf="!file.isError">
|
||||||
<div class="cell">
|
<div class="cell">
|
||||||
<div class="small-label stats-subtitle">
|
<div class="small-label stats-subtitle">
|
||||||
<div>
|
<div>
|
||||||
@ -39,7 +41,12 @@
|
|||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
||||||
<div [class.extend-cols]="file.isError" class="status-container cell">
|
<div [class.extend-cols]="file.isError" class="status-container cell">
|
||||||
<div *ngIf="file.isError" class="small-label error" translate="dossier-overview.file-listing.file-entry.file-error"></div>
|
<div
|
||||||
|
*ngIf="file.isError"
|
||||||
|
class="small-label error"
|
||||||
|
translate="dossier-overview.file-listing.file-entry.file-error"
|
||||||
|
[translateParams]="{ errorCode: file.errorCode }"
|
||||||
|
></div>
|
||||||
|
|
||||||
<div *ngIf="file.isUnprocessed" class="small-label" translate="dossier-overview.file-listing.file-entry.file-pending"></div>
|
<div *ngIf="file.isUnprocessed" class="small-label" translate="dossier-overview.file-listing.file-entry.file-pending"></div>
|
||||||
|
|
||||||
|
|||||||
@ -5,7 +5,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.extend-cols {
|
.extend-cols {
|
||||||
grid-column-end: span 3;
|
grid-column-end: span 2;
|
||||||
align-items: flex-end;
|
align-items: flex-end;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -24,6 +24,7 @@ import {
|
|||||||
FileAttributeConfigType,
|
FileAttributeConfigType,
|
||||||
FileAttributeConfigTypes,
|
FileAttributeConfigTypes,
|
||||||
IFileAttributeConfig,
|
IFileAttributeConfig,
|
||||||
|
PendingType,
|
||||||
ProcessingType,
|
ProcessingType,
|
||||||
StatusSorter,
|
StatusSorter,
|
||||||
User,
|
User,
|
||||||
@ -46,6 +47,7 @@ import { annotationFilterChecker, RedactionFilterSorter, sortArray, sortByName }
|
|||||||
import { EditDossierDialogComponent } from '../shared-dossiers/dialogs/edit-dossier-dialog/edit-dossier-dialog.component';
|
import { EditDossierDialogComponent } from '../shared-dossiers/dialogs/edit-dossier-dialog/edit-dossier-dialog.component';
|
||||||
import { DossiersDialogService } from '../shared-dossiers/services/dossiers-dialog.service';
|
import { DossiersDialogService } from '../shared-dossiers/services/dossiers-dialog.service';
|
||||||
import { BulkActionsService } from './services/bulk-actions.service';
|
import { BulkActionsService } from './services/bulk-actions.service';
|
||||||
|
import { FileAttributesService } from '@services/entity-services/file-attributes.service';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class ConfigService {
|
export class ConfigService {
|
||||||
@ -67,6 +69,7 @@ export class ConfigService {
|
|||||||
private readonly _userPreferenceService: UserPreferenceService,
|
private readonly _userPreferenceService: UserPreferenceService,
|
||||||
private readonly _dossiersService: DossiersService,
|
private readonly _dossiersService: DossiersService,
|
||||||
private readonly _iqserPermissionsService: IqserPermissionsService,
|
private readonly _iqserPermissionsService: IqserPermissionsService,
|
||||||
|
private readonly _fileAttributesService: FileAttributesService,
|
||||||
) {
|
) {
|
||||||
const previousListingMode = this._userPreferenceService.getFilesListingMode();
|
const previousListingMode = this._userPreferenceService.getFilesListingMode();
|
||||||
const listingMode = previousListingMode ? previousListingMode : ListingModes.table;
|
const listingMode = previousListingMode ? previousListingMode : ListingModes.table;
|
||||||
@ -112,7 +115,7 @@ export class ConfigService {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
actionConfig(dossierId: string, disabled$: Observable<boolean>): List<ActionConfig> {
|
actionConfig(dossierId: string, disabled: boolean): List<ActionConfig> {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
id: 'editDossier',
|
id: 'editDossier',
|
||||||
@ -121,7 +124,8 @@ export class ConfigService {
|
|||||||
icon: 'iqser:edit',
|
icon: 'iqser:edit',
|
||||||
hide: !this.#currentUser.isManager && !this._iqserPermissionsService.has(Roles.dossiers.edit),
|
hide: !this.#currentUser.isManager && !this._iqserPermissionsService.has(Roles.dossiers.edit),
|
||||||
helpModeKey: 'edit_dossier',
|
helpModeKey: 'edit_dossier',
|
||||||
disabled$,
|
disableStopPropagation: this._fileAttributesService.isEditingFileAttribute(),
|
||||||
|
disabled,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
@ -184,6 +188,7 @@ export class ConfigService {
|
|||||||
const allDistinctPeople = new Set<string>();
|
const allDistinctPeople = new Set<string>();
|
||||||
const allDistinctNeedsWork = new Set<string>();
|
const allDistinctNeedsWork = new Set<string>();
|
||||||
const allDistinctProcessingTypes = new Set<ProcessingType>();
|
const allDistinctProcessingTypes = new Set<ProcessingType>();
|
||||||
|
const allDistinctPendingTypes = new Set<PendingType>();
|
||||||
|
|
||||||
const dynamicFilters = new Map<string, { type: FileAttributeConfigType; filterValue: Set<string> }>();
|
const dynamicFilters = new Map<string, { type: FileAttributeConfigType; filterValue: Set<string> }>();
|
||||||
|
|
||||||
@ -216,6 +221,7 @@ export class ConfigService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
allDistinctProcessingTypes.add(file.processingType);
|
allDistinctProcessingTypes.add(file.processingType);
|
||||||
|
allDistinctPendingTypes.add(file.pendingType);
|
||||||
|
|
||||||
// extract values for dynamic filters
|
// extract values for dynamic filters
|
||||||
fileAttributeConfigs.forEach(config => {
|
fileAttributeConfigs.forEach(config => {
|
||||||
@ -317,6 +323,14 @@ export class ConfigService {
|
|||||||
hide: true,
|
hide: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const pendingTypesFilters = [...allDistinctPendingTypes].map(item => new NestedFilter({ id: item, label: item }));
|
||||||
|
filterGroups.push({
|
||||||
|
slug: 'pendingTypeFilters',
|
||||||
|
filters: pendingTypesFilters,
|
||||||
|
checker: (file: File, filter: INestedFilter) => file.pendingType === filter.id,
|
||||||
|
hide: true,
|
||||||
|
});
|
||||||
|
|
||||||
dynamicFilters.forEach((value: { filterValue: Set<string>; type: FileAttributeConfigType }, filterKey: string) => {
|
dynamicFilters.forEach((value: { filterValue: Set<string>; type: FileAttributeConfigType }, filterKey: string) => {
|
||||||
const id = filterKey.split(':')[0];
|
const id = filterKey.split(':')[0];
|
||||||
const key = filterKey.split(':')[1];
|
const key = filterKey.split(':')[1];
|
||||||
|
|||||||
@ -41,7 +41,7 @@ import { Roles } from '@users/roles';
|
|||||||
import { UserPreferenceService } from '@users/user-preference.service';
|
import { UserPreferenceService } from '@users/user-preference.service';
|
||||||
import { convertFiles, Files, handleFileDrop } from '@utils/index';
|
import { convertFiles, Files, handleFileDrop } from '@utils/index';
|
||||||
import { merge, Observable } from 'rxjs';
|
import { merge, Observable } from 'rxjs';
|
||||||
import { filter, skip, switchMap, tap } from 'rxjs/operators';
|
import { filter, map, skip, switchMap, tap } from 'rxjs/operators';
|
||||||
import { ConfigService } from '../config.service';
|
import { ConfigService } from '../config.service';
|
||||||
import { BulkActionsService } from '../services/bulk-actions.service';
|
import { BulkActionsService } from '../services/bulk-actions.service';
|
||||||
import { DossiersCacheService } from '@services/dossiers/dossiers-cache.service';
|
import { DossiersCacheService } from '@services/dossiers/dossiers-cache.service';
|
||||||
@ -145,8 +145,9 @@ export default class DossierOverviewScreenComponent extends ListingComponent<Fil
|
|||||||
|
|
||||||
get #dossierFilesChange$() {
|
get #dossierFilesChange$() {
|
||||||
return this._dossiersService.dossierFileChanges$.pipe(
|
return this._dossiersService.dossierFileChanges$.pipe(
|
||||||
filter(dossierId => dossierId === this.dossierId && !!this._dossiersCacheService.get(dossierId)),
|
map(changes => changes[this.dossierId]),
|
||||||
switchMap(dossierId => this._filesService.loadAll(dossierId)),
|
filter(changes => !!changes && !!this._dossiersCacheService.get(this.dossierId)),
|
||||||
|
switchMap(changes => this._filesService.loadByIds({ [this.dossierId]: changes }).pipe(map(files => files[this.dossierId]))),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -113,11 +113,13 @@ export class BulkActionsService {
|
|||||||
async approve(files: File[]): Promise<void> {
|
async approve(files: File[]): Promise<void> {
|
||||||
this._loadingService.start();
|
this._loadingService.start();
|
||||||
const approvalResponse: ApproveResponse[] = await this._filesService.getApproveWarnings(files);
|
const approvalResponse: ApproveResponse[] = await this._filesService.getApproveWarnings(files);
|
||||||
this._loadingService.stop();
|
|
||||||
const hasWarnings = approvalResponse.some(response => response.hasWarnings);
|
const hasWarnings = approvalResponse.some(response => response.hasWarnings);
|
||||||
if (!hasWarnings) {
|
if (!hasWarnings) {
|
||||||
|
await firstValueFrom(this._filesService.loadAll(files[0].dossierId));
|
||||||
|
this._loadingService.stop();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
this._loadingService.stop();
|
||||||
|
|
||||||
const fileWarnings = approvalResponse
|
const fileWarnings = approvalResponse
|
||||||
.filter(response => response.hasWarnings)
|
.filter(response => response.hasWarnings)
|
||||||
|
|||||||
@ -15,7 +15,6 @@ import {
|
|||||||
import { DossierStatsService } from '@services/dossiers/dossier-stats.service';
|
import { DossierStatsService } from '@services/dossiers/dossier-stats.service';
|
||||||
import { DefaultColorsService } from '@services/entity-services/default-colors.service';
|
import { DefaultColorsService } from '@services/entity-services/default-colors.service';
|
||||||
import { DossierStatesMapService } from '@services/entity-services/dossier-states-map.service';
|
import { DossierStatesMapService } from '@services/entity-services/dossier-states-map.service';
|
||||||
import { PermissionsService } from '@services/permissions.service';
|
|
||||||
import { SharedDialogService } from '@shared/services/dialog.service';
|
import { SharedDialogService } from '@shared/services/dialog.service';
|
||||||
import { workflowFileStatusTranslations } from '@translations/file-status-translations';
|
import { workflowFileStatusTranslations } from '@translations/file-status-translations';
|
||||||
import { workloadTranslations } from '@translations/workload-translations';
|
import { workloadTranslations } from '@translations/workload-translations';
|
||||||
@ -40,7 +39,6 @@ export class ConfigService {
|
|||||||
private readonly _dossierStatsService: DossierStatsService,
|
private readonly _dossierStatsService: DossierStatsService,
|
||||||
private readonly _dossierStatesMapService: DossierStatesMapService,
|
private readonly _dossierStatesMapService: DossierStatesMapService,
|
||||||
private readonly _dialogService: SharedDialogService,
|
private readonly _dialogService: SharedDialogService,
|
||||||
private readonly _permissionsService: PermissionsService,
|
|
||||||
private readonly _defaultColorsService: DefaultColorsService,
|
private readonly _defaultColorsService: DefaultColorsService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
@ -67,9 +65,10 @@ export class ConfigService {
|
|||||||
{
|
{
|
||||||
label: _('dossier-listing.add-new'),
|
label: _('dossier-listing.add-new'),
|
||||||
action: () => this.#openAddDossierDialog(dossierTemplate.id),
|
action: () => this.#openAddDossierDialog(dossierTemplate.id),
|
||||||
hide: !this._permissionsService.canCreateDossier(dossierTemplate),
|
|
||||||
icon: 'iqser:plus',
|
icon: 'iqser:plus',
|
||||||
type: 'primary',
|
type: 'primary',
|
||||||
|
tooltip: dossierTemplate.isInactive ? _('dossier-listing.template-inactive') : null,
|
||||||
|
disabled: dossierTemplate.isInactive,
|
||||||
helpModeKey: 'new_dossier',
|
helpModeKey: 'new_dossier',
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
<section>
|
<section>
|
||||||
<iqser-page-header [buttonConfigs]="buttonConfigs" [helpModeKey]="'dossier'">
|
<iqser-page-header [buttonConfigs]="buttonConfigs()" [helpModeKey]="'dossier'">
|
||||||
<ng-container slot="beforeFilters">
|
<ng-container slot="beforeFilters">
|
||||||
<redaction-dossiers-type-switch></redaction-dossiers-type-switch>
|
<redaction-dossiers-type-switch></redaction-dossiers-type-switch>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
@ -18,7 +18,7 @@
|
|||||||
[noDataButtonLabel]="'dossier-listing.no-data.action' | translate"
|
[noDataButtonLabel]="'dossier-listing.no-data.action' | translate"
|
||||||
[noDataText]="'dossier-listing.no-data.title' | translate"
|
[noDataText]="'dossier-listing.no-data.title' | translate"
|
||||||
[noMatchText]="'dossier-listing.no-match.title' | translate"
|
[noMatchText]="'dossier-listing.no-match.title' | translate"
|
||||||
[showNoDataButton]="permissionsService.canCreateDossier(dossierTemplate)"
|
[showNoDataButton]="permissionsService.canCreateDossier(dossierTemplate())"
|
||||||
[tableColumnConfigs]="tableColumnConfigs"
|
[tableColumnConfigs]="tableColumnConfigs"
|
||||||
[rowIdPrefix]="'dossier'"
|
[rowIdPrefix]="'dossier'"
|
||||||
[namePropertyKey]="'dossierName'"
|
[namePropertyKey]="'dossierName'"
|
||||||
@ -33,7 +33,7 @@
|
|||||||
</section>
|
</section>
|
||||||
|
|
||||||
<ng-template #needsWorkFilterTemplate let-filter="filter">
|
<ng-template #needsWorkFilterTemplate let-filter="filter">
|
||||||
<redaction-type-filter [dossierTemplateId]="dossierTemplate.id" [filter]="filter"></redaction-type-filter>
|
<redaction-type-filter [dossierTemplateId]="dossierTemplate().id" [filter]="filter"></redaction-type-filter>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
|
|
||||||
<ng-template #tableItemTemplate let-dossier="entity">
|
<ng-template #tableItemTemplate let-dossier="entity">
|
||||||
|
|||||||
@ -1,8 +1,7 @@
|
|||||||
import { ChangeDetectionStrategy, Component, OnInit, TemplateRef, ViewChild } from '@angular/core';
|
import { ChangeDetectionStrategy, Component, computed, OnInit, TemplateRef, ViewChild } from '@angular/core';
|
||||||
import { Dossier, DOSSIER_TEMPLATE_ID, DossierTemplate } from '@red/domain';
|
import { Dossier, DOSSIER_TEMPLATE_ID } from '@red/domain';
|
||||||
import { PermissionsService } from '@services/permissions.service';
|
import { PermissionsService } from '@services/permissions.service';
|
||||||
import {
|
import {
|
||||||
ButtonConfig,
|
|
||||||
HasScrollbarDirective,
|
HasScrollbarDirective,
|
||||||
IqserListingModule,
|
IqserListingModule,
|
||||||
ListingComponent,
|
ListingComponent,
|
||||||
@ -25,6 +24,7 @@ import { DossiersListingDetailsComponent } from '../components/dossiers-listing-
|
|||||||
import { AsyncPipe, NgIf } from '@angular/common';
|
import { AsyncPipe, NgIf } from '@angular/common';
|
||||||
import { TypeFilterComponent } from '@shared/components/type-filter/type-filter.component';
|
import { TypeFilterComponent } from '@shared/components/type-filter/type-filter.component';
|
||||||
import { TableItemComponent } from '../components/table-item/table-item.component';
|
import { TableItemComponent } from '../components/table-item/table-item.component';
|
||||||
|
import { toSignal } from '@angular/core/rxjs-interop';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
templateUrl: './dossiers-listing-screen.component.html',
|
templateUrl: './dossiers-listing-screen.component.html',
|
||||||
@ -47,8 +47,10 @@ import { TableItemComponent } from '../components/table-item/table-item.componen
|
|||||||
export class DossiersListingScreenComponent extends ListingComponent<Dossier> implements OnInit, OnAttach {
|
export class DossiersListingScreenComponent extends ListingComponent<Dossier> implements OnInit, OnAttach {
|
||||||
readonly tableColumnConfigs = this._configService.tableConfig;
|
readonly tableColumnConfigs = this._configService.tableConfig;
|
||||||
readonly tableHeaderLabel = _('dossier-listing.table-header.title');
|
readonly tableHeaderLabel = _('dossier-listing.table-header.title');
|
||||||
readonly buttonConfigs: ButtonConfig[];
|
readonly dossierTemplateId = this.router.routerState.snapshot.root.firstChild.firstChild.paramMap.get(DOSSIER_TEMPLATE_ID);
|
||||||
readonly dossierTemplate: DossierTemplate;
|
readonly dossierTemplates = toSignal(this.dossierTemplatesService.all$);
|
||||||
|
readonly dossierTemplate = computed(() => this.dossierTemplates().find(template => this.dossierTemplateId === template.id));
|
||||||
|
readonly buttonConfigs = computed(() => this._configService.buttonsConfig(this.dossierTemplate()));
|
||||||
readonly computeFilters$ = this._activeDossiersService.all$.pipe(tap(() => this._computeAllFilters()));
|
readonly computeFilters$ = this._activeDossiersService.all$.pipe(tap(() => this._computeAllFilters()));
|
||||||
@ViewChild('needsWorkFilterTemplate', {
|
@ViewChild('needsWorkFilterTemplate', {
|
||||||
read: TemplateRef,
|
read: TemplateRef,
|
||||||
@ -68,20 +70,19 @@ export class DossiersListingScreenComponent extends ListingComponent<Dossier> im
|
|||||||
readonly dossierTemplatesService: DossierTemplatesService,
|
readonly dossierTemplatesService: DossierTemplatesService,
|
||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
const dossierTemplateId = router.routerState.snapshot.root.firstChild.firstChild.paramMap.get(DOSSIER_TEMPLATE_ID);
|
this.entitiesService.setEntities(this._activeDossiersService.all.filter(d => d.dossierTemplateId === this.dossierTemplate().id));
|
||||||
this.dossierTemplate = dossierTemplatesService.find(dossierTemplateId);
|
|
||||||
this.buttonConfigs = this._configService.buttonsConfig(this.dossierTemplate);
|
|
||||||
this.entitiesService.setEntities(this._activeDossiersService.all.filter(d => d.dossierTemplateId === this.dossierTemplate.id));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
openAddDossierDialog(): void {
|
openAddDossierDialog(): void {
|
||||||
this._dialogService.openDialog('addDossier', { dossierTemplateId: this.dossierTemplate.id });
|
this._dialogService.openDialog('addDossier', { dossierTemplateId: this.dossierTemplate().id });
|
||||||
}
|
}
|
||||||
|
|
||||||
async ngOnInit(): Promise<void> {
|
async ngOnInit(): Promise<void> {
|
||||||
await this._userPreferenceService.saveLastDossierTemplate(this.dossierTemplate.id);
|
await this._userPreferenceService.saveLastDossierTemplate(this.dossierTemplate().id);
|
||||||
this.addSubscription = this._activeDossiersService.all$
|
this.addSubscription = this._activeDossiersService.all$
|
||||||
.pipe(tap(dossiers => this.entitiesService.setEntities(dossiers.filter(d => d.dossierTemplateId === this.dossierTemplate.id))))
|
.pipe(
|
||||||
|
tap(dossiers => this.entitiesService.setEntities(dossiers.filter(d => d.dossierTemplateId === this.dossierTemplate().id))),
|
||||||
|
)
|
||||||
.subscribe();
|
.subscribe();
|
||||||
this._loadingService.stop();
|
this._loadingService.stop();
|
||||||
}
|
}
|
||||||
@ -95,7 +96,7 @@ export class DossiersListingScreenComponent extends ListingComponent<Dossier> im
|
|||||||
const filterGroups = this._configService.filterGroups(
|
const filterGroups = this._configService.filterGroups(
|
||||||
this.entitiesService.all,
|
this.entitiesService.all,
|
||||||
this._needsWorkFilterTemplate,
|
this._needsWorkFilterTemplate,
|
||||||
this.dossierTemplate.id,
|
this.dossierTemplate().id,
|
||||||
);
|
);
|
||||||
this.filterService.addFilterGroups(filterGroups);
|
this.filterService.addFilterGroups(filterGroups);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -126,21 +126,21 @@ export class AnnotationActionsComponent {
|
|||||||
|
|
||||||
async acceptRecommendation(): Promise<void> {
|
async acceptRecommendation(): Promise<void> {
|
||||||
const annotations = untracked(this.annotations);
|
const annotations = untracked(this.annotations);
|
||||||
await this.annotationActionsService.convertRecommendationToAnnotation(annotations);
|
await this.annotationActionsService.convertRecommendationToAnnotation(annotations, 'accept');
|
||||||
}
|
}
|
||||||
|
|
||||||
hideAnnotation() {
|
hideAnnotation() {
|
||||||
const viewerAnnotations = untracked(this.viewerAnnotations);
|
const viewerAnnotations = untracked(this.viewerAnnotations);
|
||||||
this._annotationManager.hide(viewerAnnotations);
|
this._annotationManager.hide(viewerAnnotations);
|
||||||
this._annotationManager.deselect();
|
this._annotationManager.deselect();
|
||||||
this._annotationManager.addToHidden(viewerAnnotations[0].Id);
|
viewerAnnotations.forEach(a => this._annotationManager.addToHidden(a.Id));
|
||||||
}
|
}
|
||||||
|
|
||||||
showAnnotation() {
|
showAnnotation() {
|
||||||
const viewerAnnotations = untracked(this.viewerAnnotations);
|
const viewerAnnotations = untracked(this.viewerAnnotations);
|
||||||
this._annotationManager.show(viewerAnnotations);
|
this._annotationManager.show(viewerAnnotations);
|
||||||
this._annotationManager.deselect();
|
this._annotationManager.deselect();
|
||||||
this._annotationManager.removeFromHidden(viewerAnnotations[0].Id);
|
viewerAnnotations.forEach(a => this._annotationManager.removeFromHidden(a.Id));
|
||||||
}
|
}
|
||||||
|
|
||||||
resize() {
|
resize() {
|
||||||
|
|||||||
@ -89,7 +89,7 @@ export class AnnotationDetailsComponent {
|
|||||||
icon: 'red:rule',
|
icon: 'red:rule',
|
||||||
description: _('annotation-engines.rule'),
|
description: _('annotation-engines.rule'),
|
||||||
show: isBasedOn(annotation, LogEntryEngines.RULE),
|
show: isBasedOn(annotation, LogEntryEngines.RULE),
|
||||||
translateParams: { rule: annotation.legalBasisValue || '' },
|
translateParams: { rule: annotation.legalBasisValue === 'n-a' ? '' : annotation.legalBasisValue || '' },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: 'red:import_redactions',
|
icon: 'red:import_redactions',
|
||||||
|
|||||||
@ -6,10 +6,9 @@
|
|||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
@include common-mixins.scroll-bar;
|
@include common-mixins.scroll-bar;
|
||||||
|
|
||||||
&.has-scrollbar:hover redaction-annotation-wrapper::ng-deep,
|
redaction-annotation-wrapper.documine-wrapper {
|
||||||
&::ng-deep.documine-wrapper {
|
&::ng-deep.annotation {
|
||||||
.annotation {
|
padding-right: 10px;
|
||||||
padding-right: 5px;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -15,6 +15,8 @@ import { JsonPipe, NgForOf, NgIf } from '@angular/common';
|
|||||||
import { HighlightsSeparatorComponent } from '../highlights-separator/highlights-separator.component';
|
import { HighlightsSeparatorComponent } from '../highlights-separator/highlights-separator.component';
|
||||||
import { AnnotationWrapperComponent } from '../annotation-wrapper/annotation-wrapper.component';
|
import { AnnotationWrapperComponent } from '../annotation-wrapper/annotation-wrapper.component';
|
||||||
import { AnnotationReferencesListComponent } from '../annotation-references-list/annotation-references-list.component';
|
import { AnnotationReferencesListComponent } from '../annotation-references-list/annotation-references-list.component';
|
||||||
|
import { Clipboard } from '@angular/cdk/clipboard';
|
||||||
|
import { isTargetInput } from '@utils/functions';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'redaction-annotations-list',
|
selector: 'redaction-annotations-list',
|
||||||
@ -54,16 +56,20 @@ export class AnnotationsListComponent extends HasScrollbarDirective {
|
|||||||
private readonly _annotationManager: REDAnnotationManager,
|
private readonly _annotationManager: REDAnnotationManager,
|
||||||
private readonly _listingService: AnnotationsListingService,
|
private readonly _listingService: AnnotationsListingService,
|
||||||
readonly annotationReferencesService: AnnotationReferencesService,
|
readonly annotationReferencesService: AnnotationReferencesService,
|
||||||
|
private readonly clipboard: Clipboard,
|
||||||
) {
|
) {
|
||||||
super(_elementRef);
|
super(_elementRef);
|
||||||
}
|
}
|
||||||
|
|
||||||
annotationClicked(annotation: AnnotationWrapper, $event: MouseEvent): void {
|
annotationClicked(annotation: AnnotationWrapper, $event: MouseEvent): void {
|
||||||
|
if ($event.ctrlKey && $event.altKey) {
|
||||||
|
this.clipboard.copy(annotation.id);
|
||||||
|
}
|
||||||
if (this._userPreferenceService.isIqserDevMode) {
|
if (this._userPreferenceService.isIqserDevMode) {
|
||||||
console.log('Selected Annotation:', annotation);
|
console.log('Selected Annotation:', annotation);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (($event?.target as IqserEventTarget)?.localName === 'input') {
|
if (isTargetInput($event)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,10 +1,10 @@
|
|||||||
import { Component, input, Input } from '@angular/core';
|
import { Component, input, Input } from '@angular/core';
|
||||||
import { firstValueFrom } from 'rxjs';
|
import { firstValueFrom } from 'rxjs';
|
||||||
import { Dossier, File } from '@red/domain';
|
import { Dossier, File } from '@red/domain';
|
||||||
import { ComponentLogService } from '@services/files/component-log.service';
|
|
||||||
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 { MatMenu, MatMenuItem, MatMenuTrigger } from '@angular/material/menu';
|
import { MatMenu, MatMenuItem, MatMenuTrigger } from '@angular/material/menu';
|
||||||
|
import { ComponentLogService } from '@services/entity-services/component-log.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'redaction-documine-export',
|
selector: 'redaction-documine-export',
|
||||||
|
|||||||
@ -20,6 +20,12 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
|
|
||||||
|
span {
|
||||||
|
word-wrap: break-word;
|
||||||
|
word-break: break-word;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.actions {
|
.actions {
|
||||||
|
|||||||
@ -88,7 +88,7 @@ export class EditableStructuredComponentValueComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
this.deselectLast.emit();
|
this.deselectLast.emit();
|
||||||
this.selected = true;
|
this.selected = true;
|
||||||
this._state.componentReferenceIds = this.#getUniqueReferencesIds(this.currentEntry().componentValues);
|
this._state.componentReferenceIds.set(this.#getUniqueReferencesIds(this.currentEntry().componentValues));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -104,7 +104,7 @@ export class EditableStructuredComponentValueComponent implements OnInit {
|
|||||||
$event?.stopImmediatePropagation();
|
$event?.stopImmediatePropagation();
|
||||||
this.selected = false;
|
this.selected = false;
|
||||||
this.editing = false;
|
this.editing = false;
|
||||||
this._state.componentReferenceIds = null;
|
this._state.componentReferenceIds.set(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
cancel($event?: MouseEvent) {
|
cancel($event?: MouseEvent) {
|
||||||
|
|||||||
@ -4,7 +4,6 @@ import {
|
|||||||
Component,
|
Component,
|
||||||
computed,
|
computed,
|
||||||
ElementRef,
|
ElementRef,
|
||||||
HostListener,
|
|
||||||
Input,
|
Input,
|
||||||
NgZone,
|
NgZone,
|
||||||
OnDestroy,
|
OnDestroy,
|
||||||
@ -19,7 +18,6 @@ import {
|
|||||||
getConfig,
|
getConfig,
|
||||||
HelpModeService,
|
HelpModeService,
|
||||||
IqserAllowDirective,
|
IqserAllowDirective,
|
||||||
IqserDialog,
|
|
||||||
IqserPermissionsService,
|
IqserPermissionsService,
|
||||||
isIqserDevMode,
|
isIqserDevMode,
|
||||||
LoadingService,
|
LoadingService,
|
||||||
@ -51,6 +49,7 @@ import { ALL_HOTKEYS } from '../../utils/constants';
|
|||||||
import { AnnotationDrawService } from '../../../pdf-viewer/services/annotation-draw.service';
|
import { AnnotationDrawService } from '../../../pdf-viewer/services/annotation-draw.service';
|
||||||
import { FileManagementService } from '@services/files/file-management.service';
|
import { FileManagementService } from '@services/files/file-management.service';
|
||||||
import { MatDialog } from '@angular/material/dialog';
|
import { MatDialog } from '@angular/material/dialog';
|
||||||
|
import { isTargetInput, isTargetTextArea } from '@utils/functions';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'redaction-file-header',
|
selector: 'redaction-file-header',
|
||||||
@ -105,6 +104,7 @@ export class FileHeaderComponent implements OnInit, AfterViewInit, OnDetach, OnD
|
|||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
document.documentElement.addEventListener('fullscreenchange', this.fullscreenListener);
|
document.documentElement.addEventListener('fullscreenchange', this.fullscreenListener);
|
||||||
|
this._pdf.instance.UI.iframeWindow.addEventListener('keyup', this.handleKeyEvent);
|
||||||
}
|
}
|
||||||
|
|
||||||
ngAfterViewInit() {
|
ngAfterViewInit() {
|
||||||
@ -116,10 +116,12 @@ export class FileHeaderComponent implements OnInit, AfterViewInit, OnDetach, OnD
|
|||||||
|
|
||||||
ngOnDetach() {
|
ngOnDetach() {
|
||||||
document.documentElement.removeEventListener('fullscreenchange', this.fullscreenListener);
|
document.documentElement.removeEventListener('fullscreenchange', this.fullscreenListener);
|
||||||
|
this._pdf.instance.UI.iframeWindow.removeEventListener('keyup', this.handleKeyEvent);
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnDestroy() {
|
ngOnDestroy() {
|
||||||
document.documentElement.removeEventListener('fullscreenchange', this.fullscreenListener);
|
document.documentElement.removeEventListener('fullscreenchange', this.fullscreenListener);
|
||||||
|
this._pdf.instance.UI.iframeWindow.removeEventListener('keyup', this.handleKeyEvent);
|
||||||
}
|
}
|
||||||
|
|
||||||
async downloadOriginalFile({ cacheIdentifier, dossierId, fileId, filename }: File) {
|
async downloadOriginalFile({ cacheIdentifier, dossierId, fileId, filename }: File) {
|
||||||
@ -177,7 +179,7 @@ export class FileHeaderComponent implements OnInit, AfterViewInit, OnDetach, OnD
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@HostListener('document:keyup', ['$event'])
|
@Bind()
|
||||||
handleKeyEvent($event: KeyboardEvent) {
|
handleKeyEvent($event: KeyboardEvent) {
|
||||||
if (this._router.url.indexOf('/file/') < 0) {
|
if (this._router.url.indexOf('/file/') < 0) {
|
||||||
return;
|
return;
|
||||||
@ -208,19 +210,20 @@ export class FileHeaderComponent implements OnInit, AfterViewInit, OnDetach, OnD
|
|||||||
this._changeRef.markForCheck();
|
this._changeRef.markForCheck();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($event.key === 'F5') {
|
||||||
|
window.location.reload();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isTargetInput($event) || isTargetTextArea($event)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (!$event.ctrlKey && !$event.metaKey && ['f', 'F'].includes($event.key)) {
|
if (!$event.ctrlKey && !$event.metaKey && ['f', 'F'].includes($event.key)) {
|
||||||
// if you type in an input, don't toggle full-screen
|
|
||||||
if ($event.target instanceof HTMLInputElement || $event.target instanceof HTMLTextAreaElement) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.toggleFullScreen();
|
this.toggleFullScreen();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (['h', 'H'].includes($event.key)) {
|
if (['h', 'H'].includes($event.key)) {
|
||||||
if ($event.target instanceof HTMLInputElement || $event.target instanceof HTMLTextAreaElement) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this._ngZone.run(() => {
|
this._ngZone.run(() => {
|
||||||
window.focus();
|
window.focus();
|
||||||
this._helpModeService.activateHelpMode(false);
|
this._helpModeService.activateHelpMode(false);
|
||||||
|
|||||||
@ -146,7 +146,7 @@
|
|||||||
id="annotations-list"
|
id="annotations-list"
|
||||||
tabindex="1"
|
tabindex="1"
|
||||||
>
|
>
|
||||||
<ng-container *ngIf="pdf.currentPage() && !displayedAnnotations.get(pdf.currentPage())?.length">
|
<ng-container *ngIf="pdf.currentPage() && !displayedAnnotations().get(pdf.currentPage())?.length">
|
||||||
<iqser-empty-state
|
<iqser-empty-state
|
||||||
[horizontalPadding]="24"
|
[horizontalPadding]="24"
|
||||||
[text]="'file-preview.no-data.title' | translate"
|
[text]="'file-preview.no-data.title' | translate"
|
||||||
|
|||||||
@ -8,6 +8,7 @@ import {
|
|||||||
HostListener,
|
HostListener,
|
||||||
OnDestroy,
|
OnDestroy,
|
||||||
OnInit,
|
OnInit,
|
||||||
|
signal,
|
||||||
TemplateRef,
|
TemplateRef,
|
||||||
untracked,
|
untracked,
|
||||||
viewChild,
|
viewChild,
|
||||||
@ -58,7 +59,7 @@ import { PageExclusionComponent } from '../page-exclusion/page-exclusion.compone
|
|||||||
import { PagesComponent } from '../pages/pages.component';
|
import { PagesComponent } from '../pages/pages.component';
|
||||||
import { ReadonlyBannerComponent } from '../readonly-banner/readonly-banner.component';
|
import { ReadonlyBannerComponent } from '../readonly-banner/readonly-banner.component';
|
||||||
import { DocumentInfoComponent } from '../document-info/document-info.component';
|
import { DocumentInfoComponent } from '../document-info/document-info.component';
|
||||||
import { getLast } from '@utils/functions';
|
import { getLast, isTargetInput } from '@utils/functions';
|
||||||
|
|
||||||
const COMMAND_KEY_ARRAY = ['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown', 'Escape'];
|
const COMMAND_KEY_ARRAY = ['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown', 'Escape'];
|
||||||
const ALL_HOTKEY_ARRAY = ['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown'];
|
const ALL_HOTKEY_ARRAY = ['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown'];
|
||||||
@ -107,8 +108,8 @@ export class FileWorkloadComponent extends AutoUnsubscribe implements OnInit, On
|
|||||||
const file = this.state.file();
|
const file = this.state.file();
|
||||||
return this.isDocumine && file.excludedFromAutomaticAnalysis && file.workflowStatus !== WorkflowFileStatuses.APPROVED;
|
return this.isDocumine && file.excludedFromAutomaticAnalysis && file.workflowStatus !== WorkflowFileStatuses.APPROVED;
|
||||||
});
|
});
|
||||||
protected displayedAnnotations = new Map<number, AnnotationWrapper[]>();
|
protected displayedAnnotations = signal(new Map<number, AnnotationWrapper[]>());
|
||||||
readonly activeAnnotations = computed(() => this.displayedAnnotations.get(this.pdf.currentPage()) || []);
|
readonly activeAnnotations = computed(() => this.displayedAnnotations().get(this.pdf.currentPage()) || []);
|
||||||
protected displayedPages: number[] = [];
|
protected displayedPages: number[] = [];
|
||||||
protected pagesPanelActive = true;
|
protected pagesPanelActive = true;
|
||||||
protected enabledFilters = [];
|
protected enabledFilters = [];
|
||||||
@ -249,11 +250,7 @@ export class FileWorkloadComponent extends AutoUnsubscribe implements OnInit, On
|
|||||||
handleKeyEvent($event: KeyboardEvent): void {
|
handleKeyEvent($event: KeyboardEvent): void {
|
||||||
const multiSelectServiceInactive = untracked(this.multiSelectService.inactive);
|
const multiSelectServiceInactive = untracked(this.multiSelectService.inactive);
|
||||||
|
|
||||||
if (
|
if (!ALL_HOTKEY_ARRAY.includes($event.key) || this._dialog.openDialogs.length || isTargetInput($event)) {
|
||||||
!ALL_HOTKEY_ARRAY.includes($event.key) ||
|
|
||||||
this._dialog.openDialogs.length ||
|
|
||||||
($event.target as IqserEventTarget).localName === 'input'
|
|
||||||
) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -308,7 +305,11 @@ export class FileWorkloadComponent extends AutoUnsubscribe implements OnInit, On
|
|||||||
@Debounce()
|
@Debounce()
|
||||||
scrollToSelectedAnnotation(): void {
|
scrollToSelectedAnnotation(): void {
|
||||||
const annotationsElement = untracked(this._annotationsElement);
|
const annotationsElement = untracked(this._annotationsElement);
|
||||||
if (this.listingService.selected.length === 0 || !annotationsElement) {
|
if (
|
||||||
|
this.listingService.selected.length === 0 ||
|
||||||
|
!annotationsElement ||
|
||||||
|
this.activeAnnotations().length === this.listingService.selected.length
|
||||||
|
) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const elements: HTMLElement[] = annotationsElement.nativeElement.querySelectorAll(
|
const elements: HTMLElement[] = annotationsElement.nativeElement.querySelectorAll(
|
||||||
@ -336,7 +337,7 @@ export class FileWorkloadComponent extends AutoUnsubscribe implements OnInit, On
|
|||||||
}
|
}
|
||||||
|
|
||||||
preventKeyDefault($event: KeyboardEvent): void {
|
preventKeyDefault($event: KeyboardEvent): void {
|
||||||
if (COMMAND_KEY_ARRAY.includes($event.key) && !(($event.target as any).localName === 'input')) {
|
if (COMMAND_KEY_ARRAY.includes($event.key) && !isTargetInput($event)) {
|
||||||
$event.preventDefault();
|
$event.preventDefault();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -362,11 +363,11 @@ export class FileWorkloadComponent extends AutoUnsubscribe implements OnInit, On
|
|||||||
if ($event.key === 'ArrowDown') {
|
if ($event.key === 'ArrowDown') {
|
||||||
const nextPage = this.#nextPageWithAnnotations();
|
const nextPage = this.#nextPageWithAnnotations();
|
||||||
|
|
||||||
return this.listingService.selectAnnotations(this.displayedAnnotations.get(nextPage)[0]);
|
return this.listingService.selectAnnotations(this.displayedAnnotations().get(nextPage)[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
const prevPage = this.#prevPageWithAnnotations();
|
const prevPage = this.#prevPageWithAnnotations();
|
||||||
const prevPageAnnotations = this.displayedAnnotations.get(prevPage);
|
const prevPageAnnotations = this.displayedAnnotations().get(prevPage);
|
||||||
|
|
||||||
return this.listingService.selectAnnotations(getLast(prevPageAnnotations));
|
return this.listingService.selectAnnotations(getLast(prevPageAnnotations));
|
||||||
}
|
}
|
||||||
@ -375,7 +376,7 @@ export class FileWorkloadComponent extends AutoUnsubscribe implements OnInit, On
|
|||||||
const pageIdx = this.displayedPages.indexOf(page);
|
const pageIdx = this.displayedPages.indexOf(page);
|
||||||
const nextPageIdx = pageIdx + 1;
|
const nextPageIdx = pageIdx + 1;
|
||||||
const previousPageIdx = pageIdx - 1;
|
const previousPageIdx = pageIdx - 1;
|
||||||
const annotationsOnPage = this.displayedAnnotations.get(page);
|
const annotationsOnPage = this.displayedAnnotations().get(page);
|
||||||
const idx = annotationsOnPage.findIndex(a => a.id === this._firstSelectedAnnotation.id);
|
const idx = annotationsOnPage.findIndex(a => a.id === this._firstSelectedAnnotation.id);
|
||||||
|
|
||||||
if ($event.key === 'ArrowDown') {
|
if ($event.key === 'ArrowDown') {
|
||||||
@ -385,7 +386,7 @@ export class FileWorkloadComponent extends AutoUnsubscribe implements OnInit, On
|
|||||||
} else if (nextPageIdx < this.displayedPages.length) {
|
} else if (nextPageIdx < this.displayedPages.length) {
|
||||||
// If not last page
|
// If not last page
|
||||||
for (let i = nextPageIdx; i < this.displayedPages.length; i++) {
|
for (let i = nextPageIdx; i < this.displayedPages.length; i++) {
|
||||||
const nextPageAnnotations = this.displayedAnnotations.get(this.displayedPages[i]);
|
const nextPageAnnotations = this.displayedAnnotations().get(this.displayedPages[i]);
|
||||||
if (nextPageAnnotations) {
|
if (nextPageAnnotations) {
|
||||||
this.listingService.selectAnnotations(nextPageAnnotations[0]);
|
this.listingService.selectAnnotations(nextPageAnnotations[0]);
|
||||||
break;
|
break;
|
||||||
@ -403,7 +404,7 @@ export class FileWorkloadComponent extends AutoUnsubscribe implements OnInit, On
|
|||||||
if (pageIdx) {
|
if (pageIdx) {
|
||||||
// If not first page
|
// If not first page
|
||||||
for (let i = previousPageIdx; i >= 0; i--) {
|
for (let i = previousPageIdx; i >= 0; i--) {
|
||||||
const prevPageAnnotations = this.displayedAnnotations.get(this.displayedPages[i]);
|
const prevPageAnnotations = this.displayedAnnotations().get(this.displayedPages[i]);
|
||||||
if (prevPageAnnotations) {
|
if (prevPageAnnotations) {
|
||||||
this.listingService.selectAnnotations(getLast(prevPageAnnotations));
|
this.listingService.selectAnnotations(getLast(prevPageAnnotations));
|
||||||
break;
|
break;
|
||||||
@ -451,8 +452,8 @@ export class FileWorkloadComponent extends AutoUnsubscribe implements OnInit, On
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.displayedAnnotations = this._annotationProcessingService.filterAndGroupAnnotations(annotations, primary, secondary);
|
this.displayedAnnotations.set(this._annotationProcessingService.filterAndGroupAnnotations(annotations, primary, secondary));
|
||||||
const pagesThatDisplayAnnotations = [...this.displayedAnnotations.keys()];
|
const pagesThatDisplayAnnotations = [...this.displayedAnnotations().keys()];
|
||||||
this.enabledFilters = this.filterService.enabledFlatFilters;
|
this.enabledFilters = this.filterService.enabledFlatFilters;
|
||||||
if (this.enabledFilters.some(f => f.id === 'pages-without-annotations')) {
|
if (this.enabledFilters.some(f => f.id === 'pages-without-annotations')) {
|
||||||
if (this.enabledFilters.length === 1 && !onlyPageWithAnnotations) {
|
if (this.enabledFilters.length === 1 && !onlyPageWithAnnotations) {
|
||||||
@ -461,7 +462,7 @@ export class FileWorkloadComponent extends AutoUnsubscribe implements OnInit, On
|
|||||||
} else {
|
} else {
|
||||||
this.#setDisplayedPages([]);
|
this.#setDisplayedPages([]);
|
||||||
}
|
}
|
||||||
this.displayedAnnotations.clear();
|
this.displayedAnnotations().clear();
|
||||||
} else if (this.enabledFilters.length || onlyPageWithAnnotations || componentReferenceIds) {
|
} else if (this.enabledFilters.length || onlyPageWithAnnotations || componentReferenceIds) {
|
||||||
this.#setDisplayedPages(pagesThatDisplayAnnotations);
|
this.#setDisplayedPages(pagesThatDisplayAnnotations);
|
||||||
} else {
|
} else {
|
||||||
@ -469,7 +470,7 @@ export class FileWorkloadComponent extends AutoUnsubscribe implements OnInit, On
|
|||||||
}
|
}
|
||||||
this.displayedPages.sort((a, b) => a - b);
|
this.displayedPages.sort((a, b) => a - b);
|
||||||
|
|
||||||
return this.displayedAnnotations;
|
return this.displayedAnnotations();
|
||||||
}
|
}
|
||||||
|
|
||||||
#selectFirstAnnotationOnCurrentPageIfNecessary() {
|
#selectFirstAnnotationOnCurrentPageIfNecessary() {
|
||||||
@ -521,7 +522,7 @@ export class FileWorkloadComponent extends AutoUnsubscribe implements OnInit, On
|
|||||||
const currentPage = untracked(this.pdf.currentPage);
|
const currentPage = untracked(this.pdf.currentPage);
|
||||||
let idx = 0;
|
let idx = 0;
|
||||||
for (const page of this.displayedPages) {
|
for (const page of this.displayedPages) {
|
||||||
if (page > currentPage && this.displayedAnnotations.get(page)) {
|
if (page > currentPage && this.displayedAnnotations().get(page)) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
++idx;
|
++idx;
|
||||||
@ -534,7 +535,7 @@ export class FileWorkloadComponent extends AutoUnsubscribe implements OnInit, On
|
|||||||
let idx = this.displayedPages.length - 1;
|
let idx = this.displayedPages.length - 1;
|
||||||
const reverseDisplayedPages = [...this.displayedPages].reverse();
|
const reverseDisplayedPages = [...this.displayedPages].reverse();
|
||||||
for (const page of reverseDisplayedPages) {
|
for (const page of reverseDisplayedPages) {
|
||||||
if (page < currentPage && this.displayedAnnotations.get(page)) {
|
if (page < currentPage && this.displayedAnnotations().get(page)) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
--idx;
|
--idx;
|
||||||
@ -588,4 +589,16 @@ export class FileWorkloadComponent extends AutoUnsubscribe implements OnInit, On
|
|||||||
this.displayedPages.every((value, index) => value === newDisplayedPages[index])
|
this.displayedPages.every((value, index) => value === newDisplayedPages[index])
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@HostListener('click', ['$event'])
|
||||||
|
clickInsideWorkloadView($event: MouseEvent) {
|
||||||
|
$event?.stopPropagation();
|
||||||
|
}
|
||||||
|
|
||||||
|
@HostListener('document: click')
|
||||||
|
clickOutsideWorkloadView() {
|
||||||
|
if (this.multiSelectService.active() && !this._dialog.openDialogs.length) {
|
||||||
|
this.multiSelectService.deactivate();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -34,12 +34,14 @@ export class PagesComponent implements AfterViewInit {
|
|||||||
// TODO: looks like this is not working
|
// TODO: looks like this is not working
|
||||||
scrollToLastViewedPage() {
|
scrollToLastViewedPage() {
|
||||||
const currentPdfPage = this._pdf.currentPage();
|
const currentPdfPage = this._pdf.currentPage();
|
||||||
scrollIntoView(document.getElementById(`quick-nav-page-${currentPdfPage}`), {
|
const currentElement = document.getElementById(`quick-nav-page-${currentPdfPage}`);
|
||||||
behavior: 'smooth',
|
if (currentElement)
|
||||||
scrollMode: 'if-needed',
|
scrollIntoView(currentElement, {
|
||||||
block: 'start',
|
behavior: 'smooth',
|
||||||
inline: 'start',
|
scrollMode: 'if-needed',
|
||||||
});
|
block: 'start',
|
||||||
|
inline: 'start',
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
pageSelectedByClick($event: number): void {
|
pageSelectedByClick($event: number): void {
|
||||||
|
|||||||
@ -1,24 +1,35 @@
|
|||||||
<div *ngIf="_state.file() as file" class="justify-center banner read-only flex">
|
@if (_state.file(); as file) {
|
||||||
<div *ngIf="file.isFullProcessing" class="ocr-indicator">
|
<div class="justify-center banner read-only flex">
|
||||||
<ng-container *ngIf="file.isOcrProcessing; else defaultProcessing">
|
@if (file.isFullProcessing) {
|
||||||
<redaction-ocr-progress-bar
|
<div class="ocr-indicator">
|
||||||
[numberOfOCRedPages]="file.numberOfOCRedPages"
|
@if (file.isOcrProcessing) {
|
||||||
[numberOfPagesToOCR]="file.numberOfPagesToOCR"
|
<redaction-ocr-progress-bar
|
||||||
[showLabel]="true"
|
[numberOfOCRedPages]="file.numberOfOCRedPages"
|
||||||
></redaction-ocr-progress-bar>
|
[numberOfPagesToOCR]="file.numberOfPagesToOCR"
|
||||||
</ng-container>
|
[showLabel]="true"
|
||||||
|
></redaction-ocr-progress-bar>
|
||||||
|
} @else {
|
||||||
|
<span [translate]="'processing.basic'" class="read-only-text"></span>
|
||||||
|
<mat-progress-bar class="white ml-8 w-100" mode="indeterminate"></mat-progress-bar>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
<ng-template #defaultProcessing>
|
<div class="flex-center">
|
||||||
<span [translate]="'processing.basic'" class="read-only-text"></span>
|
@if (!customTranslation) {
|
||||||
<mat-progress-bar class="white ml-8 w-100" mode="indeterminate"></mat-progress-bar>
|
<mat-icon
|
||||||
</ng-template>
|
[matTooltip]="file.isFullProcessing ? ('readonly' | translate) : null"
|
||||||
|
[matTooltipPosition]="'above'"
|
||||||
|
class="primary-white"
|
||||||
|
svgIcon="red:read-only"
|
||||||
|
></mat-icon>
|
||||||
|
@if (!file.isFullProcessing) {
|
||||||
|
<span [translate]="_state.dossier().isActive ? 'readonly' : 'readonly-archived'" class="read-only-text"></span>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@if (customTranslation) {
|
||||||
|
<span [translate]="customTranslation" class="read-only-text"></span>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
}
|
||||||
<div class="flex-center">
|
|
||||||
<ng-container *ngIf="!customTranslation">
|
|
||||||
<mat-icon class="primary-white" svgIcon="red:read-only"></mat-icon>
|
|
||||||
<span [translate]="_state.dossier().isActive ? 'readonly' : 'readonly-archived'" class="read-only-text"></span>
|
|
||||||
</ng-container>
|
|
||||||
<span *ngIf="customTranslation" [translate]="customTranslation" class="read-only-text"></span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|||||||
@ -22,7 +22,7 @@
|
|||||||
|
|
||||||
.ocr-indicator {
|
.ocr-indicator {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex: 1;
|
margin-right: 40px;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -5,13 +5,14 @@ import { NgIf } from '@angular/common';
|
|||||||
import { MatProgressBar } from '@angular/material/progress-bar';
|
import { MatProgressBar } from '@angular/material/progress-bar';
|
||||||
import { MatIcon } from '@angular/material/icon';
|
import { MatIcon } from '@angular/material/icon';
|
||||||
import { TranslateModule } from '@ngx-translate/core';
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
|
import { MatTooltip } from '@angular/material/tooltip';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'redaction-readonly-banner',
|
selector: 'redaction-readonly-banner',
|
||||||
templateUrl: './readonly-banner.component.html',
|
templateUrl: './readonly-banner.component.html',
|
||||||
styleUrls: ['./readonly-banner.component.scss'],
|
styleUrls: ['./readonly-banner.component.scss'],
|
||||||
standalone: true,
|
standalone: true,
|
||||||
imports: [OcrProgressBarComponent, NgIf, MatProgressBar, MatIcon, TranslateModule],
|
imports: [OcrProgressBarComponent, NgIf, MatProgressBar, MatIcon, TranslateModule, MatTooltip],
|
||||||
})
|
})
|
||||||
export class ReadonlyBannerComponent {
|
export class ReadonlyBannerComponent {
|
||||||
protected readonly _state = inject(FilePreviewStateService);
|
protected readonly _state = inject(FilePreviewStateService);
|
||||||
|
|||||||
@ -3,7 +3,7 @@
|
|||||||
<iqser-popup-filter [primaryFiltersSlug]="'componentLogFilters'" [attr.help-mode-key]="'filter_components'"></iqser-popup-filter>
|
<iqser-popup-filter [primaryFiltersSlug]="'componentLogFilters'" [attr.help-mode-key]="'filter_components'"></iqser-popup-filter>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div *ngIf="displayedComponents$ | async as displayedComponents" class="components-container" id="components-view">
|
<div *ngIf="componentLogService.all$ | async as components" class="components-container" id="components-view">
|
||||||
<div class="component-row">
|
<div class="component-row">
|
||||||
<div class="header">
|
<div class="header">
|
||||||
<div class="component">{{ 'component-management.table-header.component' | translate }}</div>
|
<div class="component">{{ 'component-management.table-header.component' | translate }}</div>
|
||||||
@ -12,7 +12,7 @@
|
|||||||
<div class="row-separator"></div>
|
<div class="row-separator"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div *ngFor="let entry of displayedComponents" class="component-row">
|
<div *ngFor="let entry of components" class="component-row">
|
||||||
<redaction-editable-structured-component-value
|
<redaction-editable-structured-component-value
|
||||||
#editableComponent
|
#editableComponent
|
||||||
[entry]="entry"
|
[entry]="entry"
|
||||||
|
|||||||
@ -2,16 +2,14 @@ import { Component, effect, Input, OnInit, signal, ViewChildren } from '@angular
|
|||||||
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';
|
||||||
import { ComponentLogService } from '@services/files/component-log.service';
|
|
||||||
import { combineLatest, firstValueFrom, Observable } from 'rxjs';
|
import { combineLatest, firstValueFrom, Observable } from 'rxjs';
|
||||||
import { EditableStructuredComponentValueComponent } from '../editable-structured-component-value/editable-structured-component-value.component';
|
import { EditableStructuredComponentValueComponent } from '../editable-structured-component-value/editable-structured-component-value.component';
|
||||||
import { FilterService, PopupFilterComponent } from '@common-ui/filtering';
|
import { FilterService, PopupFilterComponent } from '@common-ui/filtering';
|
||||||
import { ComponentLogFilterService } from '../../services/component-log-filter.service';
|
import { ComponentLogFilterService } from '../../services/component-log-filter.service';
|
||||||
import { map } from 'rxjs/operators';
|
|
||||||
import { toObservable } from '@angular/core/rxjs-interop';
|
|
||||||
import { AsyncPipe, NgForOf, NgIf } from '@angular/common';
|
import { AsyncPipe, NgForOf, NgIf } from '@angular/common';
|
||||||
import { TranslateModule } from '@ngx-translate/core';
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
import { FilePreviewStateService } from '../../services/file-preview-state.service';
|
import { FilePreviewStateService } from '../../services/file-preview-state.service';
|
||||||
|
import { ComponentLogService } from '@services/entity-services/component-log.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'redaction-structured-component-management',
|
selector: 'redaction-structured-component-management',
|
||||||
@ -21,20 +19,17 @@ 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 {
|
||||||
protected readonly componentLogData = signal<ComponentLogEntry[] | undefined>(undefined);
|
|
||||||
protected readonly componentLogData$ = toObservable(this.componentLogData);
|
|
||||||
protected readonly iconButtonTypes = IconButtonTypes;
|
protected readonly iconButtonTypes = IconButtonTypes;
|
||||||
protected displayedComponents$: Observable<ComponentLogEntry[]>;
|
|
||||||
@Input() file: File;
|
@Input() file: File;
|
||||||
@Input() dictionaries: Dictionary[];
|
@Input() dictionaries: Dictionary[];
|
||||||
@ViewChildren('editableComponent') editableComponents: List<EditableStructuredComponentValueComponent>;
|
@ViewChildren('editableComponent') editableComponents: List<EditableStructuredComponentValueComponent>;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private readonly _componentLogService: ComponentLogService,
|
|
||||||
private readonly _loadingService: LoadingService,
|
private readonly _loadingService: LoadingService,
|
||||||
private readonly _componentLogFilterService: ComponentLogFilterService,
|
private readonly _componentLogFilterService: ComponentLogFilterService,
|
||||||
private readonly _filterService: FilterService,
|
private readonly _filterService: FilterService,
|
||||||
private readonly _state: FilePreviewStateService,
|
private readonly _state: FilePreviewStateService,
|
||||||
|
protected readonly componentLogService: ComponentLogService,
|
||||||
) {
|
) {
|
||||||
effect(async () => {
|
effect(async () => {
|
||||||
this._state.file();
|
this._state.file();
|
||||||
@ -48,7 +43,6 @@ export class StructuredComponentManagementComponent implements OnInit {
|
|||||||
|
|
||||||
async ngOnInit(): Promise<void> {
|
async ngOnInit(): Promise<void> {
|
||||||
await this.#loadData();
|
await this.#loadData();
|
||||||
this.displayedComponents$ = this.#displayedComponents$();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
deselectLast() {
|
deselectLast() {
|
||||||
@ -61,7 +55,7 @@ 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,29 +63,21 @@ 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();
|
||||||
}
|
}
|
||||||
|
|
||||||
#displayedComponents$() {
|
|
||||||
const componentLogFilters$ = this._filterService.getFilterModels$('componentLogFilters');
|
|
||||||
return combineLatest([this.componentLogData$, componentLogFilters$]).pipe(
|
|
||||||
map(([components, filters]) => this._componentLogFilterService.filterComponents(components, filters)),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
async #loadData(): Promise<void> {
|
async #loadData(): Promise<void> {
|
||||||
const componentLogData = await firstValueFrom(
|
await firstValueFrom(
|
||||||
this._componentLogService.getComponentLogData(
|
this.componentLogService.loadComponentLogData(
|
||||||
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(this.componentLogService.all);
|
||||||
this.componentLogData.set(componentLogData);
|
|
||||||
this._loadingService.stop();
|
this._loadingService.stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,12 +1,14 @@
|
|||||||
<section class="dialog">
|
<section class="dialog">
|
||||||
<form (submit)="save()" [formGroup]="form">
|
<form (submit)="save()" [formGroup]="form">
|
||||||
<div
|
<div class="dialog-header heading-l">
|
||||||
[translateParams]="{ type: isImage ? 'image' : isHint ? 'hint' : 'redaction' }"
|
<span
|
||||||
[translate]="'edit-redaction.dialog.title'"
|
[translateParams]="{ type: isImage ? 'image' : isHint ? 'hint' : 'redaction' }"
|
||||||
class="dialog-header heading-l"
|
[translate]="'edit-redaction.dialog.title'"
|
||||||
></div>
|
[attr.help-mode-key]="helpModeKeyByType"
|
||||||
|
></span>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div [class.fixed-height]="isRedacted && isImage" class="dialog-content redaction">
|
<div [class.image-dialog]="isRedacted && isImage" [class.rectangle-dialog]="allRectangles" class="dialog-content redaction">
|
||||||
<div *ngIf="!isImage && redactedTexts.length && !allRectangles" class="iqser-input-group">
|
<div *ngIf="!isImage && redactedTexts.length && !allRectangles" class="iqser-input-group">
|
||||||
<redaction-selected-annotations-table
|
<redaction-selected-annotations-table
|
||||||
[columns]="tableColumns"
|
[columns]="tableColumns"
|
||||||
@ -90,10 +92,10 @@
|
|||||||
</div>
|
</div>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
||||||
<div *ngIf="allRectangles" class="iqser-input-group w-400">
|
<div *ngIf="allRectangles" class="iqser-input-group w-450">
|
||||||
<label [translate]="'change-legal-basis-dialog.content.classification'"></label>
|
<label [translate]="'change-legal-basis-dialog.content.classification'"></label>
|
||||||
<input
|
<input
|
||||||
[placeholder]="'edit-redaction.dialog.content.unchanged' | translate"
|
[placeholder]="isBulkEdit && !hideValuePlaceholder ? ('edit-redaction.dialog.content.unchanged' | translate) : ''"
|
||||||
formControlName="value"
|
formControlName="value"
|
||||||
name="classification"
|
name="classification"
|
||||||
type="text"
|
type="text"
|
||||||
@ -107,7 +109,7 @@
|
|||||||
formControlName="comment"
|
formControlName="comment"
|
||||||
iqserHasScrollbar
|
iqserHasScrollbar
|
||||||
name="comment"
|
name="comment"
|
||||||
rows="4"
|
rows="3"
|
||||||
type="text"
|
type="text"
|
||||||
></textarea>
|
></textarea>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -1,8 +1,16 @@
|
|||||||
.dialog-content {
|
.dialog-content {
|
||||||
padding-top: 8px;
|
padding-top: 8px;
|
||||||
|
|
||||||
&.fixed-height {
|
&.rectangle-dialog {
|
||||||
height: 386px;
|
height: 600px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.image-dialog {
|
||||||
|
height: 346px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rectangle-dialog,
|
||||||
|
.image-dialog {
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -33,11 +33,13 @@ import {
|
|||||||
LegalBasisOption,
|
LegalBasisOption,
|
||||||
RectangleRedactOption,
|
RectangleRedactOption,
|
||||||
RectangleRedactOptions,
|
RectangleRedactOptions,
|
||||||
|
RedactOrHintOptions,
|
||||||
} from '../../utils/dialog-types';
|
} from '../../utils/dialog-types';
|
||||||
import { DetailsRadioComponent } from '@common-ui/inputs/details-radio/details-radio.component';
|
import { DetailsRadioComponent } from '@common-ui/inputs/details-radio/details-radio.component';
|
||||||
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, prefillPageRange } from '../../utils/enhance-manual-redaction-request.utils';
|
||||||
|
import { ActionsHelpModeKeys } from '../../utils/constants';
|
||||||
|
|
||||||
interface TypeSelectOptions {
|
interface TypeSelectOptions {
|
||||||
type: string;
|
type: string;
|
||||||
@ -81,14 +83,21 @@ export class EditRedactionDialogComponent
|
|||||||
readonly isManualRedaction = this.annotations.some(annotation => annotation.type === SuperTypes.ManualRedaction);
|
readonly isManualRedaction = this.annotations.some(annotation => annotation.type === SuperTypes.ManualRedaction);
|
||||||
readonly isHint = this.annotations.every(annotation => annotation.HINT || annotation.IMAGE_HINT);
|
readonly isHint = this.annotations.every(annotation => annotation.HINT || annotation.IMAGE_HINT);
|
||||||
readonly isRedacted = this.annotations.every(annotation => annotation.isRedacted);
|
readonly isRedacted = this.annotations.every(annotation => annotation.isRedacted);
|
||||||
readonly isImported: boolean = this.annotations.every(annotation => annotation.imported);
|
readonly isImported: boolean = this.annotations.every(annotation => annotation.imported || annotation.type === 'imported_redaction');
|
||||||
|
readonly isSkipped: boolean = this.annotations.every(annotation => annotation.isSkipped);
|
||||||
readonly allRectangles = this.annotations.reduce((acc, a) => acc && a.AREA, true);
|
readonly allRectangles = this.annotations.reduce((acc, a) => acc && a.AREA, true);
|
||||||
readonly tableColumns: ValueColumn[] = [{ label: 'Value' }, { label: 'Type' }];
|
readonly tableColumns: ValueColumn[] = [{ label: 'Value' }, { label: 'Type' }];
|
||||||
readonly tableData: ValueColumn[][] = this.data.annotations.map(redaction => [
|
readonly tableData: ValueColumn[][] = this.data.annotations.map(redaction => [
|
||||||
{ label: redaction.value, bold: true },
|
{ label: redaction.value, bold: true },
|
||||||
{ label: redaction.typeLabel },
|
{ label: redaction.typeLabel },
|
||||||
]);
|
]);
|
||||||
options = this.allRectangles ? getRectangleRedactOptions('edit') : getEditRedactionOptions();
|
readonly annotationsType = this.isHint
|
||||||
|
? ActionsHelpModeKeys.hint
|
||||||
|
: this.isSkipped
|
||||||
|
? ActionsHelpModeKeys.skipped
|
||||||
|
: ActionsHelpModeKeys.redaction;
|
||||||
|
readonly helpModeKeyByType = `${this.annotationsType}_edit_DIALOG`;
|
||||||
|
options = this.allRectangles ? getRectangleRedactOptions('edit', this.data.annotations) : getEditRedactionOptions(this.isHint);
|
||||||
legalOptions: LegalBasisOption[] = [];
|
legalOptions: LegalBasisOption[] = [];
|
||||||
dictionaries: Dictionary[] = [];
|
dictionaries: Dictionary[] = [];
|
||||||
typeSelectOptions: TypeSelectOptions[] = [];
|
typeSelectOptions: TypeSelectOptions[] = [];
|
||||||
@ -103,6 +112,13 @@ export class EditRedactionDialogComponent
|
|||||||
private readonly _dictionaryService: DictionaryService,
|
private readonly _dictionaryService: DictionaryService,
|
||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
|
if (this.allRectangles && !this.isImported) {
|
||||||
|
prefillPageRange(
|
||||||
|
this.data.annotations[0],
|
||||||
|
this.data.allFileAnnotations,
|
||||||
|
this.options as DetailsRadioOption<RectangleRedactOption>[],
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
get displayedDictionaryLabel() {
|
get displayedDictionaryLabel() {
|
||||||
@ -143,7 +159,9 @@ export class EditRedactionDialogComponent
|
|||||||
get hideParagraphPlaceholder() {
|
get hideParagraphPlaceholder() {
|
||||||
return this.form.controls.section.value !== this.initialFormValue['section'] || this.#isFieldEmpty('section');
|
return this.form.controls.section.value !== this.initialFormValue['section'] || this.#isFieldEmpty('section');
|
||||||
}
|
}
|
||||||
|
get hideValuePlaceholder() {
|
||||||
|
return this.form.controls.section.value !== this.initialFormValue['value'] || this.#isFieldEmpty('value');
|
||||||
|
}
|
||||||
get hiddenReason() {
|
get hiddenReason() {
|
||||||
return this.isImage && this.form.controls.reason.disabled;
|
return this.isImage && this.form.controls.reason.disabled;
|
||||||
}
|
}
|
||||||
@ -205,11 +223,7 @@ export class EditRedactionDialogComponent
|
|||||||
const value = this.form.value;
|
const value = this.form.value;
|
||||||
const initialReason: LegalBasisOption = this.initialFormValue.reason;
|
const initialReason: LegalBasisOption = this.initialFormValue.reason;
|
||||||
const initialLegalBasis = initialReason?.legalBasis ?? '';
|
const initialLegalBasis = initialReason?.legalBasis ?? '';
|
||||||
const pageNumbers = parseSelectedPageNumbers(
|
const pageNumbers = parseSelectedPageNumbers(this.form.get('option').value?.additionalInput?.value, this.data.file);
|
||||||
this.form.get('option').value.additionalInput?.value,
|
|
||||||
this.data.file,
|
|
||||||
this.data.annotations[0],
|
|
||||||
);
|
|
||||||
const position = parseRectanglePosition(this.annotations[0]);
|
const position = parseRectanglePosition(this.annotations[0]);
|
||||||
|
|
||||||
this.close({
|
this.close({
|
||||||
@ -218,7 +232,7 @@ export class EditRedactionDialogComponent
|
|||||||
comment: value.comment,
|
comment: value.comment,
|
||||||
type: value.type,
|
type: value.type,
|
||||||
value: this.allRectangles ? value.value : null,
|
value: this.allRectangles ? value.value : null,
|
||||||
option: value.option.value,
|
option: value.option?.value ?? RedactOrHintOptions.ONLY_HERE,
|
||||||
position,
|
position,
|
||||||
pageNumbers,
|
pageNumbers,
|
||||||
});
|
});
|
||||||
@ -255,7 +269,10 @@ export class EditRedactionDialogComponent
|
|||||||
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 }),
|
||||||
option: new FormControl<DetailsRadioOption<EditRedactionOption | RectangleRedactOption>>(this.options[0], validatePageRange()),
|
option: new FormControl<DetailsRadioOption<EditRedactionOption | RectangleRedactOption>>(
|
||||||
|
this.options[0],
|
||||||
|
validatePageRange(this.data.file.numberOfPages),
|
||||||
|
),
|
||||||
value: new FormControl<string>(this.allRectangles ? this.annotations[0].value : null),
|
value: new FormControl<string>(this.allRectangles ? this.annotations[0].value : null),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,11 @@
|
|||||||
<section class="dialog">
|
<section class="dialog">
|
||||||
<form (submit)="save()" [formGroup]="form">
|
<form (submit)="save()" [formGroup]="form">
|
||||||
<div class="dialog-header heading-l" [translate]="dialogTitle"></div>
|
<div class="dialog-header heading-l" [translate]="dialogTitle">
|
||||||
|
<span
|
||||||
|
[translate]="dialogTitle"
|
||||||
|
[attr.help-mode-key]="isSkipped ? 'skipped_force_DIALOG' : isImageHint ? 'hint_redact_DIALOG' : ''"
|
||||||
|
></span>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="dialog-content force-annotation">
|
<div class="dialog-content force-annotation">
|
||||||
@if (!isImageHint) {
|
@if (!isImageHint) {
|
||||||
|
|||||||
@ -1,19 +1,17 @@
|
|||||||
import { Component, Inject, OnInit } from '@angular/core';
|
import { Component, OnInit } from '@angular/core';
|
||||||
import { ReactiveFormsModule, UntypedFormGroup, Validators } from '@angular/forms';
|
import { FormBuilder, FormGroup, ReactiveFormsModule, UntypedFormGroup, Validators } from '@angular/forms';
|
||||||
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
|
|
||||||
import {
|
import {
|
||||||
BaseDialogComponent,
|
|
||||||
CircleButtonComponent,
|
CircleButtonComponent,
|
||||||
getConfig,
|
getConfig,
|
||||||
HasScrollbarDirective,
|
HasScrollbarDirective,
|
||||||
HelpButtonComponent,
|
HelpButtonComponent,
|
||||||
IconButtonComponent,
|
IconButtonComponent,
|
||||||
IqserDenyDirective,
|
IqserDenyDirective,
|
||||||
|
IqserDialogComponent,
|
||||||
} from '@iqser/common-ui';
|
} from '@iqser/common-ui';
|
||||||
import { JustificationsService } from '@services/entity-services/justifications.service';
|
import { JustificationsService } from '@services/entity-services/justifications.service';
|
||||||
import { Dossier, ILegalBasisChangeRequest } from '@red/domain';
|
import { ILegalBasisChangeRequest } from '@red/domain';
|
||||||
import { firstValueFrom } from 'rxjs';
|
import { firstValueFrom } from 'rxjs';
|
||||||
import { AnnotationWrapper } from '@models/file/annotation.wrapper';
|
|
||||||
import { Roles } from '@users/roles';
|
import { Roles } from '@users/roles';
|
||||||
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
||||||
import {
|
import {
|
||||||
@ -21,29 +19,26 @@ import {
|
|||||||
ValueColumn,
|
ValueColumn,
|
||||||
} from '../../components/selected-annotations-table/selected-annotations-table.component';
|
} from '../../components/selected-annotations-table/selected-annotations-table.component';
|
||||||
import { NgForOf, NgIf } from '@angular/common';
|
import { NgForOf, NgIf } from '@angular/common';
|
||||||
import { MatFormField } from '@angular/material/form-field';
|
|
||||||
import { MatOption, MatSelect, MatSelectTrigger } from '@angular/material/select';
|
import { MatOption, MatSelect, MatSelectTrigger } from '@angular/material/select';
|
||||||
|
import { DetailsRadioOption } from '@common-ui/inputs/details-radio/details-radio-option';
|
||||||
|
import { ForceAnnotationData, ForceAnnotationOption, ForceAnnotationResult, LegalBasisOption } from '../../utils/dialog-types';
|
||||||
|
import { getForceAnnotationOptions } from '../../utils/dialog-options';
|
||||||
|
import { SystemDefaults } from '../../../account/utils/dialog-defaults';
|
||||||
|
import { MatFormField } from '@angular/material/form-field';
|
||||||
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 { DetailsRadioOption } from '@common-ui/inputs/details-radio/details-radio-option';
|
|
||||||
import { ForceAnnotationOption, LegalBasisOption } from '../../utils/dialog-types';
|
|
||||||
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';
|
|
||||||
|
|
||||||
const DOCUMINE_LEGAL_BASIS = 'n-a.';
|
const DOCUMINE_LEGAL_BASIS = 'n-a.';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'redaction-force-annotation-dialog',
|
|
||||||
templateUrl: './force-annotation-dialog.component.html',
|
templateUrl: './force-annotation-dialog.component.html',
|
||||||
styleUrls: ['./force-annotation-dialog.component.scss'],
|
styleUrls: ['./force-annotation-dialog.component.scss'],
|
||||||
standalone: true,
|
standalone: true,
|
||||||
imports: [
|
imports: [
|
||||||
ReactiveFormsModule,
|
ReactiveFormsModule,
|
||||||
NgIf,
|
|
||||||
SelectedAnnotationsTableComponent,
|
SelectedAnnotationsTableComponent,
|
||||||
MatFormField,
|
MatFormField,
|
||||||
MatSelectTrigger,
|
|
||||||
MatSelect,
|
MatSelect,
|
||||||
MatOption,
|
MatOption,
|
||||||
MatTooltip,
|
MatTooltip,
|
||||||
@ -52,41 +47,47 @@ const DOCUMINE_LEGAL_BASIS = 'n-a.';
|
|||||||
IconButtonComponent,
|
IconButtonComponent,
|
||||||
IqserDenyDirective,
|
IqserDenyDirective,
|
||||||
CircleButtonComponent,
|
CircleButtonComponent,
|
||||||
NgForOf,
|
|
||||||
HelpButtonComponent,
|
HelpButtonComponent,
|
||||||
DetailsRadioComponent,
|
DetailsRadioComponent,
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
export class ForceAnnotationDialogComponent extends BaseDialogComponent implements OnInit {
|
export class ForceAnnotationDialogComponent
|
||||||
|
extends IqserDialogComponent<ForceAnnotationDialogComponent, ForceAnnotationData, ForceAnnotationResult>
|
||||||
|
implements OnInit
|
||||||
|
{
|
||||||
readonly isDocumine = getConfig().IS_DOCUMINE;
|
readonly isDocumine = getConfig().IS_DOCUMINE;
|
||||||
readonly options: DetailsRadioOption<ForceAnnotationOption>[];
|
readonly options: DetailsRadioOption<ForceAnnotationOption>[];
|
||||||
|
|
||||||
|
readonly form: FormGroup;
|
||||||
readonly tableColumns: ValueColumn[] = [{ label: 'Value' }, { label: 'Type' }];
|
readonly tableColumns: ValueColumn[] = [{ label: 'Value' }, { label: 'Type' }];
|
||||||
readonly tableData: ValueColumn[][] = this._data.annotations.map(redaction => [
|
readonly tableData: ValueColumn[][] = this.data.annotations.map(redaction => [
|
||||||
{ label: redaction.value, bold: true },
|
{ label: redaction.value, bold: true },
|
||||||
{ label: redaction.typeLabel },
|
{ label: redaction.typeLabel },
|
||||||
]);
|
]);
|
||||||
|
readonly isSkipped = this.data.annotations.every(annotation => annotation.isSkipped);
|
||||||
|
|
||||||
legalOptions: LegalBasisOption[] = [];
|
legalOptions: LegalBasisOption[] = [];
|
||||||
protected readonly roles = Roles;
|
protected readonly roles = Roles;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private readonly _justificationsService: JustificationsService,
|
private readonly _justificationsService: JustificationsService,
|
||||||
protected readonly _dialogRef: MatDialogRef<ForceAnnotationDialogComponent>,
|
private readonly _formBuilder: FormBuilder,
|
||||||
@Inject(MAT_DIALOG_DATA)
|
|
||||||
private readonly _data: { readonly dossier: Dossier; readonly hint: boolean; annotations: AnnotationWrapper[] },
|
|
||||||
) {
|
) {
|
||||||
super(_dialogRef);
|
super();
|
||||||
this.options = getForceAnnotationOptions(this.isDocumine, this.isHintDialog);
|
this.options = getForceAnnotationOptions(this.isDocumine, this.isHintDialog, this.isImageDialog);
|
||||||
this.form = this.#getForm();
|
this.form = this.#getForm();
|
||||||
}
|
}
|
||||||
|
|
||||||
get isImageHint() {
|
get isImageHint() {
|
||||||
return this._data.annotations.every(annotation => annotation.IMAGE_HINT);
|
return this.data.annotations.every(annotation => annotation.IMAGE_HINT);
|
||||||
}
|
}
|
||||||
|
|
||||||
get isHintDialog() {
|
get isHintDialog() {
|
||||||
return this._data.hint;
|
return this.data.hint;
|
||||||
|
}
|
||||||
|
|
||||||
|
get isImageDialog() {
|
||||||
|
return this.data.image;
|
||||||
}
|
}
|
||||||
|
|
||||||
get disabled(): boolean {
|
get disabled(): boolean {
|
||||||
@ -103,7 +104,7 @@ export class ForceAnnotationDialogComponent extends BaseDialogComponent implemen
|
|||||||
|
|
||||||
async ngOnInit() {
|
async ngOnInit() {
|
||||||
if (!this.isDocumine) {
|
if (!this.isDocumine) {
|
||||||
const data = await firstValueFrom(this._justificationsService.getForDossierTemplate(this._data.dossier.dossierTemplateId));
|
const data = await firstValueFrom(this._justificationsService.getForDossierTemplate(this.data.dossier.dossierTemplateId));
|
||||||
|
|
||||||
this.legalOptions = data.map(lbm => ({
|
this.legalOptions = data.map(lbm => ({
|
||||||
legalBasis: lbm.reason,
|
legalBasis: lbm.reason,
|
||||||
@ -114,8 +115,8 @@ export class ForceAnnotationDialogComponent extends BaseDialogComponent implemen
|
|||||||
this.legalOptions.sort((a, b) => a.label.localeCompare(b.label));
|
this.legalOptions.sort((a, b) => a.label.localeCompare(b.label));
|
||||||
|
|
||||||
// Set pre-existing reason if it exists
|
// Set pre-existing reason if it exists
|
||||||
const existingReason = this.legalOptions.find(option => option.legalBasis === this._data.annotations[0].legalBasis);
|
const existingReason = this.legalOptions.find(option => option.legalBasis === this.data.annotations[0].legalBasis);
|
||||||
if (!this._data.hint && existingReason) {
|
if (!this.data.hint && existingReason) {
|
||||||
this.form.patchValue({ reason: existingReason }, { emitEvent: false });
|
this.form.patchValue({ reason: existingReason }, { emitEvent: false });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -123,12 +124,12 @@ export class ForceAnnotationDialogComponent extends BaseDialogComponent implemen
|
|||||||
}
|
}
|
||||||
|
|
||||||
save() {
|
save() {
|
||||||
this._dialogRef.close(this.#createForceRedactionRequest());
|
this.close(this.#createForceRedactionRequest());
|
||||||
}
|
}
|
||||||
|
|
||||||
#getForm(): UntypedFormGroup {
|
#getForm(): UntypedFormGroup {
|
||||||
return this._formBuilder.group({
|
return this._formBuilder.group({
|
||||||
reason: this._data.hint ? ['Forced Hint'] : [null, !this.isDocumine ? Validators.required : null],
|
reason: this.data.hint ? ['Forced Hint'] : [null, !this.isDocumine ? Validators.required : null],
|
||||||
comment: [null],
|
comment: [null],
|
||||||
option: this.options.find(o => o.value === SystemDefaults.FORCE_REDACTION_DEFAULT),
|
option: this.options.find(o => o.value === SystemDefaults.FORCE_REDACTION_DEFAULT),
|
||||||
});
|
});
|
||||||
@ -140,7 +141,7 @@ export class ForceAnnotationDialogComponent extends BaseDialogComponent implemen
|
|||||||
request.legalBasis = !this.isDocumine ? this.form.get('reason').value.legalBasis : DOCUMINE_LEGAL_BASIS;
|
request.legalBasis = !this.isDocumine ? this.form.get('reason').value.legalBasis : DOCUMINE_LEGAL_BASIS;
|
||||||
request.comment = this.form.get('comment').value;
|
request.comment = this.form.get('comment').value;
|
||||||
request.reason = this.form.get('reason').value.description;
|
request.reason = this.form.get('reason').value.description;
|
||||||
request.option = this.form.get('option').value.value;
|
request.option = this.form.get('option').value?.value;
|
||||||
|
|
||||||
return request;
|
return request;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,7 +2,7 @@
|
|||||||
<form (submit)="save()" [formGroup]="form">
|
<form (submit)="save()" [formGroup]="form">
|
||||||
<div [translate]="'manual-annotation.dialog.header.redaction'" class="dialog-header heading-l"></div>
|
<div [translate]="'manual-annotation.dialog.header.redaction'" class="dialog-header heading-l"></div>
|
||||||
|
|
||||||
<div class="dialog-content">
|
<div class="dialog-content redaction">
|
||||||
<iqser-details-radio
|
<iqser-details-radio
|
||||||
[options]="options"
|
[options]="options"
|
||||||
(extraOptionChanged)="extraOptionChanged($event)"
|
(extraOptionChanged)="extraOptionChanged($event)"
|
||||||
@ -43,7 +43,7 @@
|
|||||||
|
|
||||||
<div class="iqser-input-group w-450">
|
<div class="iqser-input-group w-450">
|
||||||
<label [translate]="'manual-annotation.dialog.content.comment'"></label>
|
<label [translate]="'manual-annotation.dialog.content.comment'"></label>
|
||||||
<textarea formControlName="comment" iqserHasScrollbar name="comment" rows="4" type="text"></textarea>
|
<textarea formControlName="comment" iqserHasScrollbar name="comment" rows="3" type="text"></textarea>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@ -3,8 +3,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.dialog-content {
|
.dialog-content {
|
||||||
height: 650px;
|
height: 600px;
|
||||||
overflow-y: auto;
|
padding-top: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.apply-on-multiple-pages {
|
.apply-on-multiple-pages {
|
||||||
|
|||||||
@ -130,7 +130,7 @@ export class RectangleAnnotationDialog
|
|||||||
reason: [null, Validators.required],
|
reason: [null, Validators.required],
|
||||||
comment: [null],
|
comment: [null],
|
||||||
classification: [NON_READABLE_CONTENT],
|
classification: [NON_READABLE_CONTENT],
|
||||||
option: [this.#getOption(SystemDefaults.RECTANGLE_REDACT_DEFAULT), validatePageRange()],
|
option: [this.#getOption(SystemDefaults.RECTANGLE_REDACT_DEFAULT), validatePageRange(this.data.file.numberOfPages)],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,8 @@
|
|||||||
<section class="dialog">
|
<section class="dialog">
|
||||||
<form (submit)="save()" [formGroup]="form">
|
<form (submit)="save()" [formGroup]="form">
|
||||||
<div [translate]="'redact-text.dialog.title'" class="dialog-header heading-l"></div>
|
<div class="dialog-header heading-l">
|
||||||
|
<span [translate]="'redact-text.dialog.title'" [attr.help-mode-key]="helpModeKey"></span>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="dialog-content redaction">
|
<div class="dialog-content redaction">
|
||||||
<div class="iqser-input-group">
|
<div class="iqser-input-group">
|
||||||
@ -59,6 +61,7 @@
|
|||||||
</iqser-icon-button>
|
</iqser-icon-button>
|
||||||
|
|
||||||
<div [translate]="'redact-text.dialog.actions.cancel'" class="all-caps-label cancel" mat-dialog-close></div>
|
<div [translate]="'redact-text.dialog.actions.cancel'" class="all-caps-label cancel" mat-dialog-close></div>
|
||||||
|
<iqser-help-button *deny="roles.getRss"></iqser-help-button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
|
|||||||
@ -8,7 +8,15 @@ import { MatOption, MatSelect, MatSelectTrigger } from '@angular/material/select
|
|||||||
import { MatTooltip } from '@angular/material/tooltip';
|
import { MatTooltip } from '@angular/material/tooltip';
|
||||||
import { DetailsRadioOption } from '@common-ui/inputs/details-radio/details-radio-option';
|
import { DetailsRadioOption } from '@common-ui/inputs/details-radio/details-radio-option';
|
||||||
import { DetailsRadioComponent } from '@common-ui/inputs/details-radio/details-radio.component';
|
import { DetailsRadioComponent } from '@common-ui/inputs/details-radio/details-radio.component';
|
||||||
import { CircleButtonComponent, HasScrollbarDirective, IconButtonComponent, IconButtonTypes, IqserDialogComponent } from '@iqser/common-ui';
|
import {
|
||||||
|
CircleButtonComponent,
|
||||||
|
HasScrollbarDirective,
|
||||||
|
HelpButtonComponent,
|
||||||
|
IconButtonComponent,
|
||||||
|
IconButtonTypes,
|
||||||
|
IqserDenyDirective,
|
||||||
|
IqserDialogComponent,
|
||||||
|
} from '@iqser/common-ui';
|
||||||
import { AnnotationWrapper } from '@models/file/annotation.wrapper';
|
import { AnnotationWrapper } from '@models/file/annotation.wrapper';
|
||||||
import { TranslateModule } from '@ngx-translate/core';
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
import { Dictionary, IAddRedactionRequest, SuperTypes } from '@red/domain';
|
import { Dictionary, IAddRedactionRequest, SuperTypes } from '@red/domain';
|
||||||
@ -30,6 +38,7 @@ import {
|
|||||||
RedactRecommendationResult,
|
RedactRecommendationResult,
|
||||||
ResizeOptions,
|
ResizeOptions,
|
||||||
} from '../../utils/dialog-types';
|
} from '../../utils/dialog-types';
|
||||||
|
import { Roles } from '@users/roles';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
templateUrl: './redact-recommendation-dialog.component.html',
|
templateUrl: './redact-recommendation-dialog.component.html',
|
||||||
@ -51,6 +60,8 @@ import {
|
|||||||
MatDialogClose,
|
MatDialogClose,
|
||||||
MatSelectTrigger,
|
MatSelectTrigger,
|
||||||
MatSelect,
|
MatSelect,
|
||||||
|
HelpButtonComponent,
|
||||||
|
IqserDenyDirective,
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
export class RedactRecommendationDialogComponent
|
export class RedactRecommendationDialogComponent
|
||||||
@ -75,6 +86,8 @@ export class RedactRecommendationDialogComponent
|
|||||||
reason: [null],
|
reason: [null],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
readonly helpModeKey = `recommendation_${this.data.action}_DIALOG`;
|
||||||
|
|
||||||
readonly tableColumns: ValueColumn[] = [{ label: 'Value' }, { label: 'Type' }];
|
readonly tableColumns: ValueColumn[] = [{ label: 'Value' }, { label: 'Type' }];
|
||||||
readonly tableData: ValueColumn[][] = this.data.annotations.map(redaction => [
|
readonly tableData: ValueColumn[][] = this.data.annotations.map(redaction => [
|
||||||
{ label: redaction.value, bold: true },
|
{ label: redaction.value, bold: true },
|
||||||
@ -162,7 +175,11 @@ export class RedactRecommendationDialogComponent
|
|||||||
}
|
}
|
||||||
|
|
||||||
#setDictionaries() {
|
#setDictionaries() {
|
||||||
this.dictionaries = this._dictionaryService.getRedactTextDictionaries(this.#dossier.dossierId, !this.#applyToAllDossiers);
|
this.dictionaries = this._dictionaryService.getRedactTextDictionaries(
|
||||||
|
this.#dossier.dossierId,
|
||||||
|
!this.#applyToAllDossiers,
|
||||||
|
this.#dossier.dossierTemplateId,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#selectReason() {
|
#selectReason() {
|
||||||
@ -199,4 +216,6 @@ export class RedactRecommendationDialogComponent
|
|||||||
}
|
}
|
||||||
this.form.controls.dictionary.setValue(this.#manualRedactionTypeExists ? SuperTypes.ManualRedaction : null);
|
this.form.controls.dictionary.setValue(this.#manualRedactionTypeExists ? SuperTypes.ManualRedaction : null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected readonly roles = Roles;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,8 @@
|
|||||||
<section class="dialog">
|
<section class="dialog">
|
||||||
<form (submit)="save()" [formGroup]="form">
|
<form (submit)="save()" [formGroup]="form">
|
||||||
<div [translate]="'redact-text.dialog.title'" class="dialog-header heading-l"></div>
|
<div class="dialog-header heading-l">
|
||||||
|
<span [translate]="'redact-text.dialog.title'" [attr.help-mode-key]="'add_redaction_DIALOG'"></span>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="dialog-content redaction">
|
<div class="dialog-content redaction">
|
||||||
<div class="iqser-input-group w-full selected-text-group">
|
<div class="iqser-input-group w-full selected-text-group">
|
||||||
@ -127,6 +129,7 @@
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<div [translate]="'redact-text.dialog.actions.cancel'" class="all-caps-label cancel" mat-dialog-close></div>
|
<div [translate]="'redact-text.dialog.actions.cancel'" class="all-caps-label cancel" mat-dialog-close></div>
|
||||||
|
<iqser-help-button *deny="roles.getRss"></iqser-help-button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
|
|||||||
@ -8,7 +8,15 @@ import { MatOption, MatSelect, MatSelectTrigger } from '@angular/material/select
|
|||||||
import { MatTooltip } from '@angular/material/tooltip';
|
import { MatTooltip } from '@angular/material/tooltip';
|
||||||
import { DetailsRadioOption } from '@common-ui/inputs/details-radio/details-radio-option';
|
import { DetailsRadioOption } from '@common-ui/inputs/details-radio/details-radio-option';
|
||||||
import { DetailsRadioComponent } from '@common-ui/inputs/details-radio/details-radio.component';
|
import { DetailsRadioComponent } from '@common-ui/inputs/details-radio/details-radio.component';
|
||||||
import { CircleButtonComponent, HasScrollbarDirective, IconButtonComponent, IconButtonTypes, IqserDialogComponent } from '@iqser/common-ui';
|
import {
|
||||||
|
CircleButtonComponent,
|
||||||
|
HasScrollbarDirective,
|
||||||
|
HelpButtonComponent,
|
||||||
|
IconButtonComponent,
|
||||||
|
IconButtonTypes,
|
||||||
|
IqserDenyDirective,
|
||||||
|
IqserDialogComponent,
|
||||||
|
} from '@iqser/common-ui';
|
||||||
import { TranslateModule } from '@ngx-translate/core';
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
import { Dictionary, SuperTypes } from '@red/domain';
|
import { Dictionary, SuperTypes } from '@red/domain';
|
||||||
import { ActiveDossiersService } from '@services/dossiers/active-dossiers.service';
|
import { ActiveDossiersService } from '@services/dossiers/active-dossiers.service';
|
||||||
@ -55,6 +63,8 @@ const MAXIMUM_TEXT_AREA_WIDTH = 421;
|
|||||||
AsyncPipe,
|
AsyncPipe,
|
||||||
IconButtonComponent,
|
IconButtonComponent,
|
||||||
MatDialogClose,
|
MatDialogClose,
|
||||||
|
HelpButtonComponent,
|
||||||
|
IqserDenyDirective,
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
export class RedactTextDialogComponent
|
export class RedactTextDialogComponent
|
||||||
@ -219,7 +229,11 @@ export class RedactTextDialogComponent
|
|||||||
}
|
}
|
||||||
|
|
||||||
#setDictionaries() {
|
#setDictionaries() {
|
||||||
this.dictionaries = this._dictionaryService.getRedactTextDictionaries(this.#dossier.dossierId, !this.#applyToAllDossiers);
|
this.dictionaries = this._dictionaryService.getRedactTextDictionaries(
|
||||||
|
this.#dossier.dossierId,
|
||||||
|
!this.#applyToAllDossiers,
|
||||||
|
this.#dossier.dossierTemplateId,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#getForm(): FormGroup {
|
#getForm(): FormGroup {
|
||||||
|
|||||||
@ -1,9 +1,13 @@
|
|||||||
<section class="dialog">
|
<section class="dialog">
|
||||||
<form (submit)="save()" [formGroup]="form">
|
<form (submit)="save()" [formGroup]="form">
|
||||||
<div
|
<div class="dialog-header heading-l">
|
||||||
[innerHTML]="(isBulk ? 'remove-redaction.dialog.title-bulk' : 'remove-redaction.dialog.title') | translate: typeTranslationArg"
|
<span
|
||||||
class="dialog-header heading-l"
|
[innerHTML]="
|
||||||
></div>
|
(isBulk ? 'remove-redaction.dialog.title-bulk' : 'remove-redaction.dialog.title') | translate: typeTranslationArg
|
||||||
|
"
|
||||||
|
[attr.help-mode-key]="helpModeKeyByType"
|
||||||
|
></span>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div [ngStyle]="{ height: dialogContentHeight + redactedTextsAreaHeight + 'px' }" class="dialog-content redaction">
|
<div [ngStyle]="{ height: dialogContentHeight + redactedTextsAreaHeight + 'px' }" class="dialog-content redaction">
|
||||||
<div class="iqser-input-group">
|
<div class="iqser-input-group">
|
||||||
|
|||||||
@ -36,7 +36,13 @@ import {
|
|||||||
} from '../../utils/dialog-types';
|
} from '../../utils/dialog-types';
|
||||||
import { isJustOne } from '@common-ui/utils';
|
import { 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, prefillPageRange } from '../../utils/enhance-manual-redaction-request.utils';
|
||||||
|
|
||||||
|
const ANNOTATION_TYPES = {
|
||||||
|
REDACTION: 'redaction',
|
||||||
|
HINT: 'hint',
|
||||||
|
RECOMMENDATION: 'recommendation',
|
||||||
|
};
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
templateUrl: './remove-redaction-dialog.component.html',
|
templateUrl: './remove-redaction-dialog.component.html',
|
||||||
@ -65,7 +71,12 @@ export class RemoveRedactionDialogComponent extends IqserDialogComponent<
|
|||||||
readonly iconButtonTypes = IconButtonTypes;
|
readonly iconButtonTypes = IconButtonTypes;
|
||||||
readonly recommendation = this.data.redactions.every(redaction => redaction.isRecommendation);
|
readonly recommendation = this.data.redactions.every(redaction => redaction.isRecommendation);
|
||||||
readonly hint = this.data.redactions.every(redaction => redaction.isHint);
|
readonly hint = this.data.redactions.every(redaction => redaction.isHint);
|
||||||
readonly annotationsType = this.hint ? 'hint' : this.recommendation ? 'recommendation' : 'redaction';
|
readonly annotationsType = this.hint
|
||||||
|
? ANNOTATION_TYPES.HINT
|
||||||
|
: this.recommendation
|
||||||
|
? ANNOTATION_TYPES.RECOMMENDATION
|
||||||
|
: ANNOTATION_TYPES.REDACTION;
|
||||||
|
readonly helpModeKeyByType = `${this.annotationsType}_remove_DIALOG`;
|
||||||
readonly optionByType = {
|
readonly optionByType = {
|
||||||
recommendation: {
|
recommendation: {
|
||||||
main: this._userPreferences.getRemoveRecommendationDefaultOption(),
|
main: this._userPreferences.getRemoveRecommendationDefaultOption(),
|
||||||
@ -94,6 +105,9 @@ export class RemoveRedactionDialogComponent extends IqserDialogComponent<
|
|||||||
extra: false,
|
extra: false,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
readonly isImported: boolean = this.data.redactions.every(
|
||||||
|
annotation => annotation.imported || annotation.type === 'imported_redaction',
|
||||||
|
);
|
||||||
readonly #allRectangles = this.data.redactions.reduce((acc, a) => acc && a.AREA, true);
|
readonly #allRectangles = this.data.redactions.reduce((acc, a) => acc && a.AREA, true);
|
||||||
readonly #applyToAllDossiers = this.systemDefaultByType[this.annotationsType].extra;
|
readonly #applyToAllDossiers = this.systemDefaultByType[this.annotationsType].extra;
|
||||||
readonly isSystemDefault = this.optionByType[this.annotationsType].main === SystemDefaultOption.SYSTEM_DEFAULT;
|
readonly isSystemDefault = this.optionByType[this.annotationsType].main === SystemDefaultOption.SYSTEM_DEFAULT;
|
||||||
@ -103,7 +117,7 @@ export class RemoveRedactionDialogComponent extends IqserDialogComponent<
|
|||||||
: this.optionByType[this.annotationsType].main;
|
: this.optionByType[this.annotationsType].main;
|
||||||
readonly extraOptionPreference = stringToBoolean(this.optionByType[this.annotationsType].extra);
|
readonly extraOptionPreference = stringToBoolean(this.optionByType[this.annotationsType].extra);
|
||||||
readonly options: DetailsRadioOption<RectangleRedactOption | RemoveRedactionOption>[] = this.#allRectangles
|
readonly options: DetailsRadioOption<RectangleRedactOption | RemoveRedactionOption>[] = this.#allRectangles
|
||||||
? getRectangleRedactOptions('remove')
|
? getRectangleRedactOptions('remove', this.data.redactions)
|
||||||
: getRemoveRedactionOptions(
|
: getRemoveRedactionOptions(
|
||||||
this.data,
|
this.data,
|
||||||
this.isSystemDefault || this.isExtraOptionSystemDefault ? this.#applyToAllDossiers : this.extraOptionPreference,
|
this.isSystemDefault || this.isExtraOptionSystemDefault ? this.#applyToAllDossiers : this.extraOptionPreference,
|
||||||
@ -112,7 +126,7 @@ 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(this.data.file.numberOfPages, true)],
|
||||||
});
|
});
|
||||||
|
|
||||||
readonly selectedOption = toSignal(this.form.get('option').valueChanges.pipe(map(value => value.value)));
|
readonly selectedOption = toSignal(this.form.get('option').valueChanges.pipe(map(value => value.value)));
|
||||||
@ -140,6 +154,14 @@ export class RemoveRedactionDialogComponent extends IqserDialogComponent<
|
|||||||
private readonly _userPreferences: UserPreferenceService,
|
private readonly _userPreferences: UserPreferenceService,
|
||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
|
if (this.#allRectangles && !this.isImported) {
|
||||||
|
prefillPageRange(
|
||||||
|
this.data.redactions[0],
|
||||||
|
this.data.allFileRedactions,
|
||||||
|
this.options as DetailsRadioOption<RectangleRedactOption>[],
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
get hasFalsePositiveOption() {
|
get hasFalsePositiveOption() {
|
||||||
@ -177,19 +199,19 @@ export class RemoveRedactionDialogComponent extends IqserDialogComponent<
|
|||||||
}
|
}
|
||||||
|
|
||||||
save(): void {
|
save(): void {
|
||||||
const optionValue = this.form.controls.option.value.value;
|
const optionValue = this.form.controls.option?.value?.value;
|
||||||
const pageNumbers = parseSelectedPageNumbers(
|
const optionInputValue = this.form.controls.option?.value?.additionalInput?.value;
|
||||||
this.form.get('option').value.additionalInput?.value,
|
const pageNumbers = parseSelectedPageNumbers(optionInputValue, this.data.file);
|
||||||
this.data.file,
|
const positions = [];
|
||||||
this.data.redactions[0],
|
for (const redaction of this.data.redactions) {
|
||||||
);
|
positions.push(parseRectanglePosition(redaction));
|
||||||
const position = parseRectanglePosition(this.data.redactions[0]);
|
}
|
||||||
|
|
||||||
this.close({
|
this.close({
|
||||||
...this.form.getRawValue(),
|
...this.form.getRawValue(),
|
||||||
bulkLocal: optionValue === ResizeOptions.IN_DOCUMENT || optionValue === RectangleRedactOptions.MULTIPLE_PAGES,
|
bulkLocal: optionValue === ResizeOptions.IN_DOCUMENT || optionValue === RectangleRedactOptions.MULTIPLE_PAGES,
|
||||||
pageNumbers,
|
pageNumbers,
|
||||||
position,
|
positions,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,11 @@
|
|||||||
<section class="dialog">
|
<section class="dialog">
|
||||||
<form (submit)="save()" [formGroup]="form">
|
<form (submit)="save()" [formGroup]="form">
|
||||||
<div [innerHTML]="'resize-redaction.dialog.header' | translate: { type: dialogHeaderType }" class="dialog-header heading-l"></div>
|
<div class="dialog-header heading-l">
|
||||||
|
<span
|
||||||
|
[innerHTML]="'resize-redaction.dialog.header' | translate: { type: dialogHeaderType }"
|
||||||
|
[attr.help-mode-key]="dialogTitleHelpKey"
|
||||||
|
></span>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="dialog-content redaction">
|
<div class="dialog-content redaction">
|
||||||
<ng-container *ngIf="!redaction.isImage && !redaction.AREA">
|
<ng-container *ngIf="!redaction.isImage && !redaction.AREA">
|
||||||
@ -48,6 +53,7 @@
|
|||||||
></iqser-icon-button>
|
></iqser-icon-button>
|
||||||
|
|
||||||
<div [translate]="'resize-redaction.dialog.actions.cancel'" class="all-caps-label cancel" mat-dialog-close></div>
|
<div [translate]="'resize-redaction.dialog.actions.cancel'" class="all-caps-label cancel" mat-dialog-close></div>
|
||||||
|
<iqser-help-button *deny="roles.getRss"></iqser-help-button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
|
|||||||
@ -6,12 +6,21 @@ import { MatFormField } from '@angular/material/form-field';
|
|||||||
import { MatOption, MatSelect, MatSelectTrigger } from '@angular/material/select';
|
import { MatOption, MatSelect, MatSelectTrigger } from '@angular/material/select';
|
||||||
import { DetailsRadioOption } from '@common-ui/inputs/details-radio/details-radio-option';
|
import { DetailsRadioOption } from '@common-ui/inputs/details-radio/details-radio-option';
|
||||||
import { DetailsRadioComponent } from '@common-ui/inputs/details-radio/details-radio.component';
|
import { DetailsRadioComponent } from '@common-ui/inputs/details-radio/details-radio.component';
|
||||||
import { CircleButtonComponent, HasScrollbarDirective, IconButtonComponent, IconButtonTypes, IqserDialogComponent } from '@iqser/common-ui';
|
import {
|
||||||
|
CircleButtonComponent,
|
||||||
|
HasScrollbarDirective,
|
||||||
|
HelpButtonComponent,
|
||||||
|
IconButtonComponent,
|
||||||
|
IconButtonTypes,
|
||||||
|
IqserDenyDirective,
|
||||||
|
IqserDialogComponent,
|
||||||
|
} from '@iqser/common-ui';
|
||||||
import { TranslateModule } from '@ngx-translate/core';
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
import { ActiveDossiersService } from '@services/dossiers/active-dossiers.service';
|
import { ActiveDossiersService } from '@services/dossiers/active-dossiers.service';
|
||||||
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 { Roles } from '@users/roles';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
templateUrl: './resize-redaction-dialog.component.html',
|
templateUrl: './resize-redaction-dialog.component.html',
|
||||||
@ -30,6 +39,8 @@ import { ResizeOptions, ResizeRedactionData, ResizeRedactionOption, ResizeRedact
|
|||||||
HasScrollbarDirective,
|
HasScrollbarDirective,
|
||||||
MatDialogClose,
|
MatDialogClose,
|
||||||
NgIf,
|
NgIf,
|
||||||
|
HelpButtonComponent,
|
||||||
|
IqserDenyDirective,
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
export class ResizeRedactionDialogComponent extends IqserDialogComponent<
|
export class ResizeRedactionDialogComponent extends IqserDialogComponent<
|
||||||
@ -67,6 +78,14 @@ export class ResizeRedactionDialogComponent extends IqserDialogComponent<
|
|||||||
return this.data.redaction.HINT ? 'hint' : this.data.redaction.isSkippedImageHint ? 'image' : 'redaction';
|
return this.data.redaction.HINT ? 'hint' : this.data.redaction.isSkippedImageHint ? 'image' : 'redaction';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get dialogTitleHelpKey() {
|
||||||
|
return this.data.redaction.isRecommendation
|
||||||
|
? 'recommendation_resize_DIALOG'
|
||||||
|
: this.data.redaction.isHint
|
||||||
|
? 'hint_resize_DIALOG'
|
||||||
|
: 'redaction_resize_DIALOG';
|
||||||
|
}
|
||||||
|
|
||||||
get displayedDictionaryLabel() {
|
get displayedDictionaryLabel() {
|
||||||
const dictType = this.form.get('dictionary').value;
|
const dictType = this.form.get('dictionary').value;
|
||||||
if (dictType) {
|
if (dictType) {
|
||||||
@ -77,7 +96,7 @@ export class ResizeRedactionDialogComponent extends IqserDialogComponent<
|
|||||||
|
|
||||||
save() {
|
save() {
|
||||||
const formValue = this.form.getRawValue();
|
const formValue = this.form.getRawValue();
|
||||||
const updateDictionary = formValue.option.value === ResizeOptions.IN_DOSSIER;
|
const updateDictionary = formValue.option?.value === ResizeOptions.IN_DOSSIER;
|
||||||
|
|
||||||
super.close({
|
super.close({
|
||||||
comment: formValue.comment,
|
comment: formValue.comment,
|
||||||
@ -93,4 +112,6 @@ export class ResizeRedactionDialogComponent extends IqserDialogComponent<
|
|||||||
option: this.options[0],
|
option: this.options[0],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected readonly roles = Roles;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -17,7 +17,15 @@ import {
|
|||||||
LoadingService,
|
LoadingService,
|
||||||
Toaster,
|
Toaster,
|
||||||
} from '@iqser/common-ui';
|
} from '@iqser/common-ui';
|
||||||
import { copyLocalStorageFiltersValues, FilterService, NestedFilter, processFilters } from '@iqser/common-ui/lib/filtering';
|
import {
|
||||||
|
copyLocalStorageFiltersValues,
|
||||||
|
Filter,
|
||||||
|
FilterService,
|
||||||
|
IFilter,
|
||||||
|
INestedFilter,
|
||||||
|
NestedFilter,
|
||||||
|
processFilters,
|
||||||
|
} from '@iqser/common-ui/lib/filtering';
|
||||||
import { AutoUnsubscribe, Bind, bool, List, OnAttach, OnDetach } from '@iqser/common-ui/lib/utils';
|
import { AutoUnsubscribe, Bind, bool, List, OnAttach, OnDetach } from '@iqser/common-ui/lib/utils';
|
||||||
import { AnnotationWrapper } from '@models/file/annotation.wrapper';
|
import { AnnotationWrapper } from '@models/file/annotation.wrapper';
|
||||||
import { ManualRedactionEntryTypes, ManualRedactionEntryWrapper } from '@models/file/manual-redaction-entry.wrapper';
|
import { ManualRedactionEntryTypes, ManualRedactionEntryWrapper } from '@models/file/manual-redaction-entry.wrapper';
|
||||||
@ -72,6 +80,8 @@ import { FileHeaderComponent } from './components/file-header/file-header.compon
|
|||||||
import { StructuredComponentManagementComponent } from './components/structured-component-management/structured-component-management.component';
|
import { StructuredComponentManagementComponent } from './components/structured-component-management/structured-component-management.component';
|
||||||
import { DocumentInfoService } from './services/document-info.service';
|
import { DocumentInfoService } from './services/document-info.service';
|
||||||
import { RectangleAnnotationDialog } from './dialogs/rectangle-annotation-dialog/rectangle-annotation-dialog.component';
|
import { RectangleAnnotationDialog } from './dialogs/rectangle-annotation-dialog/rectangle-annotation-dialog.component';
|
||||||
|
import { ANNOTATION_ACTION_ICONS, ANNOTATION_ACTIONS } from './utils/constants';
|
||||||
|
import { ComponentLogService } from '@services/entity-services/component-log.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
templateUrl: './file-preview-screen.component.html',
|
templateUrl: './file-preview-screen.component.html',
|
||||||
@ -146,6 +156,7 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
|
|||||||
private readonly _dossierTemplatesService: DossierTemplatesService,
|
private readonly _dossierTemplatesService: DossierTemplatesService,
|
||||||
private readonly _multiSelectService: MultiSelectService,
|
private readonly _multiSelectService: MultiSelectService,
|
||||||
private readonly _documentInfoService: DocumentInfoService,
|
private readonly _documentInfoService: DocumentInfoService,
|
||||||
|
private readonly _componentLogService: ComponentLogService,
|
||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
effect(() => {
|
effect(() => {
|
||||||
@ -180,6 +191,7 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
|
|||||||
|
|
||||||
effect(() => {
|
effect(() => {
|
||||||
this._viewModeService.viewMode();
|
this._viewModeService.viewMode();
|
||||||
|
this.pdf.currentPage();
|
||||||
this.#updateViewMode().then();
|
this.#updateViewMode().then();
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -196,6 +208,11 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
|
|||||||
this._documentInfoService.shown();
|
this._documentInfoService.shown();
|
||||||
this.#updateViewerPosition();
|
this.#updateViewerPosition();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
effect(() => {
|
||||||
|
this.state.componentReferenceIds();
|
||||||
|
this.#rebuildFilters();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
get changed() {
|
get changed() {
|
||||||
@ -248,15 +265,16 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
|
|||||||
this._viewerHeaderService.enableLoadAllAnnotations(); // Reset the button state (since the viewer is reused between files)
|
this._viewerHeaderService.enableLoadAllAnnotations(); // Reset the button state (since the viewer is reused between files)
|
||||||
super.ngOnDetach();
|
super.ngOnDetach();
|
||||||
this.pdf.instance.UI.hotkeys.off('esc');
|
this.pdf.instance.UI.hotkeys.off('esc');
|
||||||
|
this.pdf.instance.UI.iframeWindow.document.removeEventListener('click', this.handleViewerClick);
|
||||||
this._changeRef.markForCheck();
|
this._changeRef.markForCheck();
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnDestroy() {
|
ngOnDestroy() {
|
||||||
this.pdf.instance.UI.hotkeys.off('esc');
|
this.pdf.instance.UI.hotkeys.off('esc');
|
||||||
|
this.pdf.instance.UI.iframeWindow.document.removeEventListener('click', this.handleViewerClick);
|
||||||
super.ngOnDestroy();
|
super.ngOnDestroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bind()
|
|
||||||
handleEscInsideViewer($event: KeyboardEvent) {
|
handleEscInsideViewer($event: KeyboardEvent) {
|
||||||
$event.preventDefault();
|
$event.preventDefault();
|
||||||
if (!!this._annotationManager.selected[0]) {
|
if (!!this._annotationManager.selected[0]) {
|
||||||
@ -277,6 +295,31 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Bind()
|
||||||
|
handleViewerClick(event: MouseEvent) {
|
||||||
|
this._ngZone.run(() => {
|
||||||
|
if (event.isTrusted) {
|
||||||
|
const clickedElement = event.target as HTMLElement;
|
||||||
|
const actionIconClicked = ANNOTATION_ACTION_ICONS.some(action =>
|
||||||
|
(clickedElement as HTMLImageElement).src?.includes(action),
|
||||||
|
);
|
||||||
|
const actionClicked = ANNOTATION_ACTIONS.some(action => clickedElement.getAttribute('aria-label')?.includes(action));
|
||||||
|
if (this._multiSelectService.active() && !actionIconClicked && !actionClicked) {
|
||||||
|
if (
|
||||||
|
clickedElement.querySelector('#selectionrect') ||
|
||||||
|
clickedElement.id === `pageWidgetContainer${this.pdf.currentPage()}`
|
||||||
|
) {
|
||||||
|
if (!this._annotationManager.selected.length) {
|
||||||
|
this._multiSelectService.deactivate();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this._multiSelectService.deactivate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
async ngOnAttach(previousRoute: ActivatedRouteSnapshot) {
|
async ngOnAttach(previousRoute: ActivatedRouteSnapshot) {
|
||||||
if (!this.state.file().canBeOpened) {
|
if (!this.state.file().canBeOpened) {
|
||||||
return this.#navigateToDossier();
|
return this.#navigateToDossier();
|
||||||
@ -305,8 +348,16 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
|
|||||||
|
|
||||||
this.pdfProxyService.configureElements();
|
this.pdfProxyService.configureElements();
|
||||||
this.#restoreOldFilters();
|
this.#restoreOldFilters();
|
||||||
this.pdf.instance.UI.hotkeys.on('esc', this.handleEscInsideViewer);
|
this.pdf.instance.UI.hotkeys.on('esc', {
|
||||||
|
keydown: (e: KeyboardEvent) => this.pdf.escKeyHandler.keydown(e),
|
||||||
|
keyup: (e: KeyboardEvent) => {
|
||||||
|
this.pdf.escKeyHandler.keyup(e);
|
||||||
|
this.handleEscInsideViewer(e);
|
||||||
|
},
|
||||||
|
});
|
||||||
this._viewerHeaderService.resetLayers();
|
this._viewerHeaderService.resetLayers();
|
||||||
|
this.pdf.instance.UI.iframeWindow.document.removeEventListener('click', this.handleViewerClick);
|
||||||
|
this.pdf.instance.UI.iframeWindow.document.addEventListener('click', this.handleViewerClick);
|
||||||
}
|
}
|
||||||
|
|
||||||
async openRectangleAnnotationDialog(manualRedactionEntryWrapper: ManualRedactionEntryWrapper) {
|
async openRectangleAnnotationDialog(manualRedactionEntryWrapper: ManualRedactionEntryWrapper) {
|
||||||
@ -394,6 +445,7 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
|
|||||||
switch (viewMode) {
|
switch (viewMode) {
|
||||||
case ViewModes.STANDARD: {
|
case ViewModes.STANDARD: {
|
||||||
const wrappers = this._fileDataService.annotations();
|
const wrappers = this._fileDataService.annotations();
|
||||||
|
const multiSelectActive = this._multiSelectService.active();
|
||||||
// TODO: const wrappers = untracked(this._fileDataService.annotations);
|
// TODO: const wrappers = untracked(this._fileDataService.annotations);
|
||||||
const ocrAnnotationIds = wrappers.filter(a => a.isOCR).map(a => a.id);
|
const ocrAnnotationIds = wrappers.filter(a => a.isOCR).map(a => a.id);
|
||||||
const standardEntries = annotations
|
const standardEntries = annotations
|
||||||
@ -408,7 +460,7 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
|
|||||||
this._readableRedactionsService.setAnnotationsColor(standardEntries, 'annotationColor');
|
this._readableRedactionsService.setAnnotationsColor(standardEntries, 'annotationColor');
|
||||||
this._readableRedactionsService.setAnnotationsOpacity(standardEntries, true);
|
this._readableRedactionsService.setAnnotationsOpacity(standardEntries, true);
|
||||||
this._annotationManager.show(standardEntries);
|
this._annotationManager.show(standardEntries);
|
||||||
this._annotationManager.hide(nonStandardEntries);
|
this._annotationManager.hide(nonStandardEntries, multiSelectActive);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case ViewModes.DELTA: {
|
case ViewModes.DELTA: {
|
||||||
@ -508,8 +560,13 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
|
|||||||
#rebuildFilters() {
|
#rebuildFilters() {
|
||||||
const startTime = new Date().getTime();
|
const startTime = new Date().getTime();
|
||||||
|
|
||||||
const annotationFilters = this._annotationProcessingService.getAnnotationFilter();
|
let annotationFilters = this._annotationProcessingService.getAnnotationFilter();
|
||||||
const primaryFilters = this._filterService.getGroup('primaryFilters')?.filters;
|
const primaryFilters = this._filterService.getGroup('primaryFilters')?.filters;
|
||||||
|
|
||||||
|
if (this.isDocumine) {
|
||||||
|
annotationFilters = this.#filterAnnotationFilters(annotationFilters);
|
||||||
|
}
|
||||||
|
|
||||||
this._filterService.addFilterGroup({
|
this._filterService.addFilterGroup({
|
||||||
slug: 'primaryFilters',
|
slug: 'primaryFilters',
|
||||||
filterTemplate: this._filterTemplate,
|
filterTemplate: this._filterTemplate,
|
||||||
@ -802,6 +859,38 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#filterAnnotationFilters(annotationFilters: INestedFilter[]) {
|
||||||
|
const components = this._componentLogService.all;
|
||||||
|
const filteredComponentIds = untracked(this.state.componentReferenceIds);
|
||||||
|
|
||||||
|
if (filteredComponentIds && annotationFilters) {
|
||||||
|
const filteredComponentIdsSet = new Set(filteredComponentIds);
|
||||||
|
|
||||||
|
const references = new Set<string>();
|
||||||
|
for (const component of components) {
|
||||||
|
for (const componentValue of component.componentValues) {
|
||||||
|
for (const reference of componentValue.entityReferences) {
|
||||||
|
if (filteredComponentIdsSet.has(reference.id)) {
|
||||||
|
references.add(reference.type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return annotationFilters
|
||||||
|
.map(filter => {
|
||||||
|
const filteredChildren = filter.children.filter(c => references.has(c.label.replace(/ /g, '_').toLowerCase()));
|
||||||
|
if (filteredChildren.length) {
|
||||||
|
return { ...filter, children: filteredChildren };
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
})
|
||||||
|
.filter(f => f !== null);
|
||||||
|
}
|
||||||
|
|
||||||
|
return annotationFilters;
|
||||||
|
}
|
||||||
|
|
||||||
#updateViewerPosition() {
|
#updateViewerPosition() {
|
||||||
if (this.isDocumine) {
|
if (this.isDocumine) {
|
||||||
if (this._documentInfoService.shown()) {
|
if (this._documentInfoService.shown()) {
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { IqserDialog } from '@common-ui/dialog/iqser-dialog.service';
|
import { IqserDialog } from '@common-ui/dialog/iqser-dialog.service';
|
||||||
import { getConfig } from '@iqser/common-ui';
|
import { getConfig } from '@iqser/common-ui';
|
||||||
import { List, log } from '@iqser/common-ui/lib/utils';
|
import { List } from '@iqser/common-ui/lib/utils';
|
||||||
import { AnnotationPermissions } from '@models/file/annotation.permissions';
|
import { AnnotationPermissions } from '@models/file/annotation.permissions';
|
||||||
import { AnnotationWrapper } from '@models/file/annotation.wrapper';
|
import { AnnotationWrapper } from '@models/file/annotation.wrapper';
|
||||||
import { Core } from '@pdftron/webviewer';
|
import { Core } from '@pdftron/webviewer';
|
||||||
@ -10,7 +10,6 @@ import {
|
|||||||
EarmarkOperation,
|
EarmarkOperation,
|
||||||
type IBulkLocalRemoveRequest,
|
type IBulkLocalRemoveRequest,
|
||||||
IBulkRecategorizationRequest,
|
IBulkRecategorizationRequest,
|
||||||
ILegalBasisChangeRequest,
|
|
||||||
IRecategorizationRequest,
|
IRecategorizationRequest,
|
||||||
IRectangle,
|
IRectangle,
|
||||||
type IRemoveRedactionRequest,
|
type IRemoveRedactionRequest,
|
||||||
@ -50,7 +49,7 @@ import { FilePreviewDialogService } from './file-preview-dialog.service';
|
|||||||
import { FilePreviewStateService } from './file-preview-state.service';
|
import { FilePreviewStateService } from './file-preview-state.service';
|
||||||
import { ManualRedactionService } from './manual-redaction.service';
|
import { ManualRedactionService } from './manual-redaction.service';
|
||||||
import { SkippedService } from './skipped.service';
|
import { SkippedService } from './skipped.service';
|
||||||
import { NON_READABLE_CONTENT } from '../dialogs/rectangle-annotation-dialog/rectangle-annotation-dialog.component';
|
import { ForceAnnotationDialogComponent } from '../dialogs/force-redaction-dialog/force-annotation-dialog.component';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class AnnotationActionsService {
|
export class AnnotationActionsService {
|
||||||
@ -81,41 +80,49 @@ export class AnnotationActionsService {
|
|||||||
this._dialogService.openDialog('highlightAction', data);
|
this._dialogService.openDialog('highlightAction', data);
|
||||||
}
|
}
|
||||||
|
|
||||||
forceAnnotation(annotations: AnnotationWrapper[], hint: boolean = false) {
|
async forceAnnotation(annotations: AnnotationWrapper[], hint: boolean = false) {
|
||||||
const { dossierId, fileId } = this._state;
|
const { dossierId, fileId } = this._state;
|
||||||
const data = { dossier: this._state.dossier(), annotations, hint };
|
const image = annotations.every(a => a.isImage);
|
||||||
this._dialogService.openDialog('forceAnnotation', data, (request: ILegalBasisChangeRequest) => {
|
const data = { dossier: this._state.dossier(), annotations, hint, image };
|
||||||
let obs$: Observable<unknown>;
|
|
||||||
if (request.option === ForceAnnotationOptions.ONLY_HERE) {
|
const dialogRef = this._iqserDialog.openDefault(ForceAnnotationDialogComponent, { data });
|
||||||
obs$ = this._manualRedactionService.bulkForce(
|
const result = await dialogRef.result();
|
||||||
annotations.map(a => ({ ...request, annotationId: a.id })),
|
|
||||||
dossierId,
|
if (!result) {
|
||||||
fileId,
|
return;
|
||||||
annotations[0].isIgnoredHint,
|
}
|
||||||
);
|
|
||||||
} else {
|
let obs$: Observable<unknown>;
|
||||||
const addAnnotationRequest = annotations.map(a => ({
|
if (result.option === ForceAnnotationOptions.ONLY_HERE || hint || image) {
|
||||||
comment: { text: request.comment },
|
obs$ = this._manualRedactionService.bulkForce(
|
||||||
legalBasis: request.legalBasis,
|
annotations.map(a => ({ ...result, annotationId: a.id })),
|
||||||
reason: request.reason,
|
dossierId,
|
||||||
positions: a.positions,
|
fileId,
|
||||||
type: a.type,
|
annotations[0].isIgnoredHint,
|
||||||
value: a.value,
|
);
|
||||||
}));
|
} else {
|
||||||
obs$ = this._manualRedactionService.addAnnotation(addAnnotationRequest, dossierId, fileId, {
|
const addAnnotationRequest = annotations.map(a => ({
|
||||||
hint,
|
comment: result.comment,
|
||||||
bulkLocal: true,
|
legalBasis: result.legalBasis,
|
||||||
});
|
reason: result.reason,
|
||||||
}
|
positions: a.positions,
|
||||||
this.#processObsAndEmit(obs$).then();
|
type: a.type,
|
||||||
});
|
value: a.value,
|
||||||
|
}));
|
||||||
|
obs$ = this._manualRedactionService.addAnnotation(addAnnotationRequest, dossierId, fileId, {
|
||||||
|
hint,
|
||||||
|
bulkLocal: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
this.#processObsAndEmit(obs$).then();
|
||||||
}
|
}
|
||||||
|
|
||||||
async editRedaction(annotations: AnnotationWrapper[]) {
|
async editRedaction(annotations: AnnotationWrapper[]) {
|
||||||
const { dossierId, file } = this._state;
|
const { dossierId, file } = this._state;
|
||||||
const includeUnprocessed = annotations.every(annotation => this.#includeUnprocessed(annotation, true));
|
const allFileAnnotations = this._fileDataService.annotations();
|
||||||
const data = {
|
const data = {
|
||||||
annotations,
|
annotations,
|
||||||
|
allFileAnnotations,
|
||||||
dossierId,
|
dossierId,
|
||||||
file: file(),
|
file: file(),
|
||||||
};
|
};
|
||||||
@ -145,15 +152,11 @@ export class AnnotationActionsService {
|
|||||||
return body;
|
return body;
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
const originTypes = annotations.map(a => a.type);
|
|
||||||
const originLegalBases = annotations.map(a => a.legalBasis);
|
|
||||||
recategorizeBody = {
|
recategorizeBody = {
|
||||||
value: annotations[0].value,
|
value: result.value ?? annotations[0].value,
|
||||||
type: result.type,
|
type: result.type,
|
||||||
legalBasis: result.legalBasis,
|
legalBasis: result.legalBasis,
|
||||||
section: result.section,
|
section: result.section,
|
||||||
originTypes,
|
|
||||||
originLegalBases,
|
|
||||||
rectangle: annotations[0].AREA,
|
rectangle: annotations[0].AREA,
|
||||||
pageNumbers: result.pageNumbers,
|
pageNumbers: result.pageNumbers,
|
||||||
position: result.position,
|
position: result.position,
|
||||||
@ -162,16 +165,13 @@ export class AnnotationActionsService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
await this.#processObsAndEmit(
|
await this.#processObsAndEmit(
|
||||||
this._manualRedactionService
|
this._manualRedactionService.recategorizeRedactions(
|
||||||
.recategorizeRedactions(
|
recategorizeBody,
|
||||||
recategorizeBody,
|
dossierId,
|
||||||
dossierId,
|
file().id,
|
||||||
file().id,
|
this.#getChangedFields(annotations, result),
|
||||||
this.#getChangedFields(annotations, result),
|
result.option === RedactOrHintOptions.IN_DOCUMENT || !!result.pageNumbers.length,
|
||||||
includeUnprocessed,
|
),
|
||||||
result.option === RedactOrHintOptions.IN_DOCUMENT || !!result.pageNumbers.length,
|
|
||||||
)
|
|
||||||
.pipe(log()),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -184,9 +184,11 @@ export class AnnotationActionsService {
|
|||||||
const dossierTemplate = this._dossierTemplatesService.find(this._state.dossierTemplateId);
|
const dossierTemplate = this._dossierTemplatesService.find(this._state.dossierTemplateId);
|
||||||
const isApprover = this._permissionsService.isApprover(this._state.dossier());
|
const isApprover = this._permissionsService.isApprover(this._state.dossier());
|
||||||
const { file } = this._state;
|
const { file } = this._state;
|
||||||
|
const allFileRedactions = this._fileDataService.annotations();
|
||||||
|
|
||||||
const data = {
|
const data = {
|
||||||
redactions,
|
redactions,
|
||||||
|
allFileRedactions,
|
||||||
file: file(),
|
file: file(),
|
||||||
dossier: this._state.dossier(),
|
dossier: this._state.dossier(),
|
||||||
falsePositiveContext: redactions.map(r => this.#getFalsePositiveText(r)),
|
falsePositiveContext: redactions.map(r => this.#getFalsePositiveText(r)),
|
||||||
@ -202,8 +204,8 @@ export class AnnotationActionsService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
result.option.value === RemoveRedactionOptions.FALSE_POSITIVE ||
|
result.option?.value === RemoveRedactionOptions.FALSE_POSITIVE ||
|
||||||
result.option.value === RemoveRedactionOptions.DO_NOT_RECOMMEND
|
result.option?.value === RemoveRedactionOptions.DO_NOT_RECOMMEND
|
||||||
) {
|
) {
|
||||||
this.#setAsFalsePositive(redactions, result);
|
this.#setAsFalsePositive(redactions, result);
|
||||||
} else {
|
} else {
|
||||||
@ -223,9 +225,9 @@ export class AnnotationActionsService {
|
|||||||
this.#processObsAndEmit(request$).then();
|
this.#processObsAndEmit(request$).then();
|
||||||
}
|
}
|
||||||
|
|
||||||
async convertRecommendationToAnnotation(recommendations: AnnotationWrapper[]) {
|
async convertRecommendationToAnnotation(recommendations: AnnotationWrapper[], action: 'accept' | 'resize') {
|
||||||
const { dossierId, fileId } = this._state;
|
const { dossierId, fileId } = this._state;
|
||||||
const data = this.#getRedactRecommendationDialogData(recommendations) as RedactRecommendationData;
|
const data = this.#getRedactRecommendationDialogData(recommendations, action) as RedactRecommendationData;
|
||||||
const dialog = this._iqserDialog.openDefault(RedactRecommendationDialogComponent, { data });
|
const dialog = this._iqserDialog.openDefault(RedactRecommendationDialogComponent, { data });
|
||||||
const result = await dialog.result();
|
const result = await dialog.result();
|
||||||
if (!result) {
|
if (!result) {
|
||||||
@ -265,7 +267,6 @@ export class AnnotationActionsService {
|
|||||||
|
|
||||||
async acceptResize(annotation: AnnotationWrapper, permissions: AnnotationPermissions): Promise<void> {
|
async acceptResize(annotation: AnnotationWrapper, permissions: AnnotationPermissions): Promise<void> {
|
||||||
const textAndPositions = await this.#extractTextAndPositions(annotation.id);
|
const textAndPositions = await this.#extractTextAndPositions(annotation.id);
|
||||||
const includeUnprocessed = this.#includeUnprocessed(annotation);
|
|
||||||
if (annotation.isRecommendation) {
|
if (annotation.isRecommendation) {
|
||||||
const recommendation = {
|
const recommendation = {
|
||||||
...annotation,
|
...annotation,
|
||||||
@ -276,7 +277,7 @@ export class AnnotationActionsService {
|
|||||||
recommendation.isRemoved = true;
|
recommendation.isRemoved = true;
|
||||||
await this._annotationDrawService.draw([recommendation], this._skippedService.hideSkipped(), this._state.dossierTemplateId);
|
await this._annotationDrawService.draw([recommendation], this._skippedService.hideSkipped(), this._state.dossierTemplateId);
|
||||||
|
|
||||||
return this.convertRecommendationToAnnotation([recommendation]);
|
return this.convertRecommendationToAnnotation([recommendation], 'resize');
|
||||||
}
|
}
|
||||||
|
|
||||||
const dossier = this._state.dossier();
|
const dossier = this._state.dossier();
|
||||||
@ -316,16 +317,16 @@ export class AnnotationActionsService {
|
|||||||
await this.cancelResize(annotation);
|
await this.cancelResize(annotation);
|
||||||
|
|
||||||
const { fileId, dossierId } = this._state;
|
const { fileId, dossierId } = this._state;
|
||||||
const request = this._manualRedactionService.resize([resizeRequest], dossierId, fileId, includeUnprocessed);
|
const request = this._manualRedactionService.resize([resizeRequest], dossierId, fileId);
|
||||||
return this.#processObsAndEmit(request);
|
return this.#processObsAndEmit(request);
|
||||||
}
|
}
|
||||||
|
|
||||||
async cancelResize(annotationWrapper: AnnotationWrapper) {
|
async cancelResize(annotationWrapper: AnnotationWrapper) {
|
||||||
this._annotationManager.resizingAnnotationId = undefined;
|
this._annotationManager.resizingAnnotationId = undefined;
|
||||||
this._annotationManager.annotationHasBeenResized = false;
|
this._annotationManager.annotationHasBeenResized = false;
|
||||||
|
this._annotationManager.deselect();
|
||||||
this._annotationManager.delete(annotationWrapper);
|
this._annotationManager.delete(annotationWrapper);
|
||||||
await this._annotationDrawService.draw([annotationWrapper], this._skippedService.hideSkipped(), this._state.dossierTemplateId);
|
await this._annotationDrawService.draw([annotationWrapper], this._skippedService.hideSkipped(), this._state.dossierTemplateId);
|
||||||
this._annotationManager.deselect();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#generateRectangle(annotationWrapper: AnnotationWrapper) {
|
#generateRectangle(annotationWrapper: AnnotationWrapper) {
|
||||||
@ -472,13 +473,13 @@ export class AnnotationActionsService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#removeRedaction(redactions: AnnotationWrapper[], dialogResult: RemoveRedactionResult) {
|
#removeRedaction(redactions: AnnotationWrapper[], dialogResult: RemoveRedactionResult) {
|
||||||
const removeFromDictionary = dialogResult.option.value === RemoveRedactionOptions.IN_DOSSIER;
|
const removeFromDictionary = dialogResult.option?.value === RemoveRedactionOptions.IN_DOSSIER;
|
||||||
const includeUnprocessed = redactions.every(redaction => this.#includeUnprocessed(redaction, true));
|
|
||||||
const body = this.#getRemoveRedactionBody(redactions, dialogResult);
|
const body = this.#getRemoveRedactionBody(redactions, dialogResult);
|
||||||
// todo: might not be correct, probably shouldn't get to this point if they are not all the same
|
// todo: might not be correct, probably shouldn't get to this point if they are not all the same
|
||||||
const isHint = redactions.every(r => r.isHint);
|
const isHint = redactions.every(r => r.isHint);
|
||||||
const { dossierId, fileId } = this._state;
|
const { dossierId, fileId } = this._state;
|
||||||
const maximumNumberEntries = 100;
|
const maximumNumberEntries = 100;
|
||||||
|
const bulkLocal = dialogResult.bulkLocal || !!dialogResult.pageNumbers.length;
|
||||||
if (removeFromDictionary && (body as List<IRemoveRedactionRequest>).length > maximumNumberEntries) {
|
if (removeFromDictionary && (body as List<IRemoveRedactionRequest>).length > maximumNumberEntries) {
|
||||||
const requests = body as List<IRemoveRedactionRequest>;
|
const requests = body as List<IRemoveRedactionRequest>;
|
||||||
const splitNumber = Math.floor(requests.length / maximumNumberEntries);
|
const splitNumber = Math.floor(requests.length / maximumNumberEntries);
|
||||||
@ -493,16 +494,28 @@ export class AnnotationActionsService {
|
|||||||
|
|
||||||
const promises = [];
|
const promises = [];
|
||||||
for (const split of splitRequests) {
|
for (const split of splitRequests) {
|
||||||
|
promises.push(
|
||||||
|
firstValueFrom(
|
||||||
|
this._manualRedactionService.removeRedaction(split, dossierId, fileId, removeFromDictionary, isHint, bulkLocal),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Promise.all(promises).finally(() => this._fileDataService.annotationsChanged());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bulkLocal) {
|
||||||
|
const promises = [];
|
||||||
|
for (const request of body) {
|
||||||
promises.push(
|
promises.push(
|
||||||
firstValueFrom(
|
firstValueFrom(
|
||||||
this._manualRedactionService.removeRedaction(
|
this._manualRedactionService.removeRedaction(
|
||||||
split,
|
request as IBulkLocalRemoveRequest,
|
||||||
dossierId,
|
dossierId,
|
||||||
fileId,
|
fileId,
|
||||||
removeFromDictionary,
|
removeFromDictionary,
|
||||||
isHint,
|
isHint,
|
||||||
includeUnprocessed,
|
bulkLocal,
|
||||||
dialogResult.bulkLocal,
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@ -510,16 +523,9 @@ export class AnnotationActionsService {
|
|||||||
Promise.all(promises).finally(() => this._fileDataService.annotationsChanged());
|
Promise.all(promises).finally(() => this._fileDataService.annotationsChanged());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.#processObsAndEmit(
|
this.#processObsAndEmit(
|
||||||
this._manualRedactionService.removeRedaction(
|
this._manualRedactionService.removeRedaction(body, dossierId, fileId, removeFromDictionary, isHint, bulkLocal),
|
||||||
body,
|
|
||||||
dossierId,
|
|
||||||
fileId,
|
|
||||||
removeFromDictionary,
|
|
||||||
isHint,
|
|
||||||
includeUnprocessed,
|
|
||||||
dialogResult.bulkLocal || !!dialogResult.pageNumbers.length,
|
|
||||||
),
|
|
||||||
).then();
|
).then();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -544,7 +550,7 @@ export class AnnotationActionsService {
|
|||||||
return this._iqserDialog.openDefault(EditRedactionDialogComponent, { data });
|
return this._iqserDialog.openDefault(EditRedactionDialogComponent, { data });
|
||||||
}
|
}
|
||||||
|
|
||||||
#getRedactRecommendationDialogData(annotations: AnnotationWrapper[]) {
|
#getRedactRecommendationDialogData(annotations: AnnotationWrapper[], action: 'accept' | 'resize') {
|
||||||
const dossierTemplate = this._dossierTemplatesService.find(this._state.dossierTemplateId);
|
const dossierTemplate = this._dossierTemplatesService.find(this._state.dossierTemplateId);
|
||||||
const isApprover = this._permissionsService.isApprover(this._state.dossier());
|
const isApprover = this._permissionsService.isApprover(this._state.dossier());
|
||||||
const applyDictionaryUpdatesToAllDossiersByDefault = dossierTemplate.applyDictionaryUpdatesToAllDossiersByDefault;
|
const applyDictionaryUpdatesToAllDossiersByDefault = dossierTemplate.applyDictionaryUpdatesToAllDossiersByDefault;
|
||||||
@ -554,6 +560,7 @@ export class AnnotationActionsService {
|
|||||||
dossierId: this._state.dossierId,
|
dossierId: this._state.dossierId,
|
||||||
applyToAllDossiers: isApprover ? applyDictionaryUpdatesToAllDossiersByDefault : false,
|
applyToAllDossiers: isApprover ? applyDictionaryUpdatesToAllDossiersByDefault : false,
|
||||||
isApprover,
|
isApprover,
|
||||||
|
action,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -576,43 +583,26 @@ export class AnnotationActionsService {
|
|||||||
return { changes: changedFields.join(', ') };
|
return { changes: changedFields.join(', ') };
|
||||||
}
|
}
|
||||||
|
|
||||||
//TODO this is temporary, based on RED-8950. Should be removed when a better solution will be found
|
|
||||||
#includeUnprocessed(annotation: AnnotationWrapper, isRemoveOrRecategorize = false) {
|
|
||||||
const processed = annotation.entry.manualChanges.at(-1)?.processed;
|
|
||||||
if (!processed) {
|
|
||||||
const autoAnalysisDisabled = this._state.file().excludedFromAutomaticAnalysis;
|
|
||||||
const addedLocallyWhileDisabled = annotation.manual;
|
|
||||||
if (autoAnalysisDisabled) {
|
|
||||||
return addedLocallyWhileDisabled;
|
|
||||||
}
|
|
||||||
return isRemoveOrRecategorize && addedLocallyWhileDisabled;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
#getRemoveRedactionBody(
|
#getRemoveRedactionBody(
|
||||||
redactions: AnnotationWrapper[],
|
redactions: AnnotationWrapper[],
|
||||||
dialogResult: RemoveRedactionResult,
|
dialogResult: RemoveRedactionResult,
|
||||||
): List<IRemoveRedactionRequest> | IBulkLocalRemoveRequest {
|
): List<IRemoveRedactionRequest | IBulkLocalRemoveRequest> {
|
||||||
if (dialogResult.bulkLocal || !!dialogResult.pageNumbers.length) {
|
if (dialogResult.bulkLocal || !!dialogResult.pageNumbers.length) {
|
||||||
const redaction = redactions[0];
|
return dialogResult.positions.map((position, index) => ({
|
||||||
return {
|
value: redactions[index].value,
|
||||||
value: redaction.value,
|
rectangle: redactions[index].AREA,
|
||||||
rectangle: redaction.value === NON_READABLE_CONTENT,
|
|
||||||
originTypes: [redaction.entry.type],
|
|
||||||
originLegalBases: [redaction.legalBasis],
|
|
||||||
pageNumbers: dialogResult.pageNumbers,
|
pageNumbers: dialogResult.pageNumbers,
|
||||||
position: dialogResult.position,
|
position: position,
|
||||||
comment: dialogResult.comment,
|
comment: dialogResult.comment,
|
||||||
};
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
return redactions.map(redaction => ({
|
return redactions.map(redaction => ({
|
||||||
annotationId: redaction.id,
|
annotationId: redaction.id,
|
||||||
value: redaction.value,
|
value: redaction.value,
|
||||||
comment: dialogResult.comment,
|
comment: dialogResult.comment,
|
||||||
removeFromDictionary: dialogResult.option.value === RemoveRedactionOptions.IN_DOSSIER,
|
removeFromDictionary: dialogResult.option?.value === RemoveRedactionOptions.IN_DOSSIER,
|
||||||
removeFromAllDossiers: !!dialogResult.option.additionalCheck?.checked || !!dialogResult.applyToAllDossiers,
|
removeFromAllDossiers: !!dialogResult.option?.additionalCheck?.checked || !!dialogResult.applyToAllDossiers,
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -270,7 +270,7 @@ export class FileDataService extends EntitiesService<AnnotationWrapper, Annotati
|
|||||||
const viewTime = timestampOf(viewedPage.viewedTime) - DELTA_VIEW_TIME;
|
const viewTime = timestampOf(viewedPage.viewedTime) - DELTA_VIEW_TIME;
|
||||||
let changeOccurredAfterPageIsViewed = lastChange && timestampOf(lastChange.dateTime) > viewTime;
|
let changeOccurredAfterPageIsViewed = lastChange && timestampOf(lastChange.dateTime) > viewTime;
|
||||||
|
|
||||||
if (changeOccurredAfterPageIsViewed) {
|
if (changeOccurredAfterPageIsViewed !== undefined) {
|
||||||
this.#markPageAsUnseenIfNeeded(viewedPage, lastChange.dateTime);
|
this.#markPageAsUnseenIfNeeded(viewedPage, lastChange.dateTime);
|
||||||
return lastChange?.type;
|
return lastChange?.type;
|
||||||
}
|
}
|
||||||
@ -281,7 +281,7 @@ export class FileDataService extends EntitiesService<AnnotationWrapper, Annotati
|
|||||||
const processedTime = lastManualChange?.processedDate;
|
const processedTime = lastManualChange?.processedDate;
|
||||||
changeOccurredAfterPageIsViewed = processedTime && timestampOf(processedTime) > viewTime;
|
changeOccurredAfterPageIsViewed = processedTime && timestampOf(processedTime) > viewTime;
|
||||||
|
|
||||||
if (changeOccurredAfterPageIsViewed) {
|
if (changeOccurredAfterPageIsViewed !== undefined) {
|
||||||
this.#markPageAsUnseenIfNeeded(viewedPage, processedTime);
|
this.#markPageAsUnseenIfNeeded(viewedPage, processedTime);
|
||||||
return ChangeTypes.CHANGED;
|
return ChangeTypes.CHANGED;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,10 +3,9 @@ import { MatDialog } from '@angular/material/dialog';
|
|||||||
import { ConfirmationDialogComponent, DialogConfig, DialogService } from '@iqser/common-ui';
|
import { ConfirmationDialogComponent, DialogConfig, DialogService } from '@iqser/common-ui';
|
||||||
import { ChangeLegalBasisDialogComponent } from '../dialogs/change-legal-basis-dialog/change-legal-basis-dialog.component';
|
import { ChangeLegalBasisDialogComponent } from '../dialogs/change-legal-basis-dialog/change-legal-basis-dialog.component';
|
||||||
import { DocumentInfoDialogComponent } from '../dialogs/document-info-dialog/document-info-dialog.component';
|
import { DocumentInfoDialogComponent } from '../dialogs/document-info-dialog/document-info-dialog.component';
|
||||||
import { ForceAnnotationDialogComponent } from '../dialogs/force-redaction-dialog/force-annotation-dialog.component';
|
|
||||||
import { HighlightActionDialogComponent } from '../dialogs/highlight-action-dialog/highlight-action-dialog.component';
|
import { HighlightActionDialogComponent } from '../dialogs/highlight-action-dialog/highlight-action-dialog.component';
|
||||||
|
|
||||||
type DialogType = 'confirm' | 'documentInfo' | 'changeLegalBasis' | 'forceAnnotation' | 'highlightAction';
|
type DialogType = 'confirm' | 'documentInfo' | 'changeLegalBasis' | 'highlightAction';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class FilePreviewDialogService extends DialogService<DialogType> {
|
export class FilePreviewDialogService extends DialogService<DialogType> {
|
||||||
@ -22,9 +21,6 @@ export class FilePreviewDialogService extends DialogService<DialogType> {
|
|||||||
changeLegalBasis: {
|
changeLegalBasis: {
|
||||||
component: ChangeLegalBasisDialogComponent,
|
component: ChangeLegalBasisDialogComponent,
|
||||||
},
|
},
|
||||||
forceAnnotation: {
|
|
||||||
component: ForceAnnotationDialogComponent,
|
|
||||||
},
|
|
||||||
highlightAction: {
|
highlightAction: {
|
||||||
component: HighlightActionDialogComponent,
|
component: HighlightActionDialogComponent,
|
||||||
},
|
},
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import { HttpEvent, HttpEventType, HttpProgressEvent, HttpResponse } from '@angular/common/http';
|
import { HttpEvent, HttpEventType, HttpProgressEvent, HttpResponse } from '@angular/common/http';
|
||||||
import { computed, effect, inject, Injectable, signal, Signal, WritableSignal } from '@angular/core';
|
import { computed, effect, inject, Injectable, signal, Signal, WritableSignal } from '@angular/core';
|
||||||
import { takeUntilDestroyed, toSignal } from '@angular/core/rxjs-interop';
|
import { takeUntilDestroyed, toObservable, toSignal } from '@angular/core/rxjs-interop';
|
||||||
import { LoadingService, wipeCache } from '@iqser/common-ui';
|
import { LoadingService, wipeCache } from '@iqser/common-ui';
|
||||||
import { getParam } from '@iqser/common-ui/lib/utils';
|
import { getParam } from '@iqser/common-ui/lib/utils';
|
||||||
import { TranslateService } from '@ngx-translate/core';
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
@ -14,7 +14,7 @@ import { FilesMapService } from '@services/files/files-map.service';
|
|||||||
import { FilesService } from '@services/files/files.service';
|
import { FilesService } from '@services/files/files.service';
|
||||||
import { PermissionsService } from '@services/permissions.service';
|
import { PermissionsService } from '@services/permissions.service';
|
||||||
import { NGXLogger } from 'ngx-logger';
|
import { NGXLogger } from 'ngx-logger';
|
||||||
import { BehaviorSubject, firstValueFrom, from, merge, Observable, of, pairwise, Subject, switchMap } from 'rxjs';
|
import { firstValueFrom, from, merge, Observable, of, pairwise, Subject, switchMap } from 'rxjs';
|
||||||
import { filter, map, startWith, tap, withLatestFrom } from 'rxjs/operators';
|
import { filter, map, startWith, tap, withLatestFrom } from 'rxjs/operators';
|
||||||
import { ViewModeService } from './view-mode.service';
|
import { ViewModeService } from './view-mode.service';
|
||||||
|
|
||||||
@ -42,7 +42,7 @@ export class FilePreviewStateService {
|
|||||||
readonly dossierDictionary: Signal<Dictionary>;
|
readonly dossierDictionary: Signal<Dictionary>;
|
||||||
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 = signal<string[]>([]);
|
||||||
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);
|
||||||
@ -64,7 +64,7 @@ export class FilePreviewStateService {
|
|||||||
this.dossier = toSignal(dossiersServiceResolver().getEntityChanged$(this.dossierId));
|
this.dossier = toSignal(dossiersServiceResolver().getEntityChanged$(this.dossierId));
|
||||||
this.file$ = inject(FilesMapService).watch$(this.dossierId, this.fileId);
|
this.file$ = inject(FilesMapService).watch$(this.dossierId, this.fileId);
|
||||||
this.file = toSignal(this.file$);
|
this.file = toSignal(this.file$);
|
||||||
this.componentReferenceIds$ = this.#componentReferenceIds$.asObservable();
|
this.componentReferenceIds$ = toObservable(this.componentReferenceIds);
|
||||||
this.excludedPages = signal(this.file().excludedPages);
|
this.excludedPages = signal(this.file().excludedPages);
|
||||||
this.isWritable = computed(() => {
|
this.isWritable = computed(() => {
|
||||||
const isWritable = this._permissionsService.canPerformAnnotationActions(this.file(), this.dossier());
|
const isWritable = this._permissionsService.canPerformAnnotationActions(this.file(), this.dossier());
|
||||||
@ -94,10 +94,6 @@ export class FilePreviewStateService {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
set componentReferenceIds(ids: string[]) {
|
|
||||||
this.#componentReferenceIds$.next(ids);
|
|
||||||
}
|
|
||||||
|
|
||||||
get dictionaries(): Dictionary[] {
|
get dictionaries(): Dictionary[] {
|
||||||
const dictionaries = this._dictionariesMapService.get(this.dossierTemplateId);
|
const dictionaries = this._dictionariesMapService.get(this.dossierTemplateId);
|
||||||
if (this.dossierDictionary()) {
|
if (this.dossierDictionary()) {
|
||||||
@ -128,15 +124,12 @@ export class FilePreviewStateService {
|
|||||||
|
|
||||||
get #dossierFilesChange$() {
|
get #dossierFilesChange$() {
|
||||||
return this._dossiersService.dossierFileChanges$.pipe(
|
return this._dossiersService.dossierFileChanges$.pipe(
|
||||||
filter(dossierId => dossierId === this.dossierId),
|
map(changes => changes[this.dossierId]),
|
||||||
|
filter(fileIds => fileIds && fileIds.length > 0),
|
||||||
map(() => true),
|
map(() => true),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
get componentReferenceIds() {
|
|
||||||
return this.#componentReferenceIds$.getValue();
|
|
||||||
}
|
|
||||||
|
|
||||||
reloadBlob(): void {
|
reloadBlob(): void {
|
||||||
this.#reloadBlob$.next(true);
|
this.#reloadBlob$.next(true);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -75,13 +75,10 @@ export class ManualRedactionService extends GenericService<IManualAddResponse> {
|
|||||||
body: List<IRecategorizationRequest> | IBulkRecategorizationRequest,
|
body: List<IRecategorizationRequest> | IBulkRecategorizationRequest,
|
||||||
dossierId: string,
|
dossierId: string,
|
||||||
fileId: string,
|
fileId: string,
|
||||||
successMessageParameters?: {
|
successMessageParameters?: { [p: string]: string },
|
||||||
[key: string]: string;
|
|
||||||
},
|
|
||||||
includeUnprocessed = false,
|
|
||||||
bulkLocal = false,
|
bulkLocal = false,
|
||||||
) {
|
) {
|
||||||
return this.#recategorize(body, dossierId, fileId, includeUnprocessed, bulkLocal).pipe(
|
return this.#recategorize(body, dossierId, fileId, bulkLocal).pipe(
|
||||||
this.#showToast('recategorize-annotation', false, successMessageParameters),
|
this.#showToast('recategorize-annotation', false, successMessageParameters),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -117,10 +114,9 @@ export class ManualRedactionService extends GenericService<IManualAddResponse> {
|
|||||||
fileId: string,
|
fileId: string,
|
||||||
removeFromDictionary = false,
|
removeFromDictionary = false,
|
||||||
isHint = false,
|
isHint = false,
|
||||||
includeUnprocessed = false,
|
|
||||||
bulkLocal = false,
|
bulkLocal = false,
|
||||||
) {
|
) {
|
||||||
return this.#remove(body, dossierId, fileId, includeUnprocessed, bulkLocal).pipe(
|
return this.#remove(body, dossierId, fileId, bulkLocal).pipe(
|
||||||
this.#showToast(!isHint ? 'remove' : 'remove-hint', removeFromDictionary),
|
this.#showToast(!isHint ? 'remove' : 'remove-hint', removeFromDictionary),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -154,36 +150,23 @@ export class ManualRedactionService extends GenericService<IManualAddResponse> {
|
|||||||
return this._post(body, `${this.#bulkRedaction}/force/${dossierId}/${fileId}`).pipe(this.#log('Force redaction', body));
|
return this._post(body, `${this.#bulkRedaction}/force/${dossierId}/${fileId}`).pipe(this.#log('Force redaction', body));
|
||||||
}
|
}
|
||||||
|
|
||||||
resize(body: List<IResizeRequest>, dossierId: string, fileId: string, includeUnprocessed = false) {
|
resize(body: List<IResizeRequest>, dossierId: string, fileId: string) {
|
||||||
return this._post(body, `${this.#bulkRedaction}/resize/${dossierId}/${fileId}?includeUnprocessed=${includeUnprocessed}`).pipe(
|
return this._post(body, `${this.#bulkRedaction}/resize/${dossierId}/${fileId}`).pipe(this.#log('Resize', body));
|
||||||
this.#log('Resize', body),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#recategorize(
|
#recategorize(
|
||||||
body: List<IRecategorizationRequest> | IBulkRecategorizationRequest,
|
body: List<IRecategorizationRequest> | IBulkRecategorizationRequest,
|
||||||
dossierId: string,
|
dossierId: string,
|
||||||
fileId: string,
|
fileId: string,
|
||||||
includeUnprocessed = false,
|
|
||||||
bulkLocal = false,
|
bulkLocal = false,
|
||||||
) {
|
) {
|
||||||
const bulkPath = bulkLocal ? this.#bulkLocal : this.#bulkRedaction;
|
const bulkPath = bulkLocal ? this.#bulkLocal : this.#bulkRedaction;
|
||||||
return this._post(body, `${bulkPath}/recategorize/${dossierId}/${fileId}?includeUnprocessed=${includeUnprocessed}`).pipe(
|
return this._post(body, `${bulkPath}/recategorize/${dossierId}/${fileId}`).pipe(this.#log('Recategorize', body));
|
||||||
this.#log('Recategorize', body),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#remove(
|
#remove(body: List<IRemoveRedactionRequest> | IBulkLocalRemoveRequest, dossierId: string, fileId: string, bulkLocal = false) {
|
||||||
body: List<IRemoveRedactionRequest> | IBulkLocalRemoveRequest,
|
|
||||||
dossierId: string,
|
|
||||||
fileId: string,
|
|
||||||
includeUnprocessed = false,
|
|
||||||
bulkLocal = false,
|
|
||||||
) {
|
|
||||||
const bulkPath = bulkLocal ? this.#bulkLocal : this.#bulkRedaction;
|
const bulkPath = bulkLocal ? this.#bulkLocal : this.#bulkRedaction;
|
||||||
return this._post(body, `${bulkPath}/remove/${dossierId}/${fileId}?includeUnprocessed=${includeUnprocessed}`).pipe(
|
return this._post(body, `${bulkPath}/remove/${dossierId}/${fileId}`).pipe(this.#log('Remove', body));
|
||||||
this.#log('Remove', body),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#log(action: string, body: unknown) {
|
#log(action: string, body: unknown) {
|
||||||
|
|||||||
@ -26,7 +26,7 @@ export class PdfAnnotationActionsService {
|
|||||||
get(annotations: AnnotationWrapper[], annotationChangesAllowed: boolean): IHeaderElement[] {
|
get(annotations: AnnotationWrapper[], annotationChangesAllowed: boolean): IHeaderElement[] {
|
||||||
const availableActions: IHeaderElement[] = [];
|
const availableActions: IHeaderElement[] = [];
|
||||||
const permissions = this.#getAnnotationsPermissions(annotations);
|
const permissions = this.#getAnnotationsPermissions(annotations);
|
||||||
const sameType = annotations.every(a => a.type === annotations[0].type);
|
const sameType = annotations.every(a => a.superType === annotations[0].superType);
|
||||||
|
|
||||||
// you can only resize one annotation at a time
|
// you can only resize one annotation at a time
|
||||||
if (permissions.canResizeAnnotation && annotationChangesAllowed) {
|
if (permissions.canResizeAnnotation && annotationChangesAllowed) {
|
||||||
@ -75,7 +75,7 @@ export class PdfAnnotationActionsService {
|
|||||||
|
|
||||||
if (permissions.canAcceptRecommendation && annotationChangesAllowed) {
|
if (permissions.canAcceptRecommendation && annotationChangesAllowed) {
|
||||||
const acceptRecommendationButton = this.#getButton('check', _('annotation-actions.accept-recommendation.label'), () =>
|
const acceptRecommendationButton = this.#getButton('check', _('annotation-actions.accept-recommendation.label'), () =>
|
||||||
this.#annotationActionsService.convertRecommendationToAnnotation(annotations),
|
this.#annotationActionsService.convertRecommendationToAnnotation(annotations, 'accept'),
|
||||||
);
|
);
|
||||||
availableActions.push(acceptRecommendationButton);
|
availableActions.push(acceptRecommendationButton);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -117,8 +117,10 @@ export class PdfProxyService {
|
|||||||
|
|
||||||
effect(() => {
|
effect(() => {
|
||||||
if (this._viewModeService.isRedacted()) {
|
if (this._viewModeService.isRedacted()) {
|
||||||
|
this._viewerHeaderService.disable([HeaderElements.SHAPE_TOOL_GROUP_BUTTON]);
|
||||||
this._viewerHeaderService.enable([HeaderElements.TOGGLE_READABLE_REDACTIONS]);
|
this._viewerHeaderService.enable([HeaderElements.TOGGLE_READABLE_REDACTIONS]);
|
||||||
} else {
|
} else {
|
||||||
|
this._viewerHeaderService.enable([HeaderElements.SHAPE_TOOL_GROUP_BUTTON]);
|
||||||
this._viewerHeaderService.disable([HeaderElements.TOGGLE_READABLE_REDACTIONS]);
|
this._viewerHeaderService.disable([HeaderElements.TOGGLE_READABLE_REDACTIONS]);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -389,10 +391,10 @@ export class PdfProxyService {
|
|||||||
this._ngZone.run(() => {
|
this._ngZone.run(() => {
|
||||||
if (allAreVisible) {
|
if (allAreVisible) {
|
||||||
this._annotationManager.hide(viewerAnnotations);
|
this._annotationManager.hide(viewerAnnotations);
|
||||||
this._annotationManager.addToHidden(viewerAnnotations[0].Id);
|
viewerAnnotations.forEach(a => this._annotationManager.addToHidden(a.Id));
|
||||||
} else {
|
} else {
|
||||||
this._annotationManager.show(viewerAnnotations);
|
this._annotationManager.show(viewerAnnotations);
|
||||||
this._annotationManager.removeFromHidden(viewerAnnotations[0].Id);
|
viewerAnnotations.forEach(a => this._annotationManager.removeFromHidden(a.Id));
|
||||||
}
|
}
|
||||||
this._annotationManager.deselect();
|
this._annotationManager.deselect();
|
||||||
});
|
});
|
||||||
|
|||||||
@ -19,7 +19,7 @@ export const ActionsHelpModeKeys = {
|
|||||||
'hint-image': 'hint',
|
'hint-image': 'hint',
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
export const ALL_HOTKEYS: List = ['Escape', 'F', 'f', 'ArrowUp', 'ArrowDown', 'H', 'h'] as const;
|
export const ALL_HOTKEYS: List = ['Escape', 'F', 'f', 'ArrowUp', 'ArrowDown', 'H', 'h', 'F5'] as const;
|
||||||
|
|
||||||
export const HeaderElements = {
|
export const HeaderElements = {
|
||||||
SHAPE_TOOL_GROUP_BUTTON: 'SHAPE_TOOL_GROUP_BUTTON',
|
SHAPE_TOOL_GROUP_BUTTON: 'SHAPE_TOOL_GROUP_BUTTON',
|
||||||
@ -45,3 +45,31 @@ export const TextPopups = {
|
|||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
export const HIDE_SKIPPED = 'hide-skipped';
|
export const HIDE_SKIPPED = 'hide-skipped';
|
||||||
|
|
||||||
|
export const ANNOTATION_ACTION_ICONS = [
|
||||||
|
'resize',
|
||||||
|
'edit',
|
||||||
|
'trash',
|
||||||
|
'check',
|
||||||
|
'thumb-up',
|
||||||
|
'pdftron-action-add-redaction',
|
||||||
|
'visibility-off',
|
||||||
|
] as const;
|
||||||
|
export const ANNOTATION_ACTIONS = [
|
||||||
|
'Resize',
|
||||||
|
'Größe ändern',
|
||||||
|
'Edit',
|
||||||
|
'Bearbeiten',
|
||||||
|
'Remove',
|
||||||
|
'Entfernen',
|
||||||
|
'Accept recommendation',
|
||||||
|
'Empfehlung annehmen',
|
||||||
|
'Force redaction',
|
||||||
|
'Schwärzung erzwingen',
|
||||||
|
'Force hint',
|
||||||
|
'Hinweis erzwingen',
|
||||||
|
'Redact',
|
||||||
|
'Schwärzen',
|
||||||
|
'Hide',
|
||||||
|
'Ausblenden',
|
||||||
|
] as const;
|
||||||
|
|||||||
@ -30,7 +30,11 @@ const DOCUMENT_ICON = 'iqser:document';
|
|||||||
const FOLDER_ICON = 'red:folder';
|
const FOLDER_ICON = 'red:folder';
|
||||||
const REMOVE_FROM_DICT_ICON = 'red:remove-from-dict';
|
const REMOVE_FROM_DICT_ICON = 'red:remove-from-dict';
|
||||||
|
|
||||||
export const getEditRedactionOptions = (): DetailsRadioOption<EditRedactionOption>[] => {
|
export const getEditRedactionOptions = (hint: boolean): DetailsRadioOption<EditRedactionOption>[] => {
|
||||||
|
if (hint) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
label: editRedactionTranslations.onlyHere.label,
|
label: editRedactionTranslations.onlyHere.label,
|
||||||
@ -98,19 +102,27 @@ export const getRedactOrHintOptions = (
|
|||||||
return options;
|
return options;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getRectangleRedactOptions = (action: 'add' | 'edit' | 'remove' = 'add'): DetailsRadioOption<RectangleRedactOption>[] => {
|
export const getRectangleRedactOptions = (
|
||||||
|
action: 'add' | 'edit' | 'remove' = 'add',
|
||||||
|
redactions: AnnotationWrapper[] = [],
|
||||||
|
): DetailsRadioOption<RectangleRedactOption>[] => {
|
||||||
const translations =
|
const translations =
|
||||||
action === 'add' ? rectangleRedactTranslations : action === 'edit' ? editRectangleTranslations : removeRectangleTranslations;
|
action === 'add' ? rectangleRedactTranslations : action === 'edit' ? editRectangleTranslations : removeRectangleTranslations;
|
||||||
return [
|
const options: DetailsRadioOption<RectangleRedactOption>[] = [
|
||||||
{
|
{
|
||||||
label: translations.onlyThisPage.label,
|
label: translations.onlyThisPage.label,
|
||||||
description: translations.onlyThisPage.description,
|
description: translations.onlyThisPage.description,
|
||||||
|
descriptionParams: { length: redactions.length },
|
||||||
icon: PIN_ICON,
|
icon: PIN_ICON,
|
||||||
value: RectangleRedactOptions.ONLY_THIS_PAGE,
|
value: RectangleRedactOptions.ONLY_THIS_PAGE,
|
||||||
},
|
},
|
||||||
{
|
];
|
||||||
|
const isImportedWithoutValue = redactions.some(redaction => redaction.type === 'imported_redaction' && !redaction.value);
|
||||||
|
if (!['edit', 'remove'].includes(action) || !isImportedWithoutValue) {
|
||||||
|
options.push({
|
||||||
label: translations.multiplePages.label,
|
label: translations.multiplePages.label,
|
||||||
description: translations.multiplePages.description,
|
description: translations.multiplePages.description,
|
||||||
|
descriptionParams: { length: redactions.length },
|
||||||
icon: DOCUMENT_ICON,
|
icon: DOCUMENT_ICON,
|
||||||
value: RectangleRedactOptions.MULTIPLE_PAGES,
|
value: RectangleRedactOptions.MULTIPLE_PAGES,
|
||||||
additionalInput: {
|
additionalInput: {
|
||||||
@ -118,9 +130,11 @@ export const getRectangleRedactOptions = (action: 'add' | 'edit' | 'remove' = 'a
|
|||||||
description: translations.multiplePages.extraOptionDescription,
|
description: translations.multiplePages.extraOptionDescription,
|
||||||
placeholder: translations.multiplePages.extraOptionPlaceholder,
|
placeholder: translations.multiplePages.extraOptionPlaceholder,
|
||||||
value: '',
|
value: '',
|
||||||
|
errorCode: 'invalidRange',
|
||||||
},
|
},
|
||||||
},
|
});
|
||||||
];
|
}
|
||||||
|
return options;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getResizeRedactionOptions = (
|
export const getResizeRedactionOptions = (
|
||||||
@ -131,22 +145,21 @@ export const getResizeRedactionOptions = (
|
|||||||
isApprover: boolean,
|
isApprover: boolean,
|
||||||
canResizeInDictionary: boolean,
|
canResizeInDictionary: boolean,
|
||||||
): DetailsRadioOption<ResizeRedactionOption>[] => {
|
): DetailsRadioOption<ResizeRedactionOption>[] => {
|
||||||
|
if (isRss || !canResizeInDictionary) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
const translations = resizeRedactionTranslations;
|
const translations = resizeRedactionTranslations;
|
||||||
const options: DetailsRadioOption<ResizeRedactionOption>[] = [
|
const dictBasedType = redaction.isModifyDictionary;
|
||||||
|
|
||||||
|
return [
|
||||||
{
|
{
|
||||||
label: translations.onlyHere.label,
|
label: translations.onlyHere.label,
|
||||||
description: translations.onlyHere.description,
|
description: translations.onlyHere.description,
|
||||||
icon: PIN_ICON,
|
icon: PIN_ICON,
|
||||||
value: ResizeOptions.ONLY_HERE,
|
value: ResizeOptions.ONLY_HERE,
|
||||||
},
|
},
|
||||||
];
|
{
|
||||||
|
|
||||||
if (isRss) {
|
|
||||||
return options;
|
|
||||||
}
|
|
||||||
if (canResizeInDictionary) {
|
|
||||||
const dictBasedType = redaction.isModifyDictionary;
|
|
||||||
options.push({
|
|
||||||
label: translations.inDossier.label,
|
label: translations.inDossier.label,
|
||||||
description: translations.inDossier.description,
|
description: translations.inDossier.description,
|
||||||
descriptionParams: { dossierName: dossier.dossierName },
|
descriptionParams: { dossierName: dossier.dossierName },
|
||||||
@ -159,9 +172,8 @@ export const getResizeRedactionOptions = (
|
|||||||
checked: applyToAllDossiers,
|
checked: applyToAllDossiers,
|
||||||
hidden: !isApprover,
|
hidden: !isApprover,
|
||||||
},
|
},
|
||||||
});
|
},
|
||||||
}
|
];
|
||||||
return options;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getRemoveRedactionOptions = (
|
export const getRemoveRedactionOptions = (
|
||||||
@ -172,30 +184,31 @@ export const getRemoveRedactionOptions = (
|
|||||||
const translations = isDocumine ? removeAnnotationTranslations : removeRedactionTranslations;
|
const translations = isDocumine ? removeAnnotationTranslations : removeRedactionTranslations;
|
||||||
const { permissions, redactions, isApprover, falsePositiveContext } = data;
|
const { permissions, redactions, isApprover, falsePositiveContext } = data;
|
||||||
const isBulk = redactions.length > 1;
|
const isBulk = redactions.length > 1;
|
||||||
|
const isImage = redactions.reduce((acc, next) => acc && next.isImage, true);
|
||||||
|
|
||||||
const options: DetailsRadioOption<RemoveRedactionOption>[] = [];
|
const options: DetailsRadioOption<RemoveRedactionOption>[] = [];
|
||||||
if (permissions.canRemoveOnlyHere) {
|
if (permissions.canRemoveOnlyHere && !isImage) {
|
||||||
options.push({
|
options.push({
|
||||||
label: translations.ONLY_HERE.label,
|
label: translations.ONLY_HERE.label,
|
||||||
description: isBulk ? translations.ONLY_HERE.descriptionBulk : translations.ONLY_HERE.description,
|
description: isBulk ? translations.ONLY_HERE.descriptionBulk : translations.ONLY_HERE.description,
|
||||||
descriptionParams: {
|
descriptionParams: {
|
||||||
value: redactions[0].value,
|
value: redactions[0].value,
|
||||||
type: redactions[0].HINT ? 'hint' : redactions[0].typeLabel,
|
type: redactions[0].HINT ? 'hint' : redactions[0].typeLabel,
|
||||||
isImage: redactions[0].isImage ? 'image' : redactions[0].typeLabel,
|
|
||||||
},
|
},
|
||||||
icon: PIN_ICON,
|
icon: PIN_ICON,
|
||||||
value: RemoveRedactionOptions.ONLY_HERE,
|
value: RemoveRedactionOptions.ONLY_HERE,
|
||||||
});
|
});
|
||||||
|
|
||||||
options.push({
|
const isHint = redactions.reduce((acc, next) => acc && next.isHint, true);
|
||||||
label: removeRedactionTranslations.IN_DOCUMENT.label,
|
if (!isHint) {
|
||||||
description: removeRedactionTranslations.IN_DOCUMENT.description,
|
options.push({
|
||||||
descriptionParams: {
|
label: removeRedactionTranslations.IN_DOCUMENT.label,
|
||||||
isImage: redactions[0].isImage ? 'image' : redactions[0].typeLabel,
|
description: removeRedactionTranslations.IN_DOCUMENT.description,
|
||||||
},
|
icon: DOCUMENT_ICON,
|
||||||
icon: DOCUMENT_ICON,
|
value: RemoveRedactionOptions.IN_DOCUMENT,
|
||||||
value: RemoveRedactionOptions.IN_DOCUMENT,
|
descriptionParams: { length: redactions.length },
|
||||||
});
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (permissions.canRemoveFromDictionary) {
|
if (permissions.canRemoveFromDictionary) {
|
||||||
options.push({
|
options.push({
|
||||||
@ -264,8 +277,12 @@ export const getRemoveRedactionOptions = (
|
|||||||
return options;
|
return options;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getForceAnnotationOptions = (isDocumine: boolean, isHint: boolean): DetailsRadioOption<ForceAnnotationOption>[] => {
|
export const getForceAnnotationOptions = (
|
||||||
if (isDocumine || isHint) {
|
isDocumine: boolean,
|
||||||
|
isHint: boolean,
|
||||||
|
isImage: boolean,
|
||||||
|
): DetailsRadioOption<ForceAnnotationOption>[] => {
|
||||||
|
if (isDocumine || isHint || isImage) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -64,6 +64,7 @@ export interface RedactTextData {
|
|||||||
|
|
||||||
export interface EditRedactionData {
|
export interface EditRedactionData {
|
||||||
annotations: AnnotationWrapper[];
|
annotations: AnnotationWrapper[];
|
||||||
|
allFileAnnotations?: AnnotationWrapper[];
|
||||||
dossierId: string;
|
dossierId: string;
|
||||||
file: File;
|
file: File;
|
||||||
isApprover?: boolean;
|
isApprover?: boolean;
|
||||||
@ -72,6 +73,21 @@ export interface EditRedactionData {
|
|||||||
export type AddAnnotationData = RedactTextData;
|
export type AddAnnotationData = RedactTextData;
|
||||||
export type AddHintData = RedactTextData;
|
export type AddHintData = RedactTextData;
|
||||||
|
|
||||||
|
export interface ForceAnnotationData {
|
||||||
|
readonly dossier: Dossier;
|
||||||
|
readonly annotations: AnnotationWrapper[];
|
||||||
|
readonly hint: boolean;
|
||||||
|
readonly image: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ForceAnnotationResult {
|
||||||
|
readonly annotationId?: string;
|
||||||
|
readonly comment?: string;
|
||||||
|
readonly legalBasis?: string;
|
||||||
|
readonly reason?: string;
|
||||||
|
readonly option?: ForceAnnotationOption;
|
||||||
|
}
|
||||||
|
|
||||||
export interface RedactTextResult {
|
export interface RedactTextResult {
|
||||||
redaction: IManualRedactionEntry;
|
redaction: IManualRedactionEntry;
|
||||||
dictionary: Dictionary;
|
dictionary: Dictionary;
|
||||||
@ -80,6 +96,7 @@ export interface RedactTextResult {
|
|||||||
|
|
||||||
export type RedactRecommendationData = EditRedactionData & {
|
export type RedactRecommendationData = EditRedactionData & {
|
||||||
applyToAllDossiers: boolean;
|
applyToAllDossiers: boolean;
|
||||||
|
action?: 'resize' | 'accept';
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface RedactRecommendationResult {
|
export interface RedactRecommendationResult {
|
||||||
@ -135,6 +152,7 @@ export interface RemoveRedactionPermissions {
|
|||||||
|
|
||||||
export interface RemoveRedactionData {
|
export interface RemoveRedactionData {
|
||||||
redactions: AnnotationWrapper[];
|
redactions: AnnotationWrapper[];
|
||||||
|
allFileRedactions?: AnnotationWrapper[];
|
||||||
dossier: Dossier;
|
dossier: Dossier;
|
||||||
file?: File;
|
file?: File;
|
||||||
falsePositiveContext: string[];
|
falsePositiveContext: string[];
|
||||||
@ -151,7 +169,7 @@ export interface RemoveRedactionResult {
|
|||||||
applyToAllDossiers?: boolean;
|
applyToAllDossiers?: boolean;
|
||||||
bulkLocal?: boolean;
|
bulkLocal?: boolean;
|
||||||
pageNumbers?: number[];
|
pageNumbers?: number[];
|
||||||
position: IEntityLogEntryPosition;
|
positions: IEntityLogEntryPosition[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export type RemoveAnnotationResult = RemoveRedactionResult;
|
export type RemoveAnnotationResult = RemoveRedactionResult;
|
||||||
|
|||||||
@ -1,7 +1,8 @@
|
|||||||
import { Dictionary, File, IAddRedactionRequest, IEntityLogEntryPosition, SuperType } from '@red/domain';
|
import { Dictionary, File, IAddRedactionRequest, IEntityLogEntryPosition, SuperType } from '@red/domain';
|
||||||
import { ManualRedactionEntryWrapper } from '@models/file/manual-redaction-entry.wrapper';
|
import { ManualRedactionEntryWrapper } from '@models/file/manual-redaction-entry.wrapper';
|
||||||
import { LegalBasisOption } from './dialog-types';
|
import { LegalBasisOption, RectangleRedactOption, RectangleRedactOptions } from './dialog-types';
|
||||||
import { AnnotationWrapper } from '@models/file/annotation.wrapper';
|
import { AnnotationWrapper } from '@models/file/annotation.wrapper';
|
||||||
|
import { DetailsRadioOption } from '@common-ui/inputs/details-radio/details-radio-option';
|
||||||
|
|
||||||
export interface EnhanceRequestData {
|
export interface EnhanceRequestData {
|
||||||
readonly type: SuperType | null;
|
readonly type: SuperType | null;
|
||||||
@ -48,7 +49,7 @@ export const enhanceManualRedactionRequest = (addRedactionRequest: IAddRedaction
|
|||||||
addRedactionRequest.addToAllDossiers = data.isApprover && data.dictionaryRequest && data.applyToAllDossiers;
|
addRedactionRequest.addToAllDossiers = data.isApprover && data.dictionaryRequest && data.applyToAllDossiers;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const parseSelectedPageNumbers = (inputValue: string, file: File, annotation: AnnotationWrapper) => {
|
export const parseSelectedPageNumbers = (inputValue: string, file: File) => {
|
||||||
if (!inputValue) {
|
if (!inputValue) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
@ -79,3 +80,60 @@ export const parseRectanglePosition = (annotation: AnnotationWrapper) => {
|
|||||||
pageNumber: position.page,
|
pageNumber: position.page,
|
||||||
} as IEntityLogEntryPosition;
|
} as IEntityLogEntryPosition;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const prefillPageRange = (
|
||||||
|
annotation: AnnotationWrapper,
|
||||||
|
allFileAnnotations: AnnotationWrapper[],
|
||||||
|
options: DetailsRadioOption<RectangleRedactOption>[],
|
||||||
|
) => {
|
||||||
|
const option = options.find(o => o.value === RectangleRedactOptions.MULTIPLE_PAGES);
|
||||||
|
const pages = extractPages(annotation, allFileAnnotations);
|
||||||
|
option.additionalInput.value = toRangeString(pages);
|
||||||
|
};
|
||||||
|
|
||||||
|
const extractPages = (annotation: AnnotationWrapper, allFileAnnotations: AnnotationWrapper[]): number[] => {
|
||||||
|
return allFileAnnotations.reduce((pages, a) => {
|
||||||
|
const position = a.positions[0];
|
||||||
|
const annotationPosition = annotation.positions[0];
|
||||||
|
if (
|
||||||
|
position.height === annotationPosition.height &&
|
||||||
|
position.width === annotationPosition.width &&
|
||||||
|
position.topLeft.x === annotationPosition.topLeft.x &&
|
||||||
|
position.topLeft.y === annotationPosition.topLeft.y
|
||||||
|
) {
|
||||||
|
pages.push(position.page);
|
||||||
|
}
|
||||||
|
return pages;
|
||||||
|
}, []);
|
||||||
|
};
|
||||||
|
|
||||||
|
const toRangeString = (pages: number[]): string => {
|
||||||
|
if (pages.length === 0) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
let ranges = [];
|
||||||
|
let start = pages[0];
|
||||||
|
let end = pages[0];
|
||||||
|
|
||||||
|
for (let i = 1; i < pages.length; i++) {
|
||||||
|
if (pages[i] === end + 1) {
|
||||||
|
end = pages[i];
|
||||||
|
} else {
|
||||||
|
if (start === end) {
|
||||||
|
ranges.push(`${start}`);
|
||||||
|
} else {
|
||||||
|
ranges.push(`${start}-${end}`);
|
||||||
|
}
|
||||||
|
start = end = pages[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (start === end) {
|
||||||
|
ranges.push(`${start}`);
|
||||||
|
} else {
|
||||||
|
ranges.push(`${start}-${end}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ranges.join(',');
|
||||||
|
};
|
||||||
|
|||||||
@ -1,12 +1,26 @@
|
|||||||
import { AbstractControl, ValidatorFn } from '@angular/forms';
|
import { AbstractControl, ValidatorFn } from '@angular/forms';
|
||||||
|
|
||||||
export const validatePageRange = (allowEmpty = false): ValidatorFn => {
|
export const validatePageRange = (numberOfPages: number, allowEmpty = false): ValidatorFn => {
|
||||||
return (control: AbstractControl): { [key: string]: any } | null => {
|
return (control: AbstractControl): { [key: string]: any } | null => {
|
||||||
const option = control.value;
|
const option = control.value;
|
||||||
if (option?.additionalInput) {
|
if (option?.additionalInput) {
|
||||||
const value = option.additionalInput.value;
|
const value = option.additionalInput.value;
|
||||||
const validRange = /^(\d+(-\d+)?)(,\d+(-\d+)?)*$/.test(value);
|
const validRange = /^(\d+(-\d+)?)(,\d+(-\d+)?)*$/.test(value);
|
||||||
return validRange || (!value.length && allowEmpty) ? null : { invalidRange: true };
|
|
||||||
|
if (!validRange && !(value.length === 0 && allowEmpty)) {
|
||||||
|
return { invalidRange: true };
|
||||||
|
}
|
||||||
|
|
||||||
|
const ranges = value.split(',');
|
||||||
|
const isWithinRange = ranges.every(range => {
|
||||||
|
const [start, end] = range.split('-').map(Number);
|
||||||
|
if (end) {
|
||||||
|
return start >= 1 && end <= numberOfPages && start < end;
|
||||||
|
}
|
||||||
|
return start >= 1 && start <= numberOfPages;
|
||||||
|
});
|
||||||
|
|
||||||
|
return isWithinRange || (value.length === 0 && allowEmpty) ? null : { invalidRange: true };
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,41 +1,72 @@
|
|||||||
import { AnnotationWrapper } from '@models/file/annotation.wrapper';
|
import { AnnotationWrapper } from '@models/file/annotation.wrapper';
|
||||||
|
|
||||||
export const sortTopLeftToBottomRight = (a1: AnnotationWrapper, a2: AnnotationWrapper): number => {
|
export const sortTopLeftToBottomRight = (a1: AnnotationWrapper, a2: AnnotationWrapper): number => {
|
||||||
if (a1.y > a2.y) {
|
const top = Math.min(a1.y + a1.height, a2.y + a2.height);
|
||||||
|
const bottom = Math.max(a1.y, a2.y);
|
||||||
|
const intersectionHeight = Math.max(0, top - bottom);
|
||||||
|
const a1IntersectionPercentage = (intersectionHeight / a1.height) * 100;
|
||||||
|
const a2IntersectionPercentage = (intersectionHeight / a2.height) * 100;
|
||||||
|
if (a1IntersectionPercentage > 10 || a2IntersectionPercentage > 10) {
|
||||||
|
return a1.x < a2.x ? -1 : 1;
|
||||||
|
}
|
||||||
|
if (a1.y + a1.height > a2.y + a2.height) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
if (a1.y < a2.y) {
|
if (a1.y + a1.height < a2.y + a2.height) {
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
return a1.x < a2.x ? -1 : 1;
|
return a1.x < a2.x ? -1 : 1;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const sortBottomLeftToTopRight = (a1: AnnotationWrapper, a2: AnnotationWrapper): number => {
|
export const sortBottomLeftToTopRight = (a1: AnnotationWrapper, a2: AnnotationWrapper): number => {
|
||||||
|
const top = Math.max(a1.x, a2.x);
|
||||||
|
const bottom = Math.min(a1.x + a1.width, a2.x + a2.width);
|
||||||
|
const intersectionHeight = Math.max(0, bottom - top);
|
||||||
|
const a1IntersectionPercentage = (intersectionHeight / a1.width) * 100;
|
||||||
|
const a2IntersectionPercentage = (intersectionHeight / a2.width) * 100;
|
||||||
|
if (a1IntersectionPercentage > 10 || a2IntersectionPercentage > 10) {
|
||||||
|
return a1.y + a1.height < a2.y + a2.height ? -1 : 1;
|
||||||
|
}
|
||||||
if (a1.x < a2.x) {
|
if (a1.x < a2.x) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
if (a1.x > a2.x) {
|
if (a1.x > a2.x) {
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
return a1.y < a2.y ? -1 : 1;
|
return a1.y + a1.height < a2.y + a2.height ? -1 : 1;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const sortBottomRightToTopLeft = (a1: AnnotationWrapper, a2: AnnotationWrapper): number => {
|
export const sortBottomRightToTopLeft = (a1: AnnotationWrapper, a2: AnnotationWrapper): number => {
|
||||||
|
const top = Math.max(a1.y, a2.y);
|
||||||
|
const bottom = Math.min(a1.y + a1.height, a2.y + a2.height);
|
||||||
|
const intersectionHeight = Math.max(0, bottom - top);
|
||||||
|
const a1IntersectionPercentage = (intersectionHeight / a1.height) * 100;
|
||||||
|
const a2IntersectionPercentage = (intersectionHeight / a2.height) * 100;
|
||||||
|
if (a1IntersectionPercentage > 10 || a2IntersectionPercentage > 10) {
|
||||||
|
return a1.x + a1.width > a2.x + a2.width ? -1 : 1;
|
||||||
|
}
|
||||||
if (a1.y < a2.y) {
|
if (a1.y < a2.y) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
if (a1.y > a2.y) {
|
if (a1.y > a2.y) {
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
return a1.x > a2.x ? -1 : 1;
|
return a1.x + a1.width > a2.x + a2.width ? -1 : 1;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const sortTopRightToBottomLeft = (a1: AnnotationWrapper, a2: AnnotationWrapper): number => {
|
export const sortTopRightToBottomLeft = (a1: AnnotationWrapper, a2: AnnotationWrapper): number => {
|
||||||
if (a1.x > a2.x) {
|
const top = Math.min(a1.x + a1.width, a2.x + a2.width);
|
||||||
|
const bottom = Math.max(a1.x, a2.x);
|
||||||
|
const intersectionHeight = Math.max(0, top - bottom);
|
||||||
|
const a1IntersectionPercentage = (intersectionHeight / a1.width) * 100;
|
||||||
|
const a2IntersectionPercentage = (intersectionHeight / a2.width) * 100;
|
||||||
|
if (a1IntersectionPercentage > 10 || a2IntersectionPercentage > 10) {
|
||||||
|
return a1.y + a1.height > a2.y + a2.height ? -1 : 1;
|
||||||
|
}
|
||||||
|
if (a1.x + a1.width > a2.x + a2.width) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
if (a1.x < a2.x) {
|
if (a1.x + a1.width < a2.x + a2.width) {
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
return a1.y > a2.y ? -1 : 1;
|
return a1.y + a1.height > a2.y + a2.height ? -1 : 1;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -106,7 +106,11 @@ export class REDAnnotationManager {
|
|||||||
this.deselect(this.selected.map(annotation => annotation.Id));
|
this.deselect(this.selected.map(annotation => annotation.Id));
|
||||||
}
|
}
|
||||||
|
|
||||||
hide(annotations: Annotation[]): void {
|
hide(annotations: Annotation[], multiSelectActive = false): void {
|
||||||
|
if (multiSelectActive) {
|
||||||
|
annotations.forEach(a => (a['Opacity'] = 0));
|
||||||
|
return;
|
||||||
|
}
|
||||||
this.#manager.hideAnnotations(annotations);
|
this.#manager.hideAnnotations(annotations);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -14,6 +14,7 @@ import { PdfViewer } from './pdf-viewer.service';
|
|||||||
import Color = Core.Annotations.Color;
|
import Color = Core.Annotations.Color;
|
||||||
import DocumentViewer = Core.DocumentViewer;
|
import DocumentViewer = Core.DocumentViewer;
|
||||||
import Quad = Core.Math.Quad;
|
import Quad = Core.Math.Quad;
|
||||||
|
import { isTargetInput } from '@utils/functions';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class REDDocumentViewer {
|
export class REDDocumentViewer {
|
||||||
@ -71,14 +72,14 @@ export class REDDocumentViewer {
|
|||||||
return fromEvent<KeyboardEvent>(this.#document, 'keyUp').pipe(
|
return fromEvent<KeyboardEvent>(this.#document, 'keyUp').pipe(
|
||||||
tap(stopAndPreventIfNotAllowed),
|
tap(stopAndPreventIfNotAllowed),
|
||||||
filter($event => {
|
filter($event => {
|
||||||
if (($event.target as HTMLElement)?.tagName?.toLowerCase() === 'input') {
|
if (isTargetInput($event)) {
|
||||||
if ($event.key === 'Escape') {
|
if ($event.key === 'Escape') {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return ($event.target as HTMLElement)?.tagName?.toLowerCase() !== 'input';
|
return isTargetInput($event);
|
||||||
}),
|
}),
|
||||||
filter($event => $event.key.startsWith('Arrow') || ['f', 'h', 'H', 'Escape'].includes($event.key)),
|
filter($event => $event.key.startsWith('Arrow') || ['f', 'h', 'H', 'Escape', 'Shift'].includes($event.key)),
|
||||||
tap<KeyboardEvent>(stopAndPrevent),
|
tap<KeyboardEvent>(stopAndPrevent),
|
||||||
log('[PDF] Keyboard shortcut'),
|
log('[PDF] Keyboard shortcut'),
|
||||||
);
|
);
|
||||||
|
|||||||
@ -13,7 +13,7 @@ import { UserPreferenceService } from '@users/user-preference.service';
|
|||||||
import { NGXLogger } from 'ngx-logger';
|
import { NGXLogger } from 'ngx-logger';
|
||||||
import { combineLatest, fromEvent, Observable } from 'rxjs';
|
import { combineLatest, fromEvent, Observable } from 'rxjs';
|
||||||
import { map, startWith } from 'rxjs/operators';
|
import { map, startWith } from 'rxjs/operators';
|
||||||
import { DISABLED_HOTKEYS, DOCUMENT_LOADING_ERROR, USELESS_ELEMENTS } from '../utils/constants';
|
import { DISABLED_HOTKEYS, DOCUMENT_LOADING_ERROR, SelectionModes, USELESS_ELEMENTS } from '../utils/constants';
|
||||||
import { asList } from '../utils/functions';
|
import { asList } from '../utils/functions';
|
||||||
import { Rgb } from '../utils/types';
|
import { Rgb } from '../utils/types';
|
||||||
import { REDAnnotationManager } from './annotation-manager.service';
|
import { REDAnnotationManager } from './annotation-manager.service';
|
||||||
@ -65,6 +65,21 @@ export class PdfViewer {
|
|||||||
};
|
};
|
||||||
selectedText = '';
|
selectedText = '';
|
||||||
|
|
||||||
|
readonly escKeyHandler = {
|
||||||
|
keydown: (e: KeyboardEvent) => {
|
||||||
|
e.preventDefault();
|
||||||
|
this.#clickSelectToolButton();
|
||||||
|
},
|
||||||
|
keyup: (e: KeyboardEvent) => {
|
||||||
|
e.preventDefault();
|
||||||
|
if (this.#isElementActive('searchPanel') && !this._annotationManager.resizingAnnotationId) {
|
||||||
|
this.#focusViewer();
|
||||||
|
this.deactivateSearch();
|
||||||
|
}
|
||||||
|
this.#clickSelectToolButton();
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private readonly _logger: NGXLogger,
|
private readonly _logger: NGXLogger,
|
||||||
private readonly _errorService: ErrorService,
|
private readonly _errorService: ErrorService,
|
||||||
@ -115,6 +130,11 @@ export class PdfViewer {
|
|||||||
return page$.pipe(map(page => this.#adjustPage(page)));
|
return page$.pipe(map(page => this.#adjustPage(page)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get #searchInput() {
|
||||||
|
const iframeWindow = this.#instance.UI.iframeWindow;
|
||||||
|
return iframeWindow.document.getElementById('SearchPanel__input') as HTMLInputElement;
|
||||||
|
}
|
||||||
|
|
||||||
activateSearch() {
|
activateSearch() {
|
||||||
this.#instance.UI.searchTextFull('', this.searchOptions);
|
this.#instance.UI.searchTextFull('', this.searchOptions);
|
||||||
}
|
}
|
||||||
@ -148,7 +168,7 @@ export class PdfViewer {
|
|||||||
this.#instance = await this.#getInstance(htmlElement);
|
this.#instance = await this.#getInstance(htmlElement);
|
||||||
|
|
||||||
if (environment.production) {
|
if (environment.production) {
|
||||||
this.#instance.Core.setCustomFontURL('https://' + window.location.host + this.#convertPath('/assets/pdftron'));
|
this.#instance.Core.setCustomFontURL(window.location.origin + this.#convertPath('/assets/pdftron/fonts'));
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.runWithCleanup(async () => {
|
await this.runWithCleanup(async () => {
|
||||||
@ -160,12 +180,12 @@ export class PdfViewer {
|
|||||||
|
|
||||||
this.pageChanged$ = this.#pageChanged$.pipe(shareDistinctLast());
|
this.pageChanged$ = this.#pageChanged$.pipe(shareDistinctLast());
|
||||||
this.#totalPages$.pipe(takeUntilDestroyed(this.#destroyRef)).subscribe(pages => this.#totalPages.set(pages));
|
this.#totalPages$.pipe(takeUntilDestroyed(this.#destroyRef)).subscribe(pages => this.#totalPages.set(pages));
|
||||||
this.#setSelectionMode();
|
this.#setSelectionMode(this.#config.SELECTION_MODE);
|
||||||
this.#configureElements();
|
this.#configureElements();
|
||||||
this.#disableHotkeys();
|
this.#disableHotkeys();
|
||||||
this.#getSelectedText();
|
this.#getSelectedText();
|
||||||
this.#listenForCommandF();
|
this.#listenForCommandF();
|
||||||
this.#listenForEsc();
|
this.#listenForShift();
|
||||||
this.#clearSearchResultsWhenVisibilityChanged();
|
this.#clearSearchResultsWhenVisibilityChanged();
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -258,7 +278,7 @@ export class PdfViewer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#listenForCommandF() {
|
#listenForCommandF() {
|
||||||
this.#instance.UI.hotkeys.on('command+f, ctrl+f', e => {
|
this.#instance.UI.hotkeys.on('command+f, ctrl+f', (e: KeyboardEvent) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
if (this.#isElementActive('searchPanel')) {
|
if (this.#isElementActive('searchPanel')) {
|
||||||
this.#updateSearchOptions();
|
this.#updateSearchOptions();
|
||||||
@ -272,20 +292,18 @@ export class PdfViewer {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
#listenForEsc() {
|
#listenForShift() {
|
||||||
this.#instance.UI.hotkeys.on('esc', {
|
this.#instance.UI.iframeWindow.addEventListener('keydown', e => {
|
||||||
keydown: e => {
|
if (e.target === this.#searchInput) return;
|
||||||
e.preventDefault();
|
if (e.key === 'Shift') {
|
||||||
this.#clickSelectToolButton();
|
this.#setSelectionMode(SelectionModes.RECTANGULAR);
|
||||||
},
|
}
|
||||||
keyup: e => {
|
});
|
||||||
e.preventDefault();
|
this.#instance.UI.iframeWindow.addEventListener('keyup', e => {
|
||||||
if (this.#isElementActive('searchPanel') && !this._annotationManager.resizingAnnotationId) {
|
if (e.target === this.#searchInput) return;
|
||||||
this.#focusViewer();
|
if (e.key === 'Shift') {
|
||||||
this.deactivateSearch();
|
this.#setSelectionMode(SelectionModes.STRUCTURAL);
|
||||||
}
|
}
|
||||||
this.#clickSelectToolButton();
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -350,21 +368,22 @@ export class PdfViewer {
|
|||||||
this.#instance.UI.disableElements(USELESS_ELEMENTS);
|
this.#instance.UI.disableElements(USELESS_ELEMENTS);
|
||||||
}
|
}
|
||||||
|
|
||||||
#setSelectionMode(): void {
|
#setSelectionMode(selectionMode: string): void {
|
||||||
const textTool = this.#instance.Core.Tools.TextTool as unknown as TextTool;
|
const textTool = this.#instance.Core.Tools.TextTool as unknown as TextTool;
|
||||||
textTool.SELECTION_MODE = this.#config.SELECTION_MODE;
|
textTool.SELECTION_MODE = selectionMode;
|
||||||
}
|
}
|
||||||
|
|
||||||
#getInstance(htmlElement: HTMLElement) {
|
#getInstance(htmlElement: HTMLElement) {
|
||||||
const options: WebViewerOptions = {
|
const options: WebViewerOptions = {
|
||||||
licenseKey: this.#licenseKey,
|
licenseKey: this.#licenseKey,
|
||||||
fullAPI: true,
|
fullAPI: true,
|
||||||
path: this.#convertPath('/assets/wv-resources/10.10.1'),
|
path: this.#convertPath('/assets/wv-resources/11.0.0'),
|
||||||
css: this.#convertPath('/assets/pdftron/stylesheet.css'),
|
css: this.#convertPath('/assets/pdftron/stylesheet.css'),
|
||||||
backendType: 'ems',
|
backendType: 'ems',
|
||||||
|
ui: 'legacy',
|
||||||
};
|
};
|
||||||
|
|
||||||
return WebViewer(options, htmlElement);
|
return WebViewer.Iframe(options, htmlElement);
|
||||||
}
|
}
|
||||||
|
|
||||||
#isElementActive(element: string): boolean {
|
#isElementActive(element: string): boolean {
|
||||||
@ -375,13 +394,11 @@ export class PdfViewer {
|
|||||||
if (this.#isElementActive('textPopup')) {
|
if (this.#isElementActive('textPopup')) {
|
||||||
this.#instance.UI.closeElements(['textPopup']);
|
this.#instance.UI.closeElements(['textPopup']);
|
||||||
}
|
}
|
||||||
const iframeWindow = this.#instance.UI.iframeWindow;
|
if (this.#searchInput) {
|
||||||
const input = iframeWindow.document.getElementById('SearchPanel__input') as HTMLInputElement;
|
this.#searchInput.focus();
|
||||||
if (input) {
|
|
||||||
input.focus();
|
|
||||||
}
|
}
|
||||||
if (input?.value?.length > 0) {
|
if (this.#searchInput?.value?.length > 0) {
|
||||||
input.select();
|
this.#searchInput.select();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -13,8 +13,7 @@ import Annotation = Core.Annotations.Annotation;
|
|||||||
export class ReadableRedactionsService {
|
export class ReadableRedactionsService {
|
||||||
readonly active$: Observable<boolean>;
|
readonly active$: Observable<boolean>;
|
||||||
readonly #convertPath = inject(UI_ROOT_PATH_FN);
|
readonly #convertPath = inject(UI_ROOT_PATH_FN);
|
||||||
readonly #enableIcon = this.#convertPath('/assets/icons/general/redaction-preview.svg');
|
readonly #icon = this.#convertPath('/assets/icons/general/redaction-preview.svg');
|
||||||
readonly #disableIcon = this.#convertPath('/assets/icons/general/redaction-final.svg');
|
|
||||||
readonly #active$ = new BehaviorSubject<boolean>(true);
|
readonly #active$ = new BehaviorSubject<boolean>(true);
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@ -29,8 +28,8 @@ export class ReadableRedactionsService {
|
|||||||
return this.#active$.getValue();
|
return this.#active$.getValue();
|
||||||
}
|
}
|
||||||
|
|
||||||
get toggleReadableRedactionsBtnIcon(): string {
|
get icon() {
|
||||||
return this.active ? this.#enableIcon : this.#disableIcon;
|
return this.#icon;
|
||||||
}
|
}
|
||||||
|
|
||||||
toggleReadableRedactions(): void {
|
toggleReadableRedactions(): void {
|
||||||
@ -79,11 +78,23 @@ export class ReadableRedactionsService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
updateState() {
|
updateState() {
|
||||||
|
this.#updateIconState();
|
||||||
this._pdf.instance.UI.updateElement(HeaderElements.TOGGLE_READABLE_REDACTIONS, {
|
this._pdf.instance.UI.updateElement(HeaderElements.TOGGLE_READABLE_REDACTIONS, {
|
||||||
title: this._translateService.instant(_('pdf-viewer.header.toggle-readable-redactions'), {
|
title: this._translateService.instant(_('pdf-viewer.header.toggle-readable-redactions'), {
|
||||||
active: this.active,
|
active: this.active,
|
||||||
}),
|
}),
|
||||||
img: this.toggleReadableRedactionsBtnIcon,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#updateIconState() {
|
||||||
|
const element = this._pdf.instance.UI.iframeWindow.document.querySelector(
|
||||||
|
`[data-element=${HeaderElements.TOGGLE_READABLE_REDACTIONS}]`,
|
||||||
|
);
|
||||||
|
if (!element) return;
|
||||||
|
if (!this.active) {
|
||||||
|
element.classList.add('active');
|
||||||
|
} else {
|
||||||
|
element.classList.remove('active');
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -24,6 +24,7 @@ export class TooltipsService {
|
|||||||
|
|
||||||
updateIconState() {
|
updateIconState() {
|
||||||
const element = this._pdf.instance.UI.iframeWindow.document.querySelector(`[data-element=${HeaderElements.TOGGLE_TOOLTIPS}]`);
|
const element = this._pdf.instance.UI.iframeWindow.document.querySelector(`[data-element=${HeaderElements.TOGGLE_TOOLTIPS}]`);
|
||||||
|
if (!element) return;
|
||||||
if (this._userPreferenceService.getFilePreviewTooltipsPreference()) {
|
if (this._userPreferenceService.getFilePreviewTooltipsPreference()) {
|
||||||
element.classList.add('active');
|
element.classList.add('active');
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@ -121,7 +121,7 @@ export class ViewerHeaderService {
|
|||||||
type: 'actionButton',
|
type: 'actionButton',
|
||||||
element: HeaderElements.TOGGLE_READABLE_REDACTIONS,
|
element: HeaderElements.TOGGLE_READABLE_REDACTIONS,
|
||||||
dataElement: HeaderElements.TOGGLE_READABLE_REDACTIONS,
|
dataElement: HeaderElements.TOGGLE_READABLE_REDACTIONS,
|
||||||
img: this._readableRedactionsService.toggleReadableRedactionsBtnIcon,
|
img: this._readableRedactionsService.icon,
|
||||||
onClick: () => this._ngZone.run(() => this._readableRedactionsService.toggleReadableRedactions()),
|
onClick: () => this._ngZone.run(() => this._readableRedactionsService.toggleReadableRedactions()),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -289,20 +289,26 @@ export class ViewerHeaderService {
|
|||||||
],
|
],
|
||||||
];
|
];
|
||||||
|
|
||||||
header.get('selectToolButton').insertAfter(this.#buttons.get(HeaderElements.SHAPE_TOOL_GROUP_BUTTON));
|
const shouldHideRectangleButton = this.#isEnabled(HeaderElements.SHAPE_TOOL_GROUP_BUTTON) ? 0 : 1;
|
||||||
|
if (!shouldHideRectangleButton) {
|
||||||
|
header.get('selectToolButton').insertAfter(this.#buttons.get(HeaderElements.SHAPE_TOOL_GROUP_BUTTON));
|
||||||
|
} else if (header.getItems().includes(this.#buttons.get(HeaderElements.SHAPE_TOOL_GROUP_BUTTON))) {
|
||||||
|
header.get(HeaderElements.SHAPE_TOOL_GROUP_BUTTON).delete();
|
||||||
|
}
|
||||||
|
|
||||||
groups.forEach(group => this.#pushGroup(enabledItems, group));
|
groups.forEach(group => this.#pushGroup(enabledItems, group));
|
||||||
|
|
||||||
const loadAllAnnotationsButton = this.#buttons.get(HeaderElements.LOAD_ALL_ANNOTATIONS);
|
const loadAllAnnotationsButton = this.#buttons.get(HeaderElements.LOAD_ALL_ANNOTATIONS);
|
||||||
let startButtons = 11 - documineButtons;
|
let startButtons = 11 - documineButtons - shouldHideRectangleButton;
|
||||||
let deleteCount = 15 - documineButtons;
|
let deleteCount = 15 - documineButtons - shouldHideRectangleButton;
|
||||||
|
|
||||||
if (this.#isEnabled(HeaderElements.LOAD_ALL_ANNOTATIONS)) {
|
if (this.#isEnabled(HeaderElements.LOAD_ALL_ANNOTATIONS)) {
|
||||||
if (!header.getItems().includes(loadAllAnnotationsButton)) {
|
if (!header.getItems().includes(loadAllAnnotationsButton)) {
|
||||||
header.get('leftPanelButton').insertAfter(loadAllAnnotationsButton);
|
header.get('leftPanelButton').insertAfter(loadAllAnnotationsButton);
|
||||||
}
|
}
|
||||||
startButtons = 12 - documineButtons;
|
startButtons = 12 - documineButtons - shouldHideRectangleButton;
|
||||||
deleteCount = 16 - documineButtons;
|
deleteCount = 16 - documineButtons - shouldHideRectangleButton;
|
||||||
} else {
|
} else if (header.getItems().includes(loadAllAnnotationsButton)) {
|
||||||
header.delete(HeaderElements.LOAD_ALL_ANNOTATIONS);
|
header.delete(HeaderElements.LOAD_ALL_ANNOTATIONS);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -87,3 +87,8 @@ export const DISABLED_HOTKEYS = [
|
|||||||
export const AnnotationToolNames = {
|
export const AnnotationToolNames = {
|
||||||
AnnotationCreateRectangle: 'AnnotationCreateRectangle',
|
AnnotationCreateRectangle: 'AnnotationCreateRectangle',
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
|
export const SelectionModes = {
|
||||||
|
RECTANGULAR: 'rectangular',
|
||||||
|
STRUCTURAL: 'structural',
|
||||||
|
} as const;
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user