Compare commits

...

40 Commits

Author SHA1 Message Date
Dominique Eifländer
2aa20085b0 RED-10148: Use rectangular text selection mode when space is pressed 2024-11-13 14:31:28 +01:00
Adina Țeudan
bd66f39474 RED-10422 - improved number of requests with help of new changes endpoint 2024-11-12 16:43:25 +02:00
Valentin Mihai
969e375986 RED-10396 - fixed current page active annotations re evaluation when they are updated 2024-11-12 11:16:39 +02:00
Nicoleta Panaghiu
d59a4c02a0 RED-10258: add all available fonts. 2024-11-06 17:28:30 +02:00
Valentin Mihai
53e9725243 RED-9944 - fix build 2024-11-01 14:39:35 +02:00
Valentin Mihai
a3b2382bad RED-9944 - Action Items don't appear in document area in webviewer when bulk-select is still unintenionally active 2024-11-01 14:33:51 +02:00
Nicoleta Panaghiu
327620d5f6 RED-9663: removed UNASSIGNED option from the status filter. 2024-10-30 16:03:43 +02:00
Nicoleta Panaghiu
471d111d69 RED-10275: differentiate available types by applyToAll flag state. 2024-10-29 15:21:51 +02:00
Valentin Mihai
46b48de214 RED-9944 - Action Items don't appear in document area in webviewer when bulk-select is still unintenionally active 2024-10-28 16:58:00 +02:00
Valentin Mihai
ae1c137b61 RED-9944 - Action Items don't appear in document area in webviewer when bulk-select is still unintenionally active 2024-10-22 16:42:40 +03:00
Valentin Mihai
84aa3f1c00 RED-9944 - Action Items don't appear in document area in webviewer when bulk-select is still unintenionally active 2024-10-18 14:39:44 +03:00
Valentin Mihai
c8c632a2c1 RED-9944 - cancel the bulk-selection mode when a user clicks somewhere else 2024-10-16 23:12:09 +03:00
Nicoleta Panaghiu
26e8d9fff9 RED-10190: fixed notifications redirect links and padding.. 2024-10-16 13:59:40 +03:00
Valentin Mihai
1fcc95b138 RED-9944 - Action Items don't appear in document area in webviewer when bulk-select is still unintenionally active 2024-10-15 12:07:09 +03:00
Nicoleta Panaghiu
aa171f992b RED-10144: fixed multi-selection randomly activating. 2024-10-09 16:55:14 +03:00
Dan Percic
33bacfd967 Merge branch 'RED-10084-bp' into 'release/4.830.x'
RED-10084: All members of a dossier are able to reanalyse files in error...

See merge request redactmanager/red-ui!596
2024-10-02 09:52:00 +02:00
Dominique Eifländer
ce262a5f3a RED-10084: All members of a dossier are able to reanalyse files in error state, no only the owner or assignee 2024-10-02 09:19:01 +02:00
Nicoleta Panaghiu
c6ec428bc4 RED-9486: fixed naming of images in remove dialog. 2024-09-25 14:56:47 +03:00
Nicoleta Panaghiu
edf9e61132 RED-9993: fixed license labels. 2024-09-23 15:57:50 +03:00
Nicoleta Panaghiu
e57d54a755 RED-10028: added OPTIMIZED_PREVIEW file type in download selection. 2024-09-23 12:52:21 +03:00
Nicoleta Panaghiu
036bfa2878 RED-9993: fixed license information. 2024-09-13 11:01:26 +03:00
Nicoleta Panaghiu
bf9e6816f8 RED-9372: update common ui. 2024-09-10 11:46:24 +03:00
Nicoleta Panaghiu
0bff02a3e7 RED-9372: fixed scrolling on dossier details. 2024-09-10 11:45:34 +03:00
Marius Schummer
9016ff91b3 RED-9937 - Backport of the tooltip label change for the document preview 2024-09-09 16:47:18 +02:00
Nicoleta Panaghiu
afa399baf1 RED-9372: fixed annotation details moving on hover. 2024-09-05 17:22:17 +03:00
Nicoleta Panaghiu
41b8407862 RED-9372: update common ui. 2024-09-05 17:22:04 +03:00
Nicoleta Panaghiu
303aa0a1a4 RED-9937: made preview tab available & trigger reanalysis when required. 2024-09-04 13:32:03 +03:00
Valentin Mihai
262e30e2c0 RED-9454 - Differences between Add Hint- and Add Redaction-Dialog 2024-09-03 17:55:01 +03:00
Nicoleta Panaghiu
901ba934af RED-9987: added sendSetPasswordMail flag in add user dialog. 2024-09-03 14:51:06 +03:00
Nicoleta Panaghiu
ca0fe4cf13 RED-9893: show missing type toaster only once. 2024-08-21 10:55:34 +03:00
Valentin Mihai
5d3eb3751c RED-9862 - Broken resize dialog for imported redaction from preview file with disabled auto-analysis on upload 2024-08-15 17:02:17 +03:00
Nicoleta Panaghiu
1db68cbb7a RED-9589: removed FP option for locally removed and forced redactions. 2024-08-14 11:25:37 +03:00
Nicoleta Panaghiu
c9b25e2af4 RED-9874: added user preference for overwrite file option. 2024-08-13 17:22:59 +03:00
Nicoleta Panaghiu
9881c3e321 RED-9504: fix some translations. 2024-08-02 10:54:51 +03:00
Nicoleta Panaghiu
e4cb33978c RED-9731: fixed help button position on resize. 2024-08-01 14:42:26 +03:00
Nicoleta Panaghiu
1376e1a486 RED-9516: fixed filter options alignment. 2024-08-01 13:17:56 +03:00
Nicoleta Panaghiu
1894a378b5 RED-9742: fixed logo router link. 2024-07-31 13:21:03 +03:00
Nicoleta Panaghiu
701a69db95 RED-9571: fixed initials-avatar not updating in some cases. 2024-07-31 12:48:44 +03:00
Nicoleta Panaghiu
77f94a7983 RED-9589: enabled false positive option for some annotation configs. 2024-07-29 12:20:06 +03:00
Nicoleta Panaghiu
108a3315da RED-9657: additional fixes for help mode links. 2024-07-23 12:46:30 +03:00
354 changed files with 1515 additions and 474 deletions

View File

@ -9,7 +9,7 @@
<redaction-breadcrumbs></redaction-breadcrumbs> <redaction-breadcrumbs></redaction-breadcrumbs>
</div> </div>
<a [matTooltip]="'top-bar.navigation-items.back-to-dashboard' | translate" [routerLink]="['/']" class="logo"> <a [matTooltip]="'top-bar.navigation-items.back-to-dashboard' | translate" [routerLink]="['/main']" class="logo">
<div [attr.help-mode-key]="'home'" class="actions"> <div [attr.help-mode-key]="'home'" class="actions">
<iqser-logo (iqserHiddenAction)="userPreferenceService.toggleDevFeatures()" icon="iqser:logo"></iqser-logo> <iqser-logo (iqserHiddenAction)="userPreferenceService.toggleDevFeatures()" icon="iqser:logo"></iqser-logo>
<div class="app-name">{{ titleService.getTitle() }}</div> <div class="app-name">{{ titleService.getTitle() }}</div>

View File

@ -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;

View File

@ -1,6 +1,6 @@
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 { Dictionary, LogEntryEngines, SuperTypes } from '@red/domain'; import { Dictionary, SuperTypes } from '@red/domain';
export const canUndo = (annotation: AnnotationWrapper, isApprover: boolean) => !isApprover && annotation.pending; export const canUndo = (annotation: AnnotationWrapper, isApprover: boolean) => !isApprover && annotation.pending;
@ -17,8 +17,9 @@ export const canAcceptRecommendation = (annotation: AnnotationWrapper) => annota
export const canMarkAsFalsePositive = (annotation: AnnotationWrapper, annotationEntity: Dictionary) => export const canMarkAsFalsePositive = (annotation: AnnotationWrapper, annotationEntity: Dictionary) =>
annotation.canBeMarkedAsFalsePositive && annotation.canBeMarkedAsFalsePositive &&
!annotation.hasBeenForcedRedaction &&
!annotation.hasBeenResizedLocally && !annotation.hasBeenResizedLocally &&
!annotation.isRemovedLocally &&
!annotation.hasBeenForcedRedaction &&
annotationEntity?.hasDictionary; annotationEntity?.hasDictionary;
export const canRemoveOnlyHere = (annotation: AnnotationWrapper, canAddRedaction: boolean, autoAnalysisDisabled: boolean) => export const canRemoveOnlyHere = (annotation: AnnotationWrapper, canAddRedaction: boolean, autoAnalysisDisabled: boolean) =>
@ -30,7 +31,7 @@ export const canRemoveFromDictionary = (annotation: AnnotationWrapper, autoAnaly
annotation.isModifyDictionary && annotation.isModifyDictionary &&
(annotation.isRedacted || annotation.isSkipped || annotation.isHint || (annotation.isIgnoredHint && !annotation.isRuleBased)) && (annotation.isRedacted || annotation.isSkipped || annotation.isHint || (annotation.isIgnoredHint && !annotation.isRuleBased)) &&
(autoAnalysisDisabled || !annotation.pending) && (autoAnalysisDisabled || !annotation.pending) &&
[LogEntryEngines.DICTIONARY, LogEntryEngines.DOSSIER_DICTIONARY].some(engine => annotation.engines.includes(engine)); annotation.isDictBased;
export const canRemoveRedaction = (annotation: AnnotationWrapper, permissions: AnnotationPermissions) => export const canRemoveRedaction = (annotation: AnnotationWrapper, permissions: AnnotationPermissions) =>
(!annotation.isIgnoredHint || !annotation.isRuleBased) && (!annotation.isIgnoredHint || !annotation.isRuleBased) &&

View File

@ -74,7 +74,7 @@ export class AnnotationWrapper implements IListable {
} }
get isDictBased() { get isDictBased() {
return this.engines.includes(LogEntryEngines.DICTIONARY); return [LogEntryEngines.DICTIONARY, LogEntryEngines.DOSSIER_DICTIONARY].some(engine => this.engines.includes(engine));
} }
get isRedactedImageHint() { get isRedactedImageHint() {

View File

@ -37,6 +37,11 @@
{{ 'preferences-screen.form.help-mode-dialog' | translate }} {{ 'preferences-screen.form.help-mode-dialog' | translate }}
</mat-checkbox> </mat-checkbox>
</div> </div>
<div class="iqser-input-group">
<mat-checkbox color="primary" formControlName="overwriteFileOption">
{{ 'preferences-screen.form.overwrite-file-option' | translate }}
</mat-checkbox>
</div>
</ng-container> </ng-container>
</div> </div>
</div> </div>

View File

@ -1,4 +1,4 @@
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, OnInit } from '@angular/core'; import { ChangeDetectionStrategy, ChangeDetectorRef, Component, inject, OnInit } from '@angular/core';
import { FormBuilder, FormGroup, ReactiveFormsModule } from '@angular/forms'; import { FormBuilder, FormGroup, ReactiveFormsModule } from '@angular/forms';
import { ActivatedRoute } from '@angular/router'; import { ActivatedRoute } from '@angular/router';
import { import {
@ -28,6 +28,7 @@ interface PreferencesForm {
// warnings preferences // warnings preferences
loadAllAnnotationsWarning: boolean; loadAllAnnotationsWarning: boolean;
helpModeDialog: boolean; helpModeDialog: boolean;
overwriteFileOption: boolean;
[k: string]: any; [k: string]: any;
} }
@ -58,6 +59,12 @@ const Screens = {
], ],
}) })
export class PreferencesComponent extends BaseFormComponent implements OnInit { export class PreferencesComponent extends BaseFormComponent implements OnInit {
readonly #formBuilder = inject(FormBuilder);
readonly #permissionsService = inject(IqserPermissionsService);
readonly #changeRef = inject(ChangeDetectorRef);
readonly #loadingService = inject(LoadingService);
readonly #userPreferenceService = inject(UserPreferenceService);
readonly form: FormGroup<AsControl<PreferencesForm>>; readonly form: FormGroup<AsControl<PreferencesForm>>;
readonly currentScreen: Screen; readonly currentScreen: Screen;
readonly screens = Screens; readonly screens = Screens;
@ -66,81 +73,92 @@ export class PreferencesComponent extends BaseFormComponent implements OnInit {
readonly config = getConfig(); readonly config = getConfig();
readonly isIqserDevMode = isIqserDevMode(); readonly isIqserDevMode = isIqserDevMode();
constructor( get #isOverwriteFileOptionActive() {
route: ActivatedRoute, return !(this.#userPreferenceService.getOverwriteFileOption() === 'undefined');
readonly userPreferenceService: UserPreferenceService, }
private readonly _formBuilder: FormBuilder,
private readonly _permissionsService: IqserPermissionsService, constructor(route: ActivatedRoute) {
private readonly _changeRef: ChangeDetectorRef,
private readonly _loadingService: LoadingService,
) {
super(); super();
this.form = this._formBuilder.group({ this.form = this.#formBuilder.group({
// preferences // preferences
autoExpandFiltersOnActions: [this.userPreferenceService.getAutoExpandFiltersOnActions()], autoExpandFiltersOnActions: [this.#userPreferenceService.getAutoExpandFiltersOnActions()],
openScmDialogByDefault: [this.userPreferenceService.getOpenScmDialogByDefault()], openScmDialogByDefault: [this.#userPreferenceService.getOpenScmDialogByDefault()],
tableExtractionType: [this.userPreferenceService.getTableExtractionType()], tableExtractionType: [this.#userPreferenceService.getTableExtractionType()],
// warnings preferences // warnings preferences
loadAllAnnotationsWarning: [this.userPreferenceService.getBool(PreferencesKeys.loadAllAnnotationsWarning)], loadAllAnnotationsWarning: [this.#userPreferenceService.getBool(PreferencesKeys.loadAllAnnotationsWarning)],
helpModeDialog: [this.userPreferenceService.getBool(KEYS.helpModeDialog)], helpModeDialog: [this.#userPreferenceService.getBool(KEYS.helpModeDialog)],
overwriteFileOption: [this.#isOverwriteFileOptionActive],
}); });
if (!this._permissionsService.has(Roles.managePreferences)) { if (!this.#permissionsService.has(Roles.managePreferences)) {
this.form.disable(); this.form.disable();
} }
if (!this._permissionsService.has(Roles.getTables)) { if (!this.#permissionsService.has(Roles.getTables)) {
this.form.controls.tableExtractionType.disable(); this.form.controls.tableExtractionType.disable();
} }
if (!this.#isOverwriteFileOptionActive) {
this.form.controls.overwriteFileOption.disable();
}
this.initialFormValue = this.form.getRawValue(); this.initialFormValue = this.form.getRawValue();
this.currentScreen = route.snapshot.data.screen; this.currentScreen = route.snapshot.data.screen;
} }
ngOnInit() { ngOnInit() {
this._loadingService.stop(); this.#loadingService.stop();
} }
async save(): Promise<any> { async save(): Promise<any> {
if (this.form.controls.autoExpandFiltersOnActions.value !== this.userPreferenceService.getAutoExpandFiltersOnActions()) { if (this.form.controls.autoExpandFiltersOnActions.value !== this.#userPreferenceService.getAutoExpandFiltersOnActions()) {
await this.userPreferenceService.toggleAutoExpandFiltersOnActions(); await this.#userPreferenceService.toggleAutoExpandFiltersOnActions();
} }
if (this.form.controls.openScmDialogByDefault.value !== this.userPreferenceService.getOpenScmDialogByDefault()) { if (this.form.controls.openScmDialogByDefault.value !== this.#userPreferenceService.getOpenScmDialogByDefault()) {
await this.userPreferenceService.toggleOpenScmDialogByDefault(); await this.#userPreferenceService.toggleOpenScmDialogByDefault();
} }
if (this.form.controls.tableExtractionType.value !== this.userPreferenceService.getTableExtractionType()) { if (this.form.controls.tableExtractionType.value !== this.#userPreferenceService.getTableExtractionType()) {
await this.userPreferenceService.save(PreferencesKeys.tableExtractionType, this.form.controls.tableExtractionType.value); await this.#userPreferenceService.save(PreferencesKeys.tableExtractionType, this.form.controls.tableExtractionType.value);
} }
if ( if (
this.form.controls.loadAllAnnotationsWarning.value !== this.form.controls.loadAllAnnotationsWarning.value !==
this.userPreferenceService.getBool(PreferencesKeys.loadAllAnnotationsWarning) this.#userPreferenceService.getBool(PreferencesKeys.loadAllAnnotationsWarning)
) { ) {
await this.userPreferenceService.save( await this.#userPreferenceService.save(
PreferencesKeys.loadAllAnnotationsWarning, PreferencesKeys.loadAllAnnotationsWarning,
String(this.form.controls.loadAllAnnotationsWarning.value), String(this.form.controls.loadAllAnnotationsWarning.value),
); );
} }
if (this.form.controls.helpModeDialog.value !== this.userPreferenceService.getBool(KEYS.helpModeDialog)) { if (this.form.controls.helpModeDialog.value !== this.#userPreferenceService.getBool(KEYS.helpModeDialog)) {
await this.userPreferenceService.save(KEYS.helpModeDialog, String(this.form.controls.helpModeDialog.value)); await this.#userPreferenceService.save(KEYS.helpModeDialog, String(this.form.controls.helpModeDialog.value));
} }
await this.userPreferenceService.reload(); if (this.form.controls.overwriteFileOption.enabled && !this.form.controls.overwriteFileOption.value) {
await this.#userPreferenceService.saveOverwriteFileOption('undefined');
}
await this.#userPreferenceService.reload();
this.#patchValues(); this.#patchValues();
this.initialFormValue = this.form.getRawValue(); this.initialFormValue = this.form.getRawValue();
this._changeRef.markForCheck(); this.#changeRef.markForCheck();
} }
#patchValues() { #patchValues() {
this.form.patchValue({ this.form.patchValue({
autoExpandFiltersOnActions: this.userPreferenceService.getAutoExpandFiltersOnActions(), autoExpandFiltersOnActions: this.#userPreferenceService.getAutoExpandFiltersOnActions(),
openScmDialogByDefault: this.userPreferenceService.getOpenScmDialogByDefault(), openScmDialogByDefault: this.#userPreferenceService.getOpenScmDialogByDefault(),
tableExtractionType: this.userPreferenceService.getTableExtractionType(), tableExtractionType: this.#userPreferenceService.getTableExtractionType(),
loadAllAnnotationsWarning: this.userPreferenceService.getBool(PreferencesKeys.loadAllAnnotationsWarning), loadAllAnnotationsWarning: this.#userPreferenceService.getBool(PreferencesKeys.loadAllAnnotationsWarning),
helpModeDialog: this.userPreferenceService.getBool(KEYS.helpModeDialog), helpModeDialog: this.#userPreferenceService.getBool(KEYS.helpModeDialog),
overwriteFileOption: this.#isOverwriteFileOptionActive,
}); });
if (!this.#isOverwriteFileOptionActive) {
this.form.controls.overwriteFileOption.disable();
}
} }
} }

View File

@ -1,6 +1,6 @@
<div <div
[translateParams]="{ [translateParams]="{
type: user ? 'edit' : 'create' type: !!user() ? 'edit' : 'create',
}" }"
[translate]="'add-edit-user.title'" [translate]="'add-edit-user.title'"
class="dialog-header heading-l" class="dialog-header heading-l"
@ -37,9 +37,17 @@
</div> </div>
</div> </div>
@if (!user()) {
<div class="iqser-input-group">
<label [translate]="'add-edit-user.form.account-setup'"></label>
<mat-checkbox formControlName="sendSetPasswordMail">{{ 'add-edit-user.form.send-email' | translate }}</mat-checkbox>
<span [translate]="'add-edit-user.form.send-email-explanation'" class="hint"></span>
</div>
}
<div <div
(click)="toggleResetPassword.emit()" (click)="toggleResetPassword.emit()"
*ngIf="!!user" *ngIf="!!user()"
[translate]="'add-edit-user.form.reset-password'" [translate]="'add-edit-user.form.reset-password'"
class="mt-24 fit-content link-action" class="mt-24 fit-content link-action"
></div> ></div>
@ -48,14 +56,14 @@
<div class="dialog-actions"> <div class="dialog-actions">
<iqser-icon-button <iqser-icon-button
[disabled]="form.invalid || !changed" [disabled]="form.invalid || !changed"
[label]="(user ? 'add-edit-user.actions.save-changes' : 'add-edit-user.actions.save') | translate" [label]="(user() ? 'add-edit-user.actions.save-changes' : 'add-edit-user.actions.save') | translate"
[submit]="true" [submit]="true"
[type]="iconButtonTypes.primary" [type]="iconButtonTypes.primary"
></iqser-icon-button> ></iqser-icon-button>
<iqser-icon-button <iqser-icon-button
(action)="delete()" (action)="delete()"
*ngIf="user && !disabledDelete(user)" *ngIf="user() && !disabledDelete(user())"
[label]="'add-edit-user.actions.delete' | translate" [label]="'add-edit-user.actions.delete' | translate"
[type]="iconButtonTypes.dark" [type]="iconButtonTypes.dark"
icon="iqser:trash" icon="iqser:trash"

View File

@ -5,3 +5,7 @@
margin-top: 8px; margin-top: 8px;
width: 300px; width: 300px;
} }
.hint {
margin-left: 23px;
}

View File

@ -1,4 +1,4 @@
import { Component, EventEmitter, Input, OnChanges, Output } from '@angular/core'; import { Component, input, OnInit, output } from '@angular/core';
import { ReactiveFormsModule, UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms'; import { ReactiveFormsModule, UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms';
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';
@ -20,15 +20,15 @@ import { NgForOf, NgIf } from '@angular/common';
standalone: true, standalone: true,
imports: [TranslateModule, ReactiveFormsModule, MatCheckbox, NgForOf, IconButtonComponent, NgIf], imports: [TranslateModule, ReactiveFormsModule, MatCheckbox, NgForOf, IconButtonComponent, NgIf],
}) })
export class UserDetailsComponent extends BaseFormComponent implements OnChanges { export class UserDetailsComponent extends BaseFormComponent implements OnInit {
/** e.g. a RED_ADMIN is automatically a RED_USER_ADMIN => can't disable RED_USER_ADMIN as long as RED_ADMIN is checked */ user = input<User>();
private readonly _ROLE_REQUIREMENTS = { RED_MANAGER: 'RED_USER', RED_ADMIN: 'RED_USER_ADMIN' }; readonly toggleResetPassword = output();
@Input() user: User; readonly closeDialog = output<boolean>();
@Output() readonly toggleResetPassword = new EventEmitter(); readonly cancel = output();
@Output() readonly closeDialog = new EventEmitter();
@Output() readonly cancel = new EventEmitter();
readonly ROLES = ['RED_USER', 'RED_MANAGER', 'RED_USER_ADMIN', 'RED_ADMIN']; readonly ROLES = ['RED_USER', 'RED_MANAGER', 'RED_USER_ADMIN', 'RED_ADMIN'];
readonly translations = rolesTranslations; readonly translations = rolesTranslations;
/** e.g. a RED_ADMIN is automatically a RED_USER_ADMIN => can't disable RED_USER_ADMIN as long as RED_ADMIN is checked */
readonly #ROLE_REQUIREMENTS = { RED_MANAGER: 'RED_USER', RED_ADMIN: 'RED_USER_ADMIN' };
constructor( constructor(
private readonly _formBuilder: UntypedFormBuilder, private readonly _formBuilder: UntypedFormBuilder,
@ -49,13 +49,17 @@ export class UserDetailsComponent extends BaseFormComponent implements OnChanges
}, []); }, []);
} }
private get _rolesControls(): any { get sendSetPasswordMail() {
return !this.form.controls.sendSetPasswordMail.value;
}
get #rolesControls() {
return this.ROLES.reduce( return this.ROLES.reduce(
(prev, role) => ({ (prev, role) => ({
...prev, ...prev,
[role]: [ [role]: [
{ {
value: this.user && this.user.has(role), value: this.user() && this.user().has(role),
disabled: this.shouldBeDisabled(role), disabled: this.shouldBeDisabled(role),
}, },
], ],
@ -64,20 +68,20 @@ export class UserDetailsComponent extends BaseFormComponent implements OnChanges
); );
} }
ngOnChanges() { ngOnInit() {
this.form = this._getForm(); this.form = this.#getForm();
this.initialFormValue = this.form.getRawValue(); this.initialFormValue = this.form.getRawValue();
} }
shouldBeDisabled(role: string): boolean { shouldBeDisabled(role: string): boolean {
const isCurrentAdminUser = this.user && this.user.isAdmin && this.user.id === this._userService.currentUser.id; const isCurrentAdminUser = this.user() && this.user().isAdmin && this.user().id === this._userService.currentUser.id;
return ( return (
// RED_ADMIN can't remove own RED_ADMIN role // RED_ADMIN can't remove own RED_ADMIN role
(role === 'RED_ADMIN' && isCurrentAdminUser) || (role === 'RED_ADMIN' && isCurrentAdminUser) ||
// only RED_ADMINs can edit RED_ADMIN roles // only RED_ADMINs can edit RED_ADMIN roles
(role === 'RED_ADMIN' && !this._userService.currentUser.isAdmin) || (role === 'RED_ADMIN' && !this._userService.currentUser.isAdmin) ||
Object.keys(this._ROLE_REQUIREMENTS).reduce( Object.keys(this.#ROLE_REQUIREMENTS).reduce(
(value, key) => value || (role === this._ROLE_REQUIREMENTS[key] && this.user?.roles.includes(key)), (value, key) => value || (role === this.#ROLE_REQUIREMENTS[key] && this.user()?.roles.includes(key)),
false, false,
) )
); );
@ -85,9 +89,13 @@ export class UserDetailsComponent extends BaseFormComponent implements OnChanges
async save() { async save() {
this._loadingService.start(); this._loadingService.start();
const userData: IProfileUpdateRequest = { ...this.form.getRawValue(), roles: this.activeRoles }; const userData: IProfileUpdateRequest = {
...this.form.getRawValue(),
roles: this.activeRoles,
sendSetPasswordMail: this.sendSetPasswordMail,
};
if (!this.user) { if (!this.user()) {
await firstValueFrom(this._userService.create(userData)) await firstValueFrom(this._userService.create(userData))
.then(() => { .then(() => {
this.closeDialog.emit(true); this.closeDialog.emit(true);
@ -101,22 +109,22 @@ export class UserDetailsComponent extends BaseFormComponent implements OnChanges
this._loadingService.stop(); this._loadingService.stop();
}); });
} else { } else {
await firstValueFrom(this._userService.updateProfile(userData, this.user.id)); await firstValueFrom(this._userService.updateProfile(userData, this.user().id));
this.closeDialog.emit(true); this.closeDialog.emit(true);
} }
} }
delete() { delete() {
this._dialogService.deleteUsers([this.user.id], () => this.closeDialog.emit(true)); this._dialogService.deleteUsers([this.user().id], () => this.closeDialog.emit(true));
} }
setRolesRequirements(checked: boolean, role: string): void { setRolesRequirements(checked: boolean, role: string): void {
if (Object.keys(this._ROLE_REQUIREMENTS).includes(role)) { if (Object.keys(this.#ROLE_REQUIREMENTS).includes(role)) {
if (checked) { if (checked) {
this.form.patchValue({ [this._ROLE_REQUIREMENTS[role]]: true }); this.form.patchValue({ [this.#ROLE_REQUIREMENTS[role]]: true });
this.form.controls[this._ROLE_REQUIREMENTS[role]].disable(); this.form.controls[this.#ROLE_REQUIREMENTS[role]].disable();
} else { } else {
this.form.controls[this._ROLE_REQUIREMENTS[role]].enable(); this.form.controls[this.#ROLE_REQUIREMENTS[role]].enable();
} }
} }
} }
@ -127,18 +135,19 @@ export class UserDetailsComponent extends BaseFormComponent implements OnChanges
return user.id === this._userService.currentUser.id || (userAdmin && !currentUserAdmin); return user.id === this._userService.currentUser.id || (userAdmin && !currentUserAdmin);
} }
private _getForm(): UntypedFormGroup { #getForm(): UntypedFormGroup {
return this._formBuilder.group({ return this._formBuilder.group({
firstName: [this.user?.firstName, Validators.required], firstName: [this.user()?.firstName, Validators.required],
lastName: [this.user?.lastName, Validators.required], lastName: [this.user()?.lastName, Validators.required],
email: [ email: [
{ {
value: this.user?.email, value: this.user()?.email,
disabled: !!this.user?.email, disabled: !!this.user()?.email,
}, },
[Validators.required, Validators.email], [Validators.required, Validators.email],
], ],
...this._rolesControls, ...this.#rolesControls,
sendSetPasswordMail: [false],
}); });
} }
} }

View File

@ -1,7 +1,7 @@
<div class="content-container" iqserHasScrollbar> <div class="content-container" iqserHasScrollbar>
<div class="dialog"> <div class="dialog">
<div class="dialog-header"> <div class="dialog-header">
<div [translate]="'entity.info.heading'" [attr.help-mode-key]="'entity_info'" class="heading-l"></div> <div [translate]="'entity.info.heading'" [attr.help-mode-key]="'entity_info'" class="heading-l w-full"></div>
<div *ngIf="!permissionsService.canEditEntities()" class="read-only-indicator all-caps-label primary"> <div *ngIf="!permissionsService.canEditEntities()" class="read-only-indicator all-caps-label primary">
<mat-icon class="mr-8" svgIcon="red:read-only"></mat-icon> <mat-icon class="mr-8" svgIcon="red:read-only"></mat-icon>
@ -26,8 +26,6 @@
></iqser-icon-button> ></iqser-icon-button>
<div (click)="revert()" [translate]="'entity.info.actions.revert'" class="all-caps-label cancel"></div> <div (click)="revert()" [translate]="'entity.info.actions.revert'" class="all-caps-label cancel"></div>
<iqser-help-button *ngIf="!config.IS_DOCUMINE"></iqser-help-button>
</div> </div>
</div> </div>
</div> </div>

View File

@ -26,3 +26,7 @@
min-height: unset; min-height: unset;
} }
} }
.w-full {
width: 100%;
}

View File

@ -27,7 +27,7 @@ import { MatDatepickerModule } from '@angular/material/datepicker';
import { MatIcon } from '@angular/material/icon'; import { MatIcon } from '@angular/material/icon';
import { SelectComponent } from '@shared/components/select/select.component'; import { SelectComponent } from '@shared/components/select/select.component';
const downloadTypes = ['ORIGINAL', 'PREVIEW', 'DELTA_PREVIEW', 'REDACTED'].map(type => ({ const downloadTypes = ['ORIGINAL', 'PREVIEW', 'OPTIMIZED_PREVIEW', 'DELTA_PREVIEW', 'REDACTED'].map(type => ({
key: type, key: type,
label: downloadTypesTranslations[type], label: downloadTypesTranslations[type],
})); }));

View File

@ -3,7 +3,7 @@ import { LicenseService } from '@services/license.service';
import { map } from 'rxjs/operators'; import { map } from 'rxjs/operators';
import { ChartDataset } from 'chart.js'; import { ChartDataset } from 'chart.js';
import { ChartBlue, ChartGreen, ChartRed } from '../../utils/constants'; import { ChartBlue, ChartGreen, ChartRed } from '../../utils/constants';
import { getDataUntilCurrentMonth, getLabelsFromLicense, getLineConfig, isCurrentMonthAndYear } from '../../utils/functions'; import { getDataUntilCurrentMonth, getLabelsFromLicense, getLineConfig, isCurrentMonth } from '../../utils/functions';
import { TranslateModule, TranslateService } from '@ngx-translate/core'; import { TranslateModule, TranslateService } from '@ngx-translate/core';
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker'; import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
import { size } from '@iqser/common-ui/lib/utils'; import { size } from '@iqser/common-ui/lib/utils';
@ -43,7 +43,7 @@ export class LicenseAnalysisCapacityUsageComponent {
#getCapacityDatasets(): ChartDataset[] { #getCapacityDatasets(): ChartDataset[] {
const monthlyData = [...this.licenseService.selectedLicenseReport.monthlyData]; const monthlyData = [...this.licenseService.selectedLicenseReport.monthlyData];
const dataUntilCurrentMonth = getDataUntilCurrentMonth(monthlyData); const dataUntilCurrentMonth = getDataUntilCurrentMonth(monthlyData);
if (monthlyData.length === 1 || isCurrentMonthAndYear(monthlyData[0].startDate)) { if (monthlyData.length === 1 || isCurrentMonth(monthlyData[0].startDate)) {
const empty = { analysedFilesBytes: null } as ILicenseData; const empty = { analysedFilesBytes: null } as ILicenseData;
dataUntilCurrentMonth.splice(0, 0, empty); dataUntilCurrentMonth.splice(0, 0, empty);
monthlyData.splice(0, 0, empty); monthlyData.splice(0, 0, empty);
@ -60,11 +60,8 @@ export class LicenseAnalysisCapacityUsageComponent {
}, },
{ {
data: dataUntilCurrentMonth.map((month, monthIndex) => data: dataUntilCurrentMonth.map((_, monthIndex) =>
month.analysedFilesBytes monthlyData.slice(0, monthIndex + 1).reduce((acc, curr) => acc + (curr.analysedFilesBytes ?? 0), 0),
? month.analysedFilesBytes +
monthlyData.slice(0, monthIndex).reduce((acc, curr) => acc + (curr.analysedFilesBytes ?? 0), 0)
: 0,
), ),
label: this._translateService.instant('license-info-screen.analysis-capacity-usage.analyzed-cumulative'), label: this._translateService.instant('license-info-screen.analysis-capacity-usage.analyzed-cumulative'),
yAxisID: 'y1', yAxisID: 'y1',

View File

@ -3,7 +3,7 @@ import { LicenseService } from '@services/license.service';
import { map } from 'rxjs/operators'; import { map } from 'rxjs/operators';
import { ChartDataset } from 'chart.js'; import { ChartDataset } from 'chart.js';
import { ChartBlue, ChartGreen, ChartRed } from '../../utils/constants'; import { ChartBlue, ChartGreen, ChartRed } from '../../utils/constants';
import { getDataUntilCurrentMonth, getLabelsFromLicense, getLineConfig, isCurrentMonthAndYear } from '../../utils/functions'; import { getDataUntilCurrentMonth, getLabelsFromLicense, getLineConfig, isCurrentMonth } from '../../utils/functions';
import { TranslateModule, TranslateService } from '@ngx-translate/core'; import { TranslateModule, TranslateService } from '@ngx-translate/core';
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker'; import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
import { ILicenseData } from '@red/domain'; import { ILicenseData } from '@red/domain';
@ -40,7 +40,7 @@ export class LicensePageUsageComponent {
#getPagesDatasets(): ChartDataset[] { #getPagesDatasets(): ChartDataset[] {
const monthlyData = [...this.licenseService.selectedLicenseReport.monthlyData]; const monthlyData = [...this.licenseService.selectedLicenseReport.monthlyData];
const dataUntilCurrentMonth = getDataUntilCurrentMonth(monthlyData); const dataUntilCurrentMonth = getDataUntilCurrentMonth(monthlyData);
if (monthlyData.length === 1 || isCurrentMonthAndYear(monthlyData[0].startDate)) { if (monthlyData.length === 1 || isCurrentMonth(monthlyData[0].startDate)) {
const empty = { numberOfAnalyzedPages: null } as ILicenseData; const empty = { numberOfAnalyzedPages: null } as ILicenseData;
dataUntilCurrentMonth.splice(0, 0, empty); dataUntilCurrentMonth.splice(0, 0, empty);
monthlyData.splice(0, 0, empty); monthlyData.splice(0, 0, empty);
@ -63,11 +63,8 @@ export class LicensePageUsageComponent {
order: 1, order: 1,
}, },
{ {
data: dataUntilCurrentMonth.map((month, monthIndex) => data: dataUntilCurrentMonth.map((_, monthIndex) =>
month.numberOfAnalyzedPages monthlyData.slice(0, monthIndex + 1).reduce((acc, curr) => acc + (curr.numberOfAnalyzedPages ?? 0), 0),
? month.numberOfAnalyzedPages +
monthlyData.slice(0, monthIndex).reduce((acc, curr) => acc + (curr.numberOfAnalyzedPages ?? 0), 0)
: 0,
), ),
label: this._translateService.instant('license-info-screen.page-usage.cumulative-pages'), label: this._translateService.instant('license-info-screen.page-usage.cumulative-pages'),
yAxisID: 'y1', yAxisID: 'y1',

View File

@ -6,7 +6,6 @@ import { ComplexFillTarget } from 'chart.js/dist/types';
const monthNames = dayjs.monthsShort(); const monthNames = dayjs.monthsShort();
const currentMonth = dayjs(Date.now()).month(); const currentMonth = dayjs(Date.now()).month();
const currentYear = dayjs(Date.now()).year();
export const verboseDate = (date: Dayjs) => `${monthNames[date.month()]} ${date.year()}`; export const verboseDate = (date: Dayjs) => `${monthNames[date.month()]} ${date.year()}`;
@ -45,7 +44,7 @@ export const getLabelsFromLicense = (license: ILicenseReport) => {
monthIterator = monthIterator.add(1, 'month'); monthIterator = monthIterator.add(1, 'month');
} }
if (startMonth.month() === endMonth.month() || startMonth.month() === currentMonth) { if (startMonth.isSame(endMonth, 'month') || isCurrentMonth(startMonth.toDate())) {
result.splice(0, 0, ''); result.splice(0, 0, '');
} }
@ -53,9 +52,9 @@ export const getLabelsFromLicense = (license: ILicenseReport) => {
}; };
export const getDataUntilCurrentMonth = (monthlyData: ILicenseData[]) => { export const getDataUntilCurrentMonth = (monthlyData: ILicenseData[]) => {
return monthlyData.filter(data => dayjs(data.startDate).month() <= currentMonth && dayjs(data.startDate).year() <= currentYear); return monthlyData.filter(data => dayjs(data.startDate).isSameOrBefore(dayjs(Date.now()), 'month'));
}; };
export const isCurrentMonthAndYear = (date: Date | string) => { export const isCurrentMonth = (date: Date | string) => {
return dayjs(date).month() === currentMonth && dayjs(date).year() === currentYear; return dayjs(date).isSame(dayjs(Date.now()), 'month');
}; };

View File

@ -4,7 +4,6 @@ import { ChangeDetectorRef, Component, ElementRef, inject, OnInit, ViewChild } f
import { FormBuilder, FormGroup, ReactiveFormsModule } from '@angular/forms'; import { FormBuilder, FormGroup, ReactiveFormsModule } from '@angular/forms';
import { MatIcon } from '@angular/material/icon'; import { MatIcon } from '@angular/material/icon';
import { MatSlider, MatSliderThumb } from '@angular/material/slider'; import { MatSlider, MatSliderThumb } from '@angular/material/slider';
import { MatTooltip } from '@angular/material/tooltip';
import { Router } from '@angular/router'; import { Router } from '@angular/router';
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker'; import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
import { environment } from '@environments/environment'; import { environment } from '@environments/environment';
@ -46,6 +45,7 @@ import { ColorPickerModule } from 'ngx-color-picker';
import { BehaviorSubject, firstValueFrom, Observable, of } from 'rxjs'; import { BehaviorSubject, firstValueFrom, Observable, of } from 'rxjs';
import { tap } from 'rxjs/operators'; import { tap } from 'rxjs/operators';
import { PaginatorComponent } from '../paginator/paginator.component'; import { PaginatorComponent } from '../paginator/paginator.component';
import { MatTooltip } from '@angular/material/tooltip';
export const DEFAULT_WATERMARK: Partial<IWatermark> = { export const DEFAULT_WATERMARK: Partial<IWatermark> = {
text: 'Watermark', text: 'Watermark',
@ -85,11 +85,11 @@ interface WatermarkForm {
HasScrollbarDirective, HasScrollbarDirective,
NgForOf, NgForOf,
NgClass, NgClass,
MatTooltip,
MatIcon, MatIcon,
MatSlider, MatSlider,
ColorPickerModule, ColorPickerModule,
MatSliderThumb, MatSliderThumb,
MatTooltip,
], ],
}) })
export class WatermarkScreenComponent implements OnInit { export class WatermarkScreenComponent implements OnInit {
@ -269,7 +269,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();

View File

@ -158,6 +158,7 @@ export class AdminSideNavComponent implements OnInit {
{ {
screen: 'info', screen: 'info',
label: _('admin-side-nav.entity-info'), label: _('admin-side-nav.entity-info'),
helpModeKey: 'entity_info',
show: true, show: true,
}, },
{ {

View File

@ -3,7 +3,7 @@ import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
export const digitalSignatureDialogTranslations = { export const digitalSignatureDialogTranslations = {
title: { title: {
beforeConfiguration: _('digital-signature-dialog.title.before-configuration'), beforeConfiguration: _('digital-signature-dialog.title.before-configuration'),
pkcs: _('digital-signature-dialog.title.pkcs'), PKCS: _('digital-signature-dialog.title.pkcs'),
kms: _('digital-signature-dialog.title.kms'), KMS: _('digital-signature-dialog.title.kms'),
}, },
} as const; } as const;

View File

@ -116,7 +116,7 @@ export class ConfigService {
return [ return [
{ {
id: 'editDossier', id: 'editDossier',
label: this._translateService.instant('dossier-overview.header-actions.edit'), label: _('dossier-overview.header-actions.edit'),
action: () => this.#openEditDossierDialog(dossierId), action: () => this.#openEditDossierDialog(dossierId),
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),

View File

@ -56,7 +56,7 @@
</iqser-workflow> </iqser-workflow>
</div> </div>
<div *ngIf="dossierAttributes$ | async" [class.collapsed]="collapsedDetails" class="right-container" iqserHasScrollbar> <div *ngIf="dossierAttributes$ | async" [class.collapsed]="collapsedDetails" class="right-container">
<redaction-dossier-details <redaction-dossier-details
(toggleCollapse)="collapsedDetails = !collapsedDetails" (toggleCollapse)="collapsedDetails = !collapsedDetails"
[dossierAttributes]="dossierAttributes" [dossierAttributes]="dossierAttributes"

View File

@ -25,10 +25,7 @@
width: 375px; width: 375px;
min-width: 375px; min-width: 375px;
padding: 16px 24px 16px 24px; padding: 16px 24px 16px 24px;
overflow-y: auto;
&.has-scrollbar:hover {
padding-right: 13px;
}
redaction-dossier-details { redaction-dossier-details {
width: 100%; width: 100%;

View File

@ -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]))),
); );
} }

View File

@ -147,13 +147,13 @@ export class AnnotationActionsComponent implements OnChanges {
hideAnnotation() { hideAnnotation() {
this._annotationManager.hide(this.viewerAnnotations); this._annotationManager.hide(this.viewerAnnotations);
this._annotationManager.deselect(); this._annotationManager.deselect();
this._annotationManager.addToHidden(this.viewerAnnotations[0].Id); this.viewerAnnotations.forEach(a => this._annotationManager.addToHidden(a.Id));
} }
showAnnotation() { showAnnotation() {
this._annotationManager.show(this.viewerAnnotations); this._annotationManager.show(this.viewerAnnotations);
this._annotationManager.deselect(); this._annotationManager.deselect();
this._annotationManager.removeFromHidden(this.viewerAnnotations[0].Id); this.viewerAnnotations.forEach(a => this._annotationManager.removeFromHidden(a.Id));
} }
resize() { resize() {

View File

@ -2,7 +2,7 @@
display: flex; display: flex;
position: absolute; position: absolute;
top: 6px; top: 6px;
right: 19px; right: 8px;
} }
.popover { .popover {

View File

@ -3,21 +3,13 @@
:host { :host {
width: 100%; width: 100%;
position: relative; position: relative;
overflow: hidden; overflow-y: auto;
@include common-mixins.scroll-bar;
&:hover {
overflow-y: auto;
@include common-mixins.scroll-bar;
}
&.has-scrollbar:hover redaction-annotation-wrapper::ng-deep, &.has-scrollbar:hover redaction-annotation-wrapper::ng-deep,
&::ng-deep.documine-wrapper { &::ng-deep.documine-wrapper {
.annotation { .annotation {
padding-right: 5px; padding-right: 5px;
} }
redaction-annotation-details {
right: 8px;
}
} }
} }

View File

@ -69,6 +69,7 @@ export class AnnotationsListComponent extends HasScrollbarDirective {
this._multiSelectService.activate(); this._multiSelectService.activate();
} }
this._listingService.selectAnnotations(annotation); this._listingService.selectAnnotations(annotation);
this._annotationManager.setSelectedFromWorkload();
} }
referenceClicked(annotation: AnnotationWrapper): void { referenceClicked(annotation: AnnotationWrapper): void {

View File

@ -129,7 +129,7 @@
></iqser-circle-button> ></iqser-circle-button>
<span <span
[translateParams]="{ page: pdf.currentPage(), count: activeAnnotations.length }" [translateParams]="{ page: pdf.currentPage(), count: activeAnnotations().length }"
[translate]="'page'" [translate]="'page'"
class="all-caps-label" class="all-caps-label"
></span> ></span>
@ -160,7 +160,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"

View File

@ -1,5 +1,17 @@
import { AsyncPipe, NgIf, NgTemplateOutlet } from '@angular/common'; import { AsyncPipe, NgIf, NgTemplateOutlet } from '@angular/common';
import { ChangeDetectorRef, Component, computed, effect, ElementRef, HostListener, OnDestroy, OnInit, ViewChild } from '@angular/core'; import {
ChangeDetectorRef,
Component,
computed,
effect,
ElementRef,
HostListener,
OnDestroy,
OnInit,
signal,
untracked,
ViewChild,
} from '@angular/core';
import { MatDialog } from '@angular/material/dialog'; import { MatDialog } from '@angular/material/dialog';
import { MatIcon } from '@angular/material/icon'; import { MatIcon } from '@angular/material/icon';
import { MatTooltip } from '@angular/material/tooltip'; import { MatTooltip } from '@angular/material/tooltip';
@ -46,6 +58,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';
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'];
@ -90,7 +103,8 @@ export class FileWorkloadComponent extends AutoUnsubscribe implements OnInit, On
protected readonly currentPageIsExcluded = computed(() => this.state.file().excludedPages.includes(this.pdf.currentPage())); protected readonly currentPageIsExcluded = computed(() => this.state.file().excludedPages.includes(this.pdf.currentPage()));
protected readonly translations = workloadTranslations; protected readonly translations = workloadTranslations;
protected readonly isDocumine = getConfig().IS_DOCUMINE; protected readonly isDocumine = getConfig().IS_DOCUMINE;
displayedAnnotations = new Map<number, AnnotationWrapper[]>(); displayedAnnotations = signal(new Map<number, AnnotationWrapper[]>());
readonly activeAnnotations = computed(() => this.displayedAnnotations().get(this.pdf.currentPage()) || []);
displayedPages: number[] = []; displayedPages: number[] = [];
pagesPanelActive = true; pagesPanelActive = true;
@ -155,10 +169,6 @@ export class FileWorkloadComponent extends AutoUnsubscribe implements OnInit, On
); );
} }
get activeAnnotations(): AnnotationWrapper[] {
return this.displayedAnnotations.get(this.pdf.currentPage()) || [];
}
get showAnalysisDisabledBanner() { get showAnalysisDisabledBanner() {
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;
@ -215,12 +225,14 @@ export class FileWorkloadComponent extends AutoUnsubscribe implements OnInit, On
} }
selectAllOnActivePage() { selectAllOnActivePage() {
this.listingService.selectAnnotations(this.activeAnnotations); const activeAnnotations = untracked(this.activeAnnotations);
this.listingService.selectAnnotations(activeAnnotations);
} }
deselectAllOnActivePage(): void { deselectAllOnActivePage(): void {
this.listingService.deselect(this.activeAnnotations); const activeAnnotations = untracked(this.activeAnnotations);
this.annotationManager.deselect(this.activeAnnotations); this.listingService.deselect(activeAnnotations);
this.annotationManager.deselect(activeAnnotations);
} }
@HostListener('window:keyup', ['$event']) @HostListener('window:keyup', ['$event'])
@ -322,31 +334,33 @@ export class FileWorkloadComponent extends AutoUnsubscribe implements OnInit, On
this.pdf.navigateTo(this.#nextPageWithAnnotations()); this.pdf.navigateTo(this.#nextPageWithAnnotations());
} }
@Debounce(15)
navigateAnnotations($event: KeyboardEvent) { navigateAnnotations($event: KeyboardEvent) {
const currentPage = this.pdf.currentPage(); const currentPage = this.pdf.currentPage();
const activeAnnotations = untracked(this.activeAnnotations);
if (!this._firstSelectedAnnotation || currentPage !== this._firstSelectedAnnotation.pageNumber) { if (!this._firstSelectedAnnotation || currentPage !== this._firstSelectedAnnotation.pageNumber) {
if (this.displayedPages.indexOf(currentPage) !== -1) { if (this.displayedPages.indexOf(currentPage) !== -1) {
// Displayed page has annotations // Displayed page has annotations
return this.listingService.selectAnnotations(this.activeAnnotations ? this.activeAnnotations[0] : null); return this.listingService.selectAnnotations(activeAnnotations ? activeAnnotations[0] : null);
} }
// Displayed page doesn't have annotations // Displayed page doesn't have annotations
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(prevPageAnnotations[prevPageAnnotations.length - 1]); return this.listingService.selectAnnotations(getLast(prevPageAnnotations));
} }
const page = this._firstSelectedAnnotation.pageNumber; const page = this._firstSelectedAnnotation.pageNumber;
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') {
@ -356,7 +370,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;
@ -374,9 +388,9 @@ 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(prevPageAnnotations[prevPageAnnotations.length - 1]); this.listingService.selectAnnotations(getLast(prevPageAnnotations));
break; break;
} }
} }
@ -413,8 +427,8 @@ export class FileWorkloadComponent extends AutoUnsubscribe implements OnInit, On
annotations = annotations.filter(a => !a.isOCR); annotations = annotations.filter(a => !a.isOCR);
} }
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()];
const enabledFilters = this.filterService.enabledFlatFilters; const enabledFilters = this.filterService.enabledFlatFilters;
if (enabledFilters.some(f => f.id === 'pages-without-annotations')) { if (enabledFilters.some(f => f.id === 'pages-without-annotations')) {
if (enabledFilters.length === 1 && !onlyPageWithAnnotations) { if (enabledFilters.length === 1 && !onlyPageWithAnnotations) {
@ -422,7 +436,7 @@ export class FileWorkloadComponent extends AutoUnsubscribe implements OnInit, On
} else { } else {
this.displayedPages = []; this.displayedPages = [];
} }
this.displayedAnnotations.clear(); this.displayedAnnotations().clear();
} else if (enabledFilters.length || onlyPageWithAnnotations) { } else if (enabledFilters.length || onlyPageWithAnnotations) {
this.displayedPages = pagesThatDisplayAnnotations; this.displayedPages = pagesThatDisplayAnnotations;
} else { } else {
@ -430,17 +444,18 @@ 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() {
const currentPage = this.pdf.currentPage(); const currentPage = this.pdf.currentPage();
const activeAnnotations = untracked(this.activeAnnotations);
if ( if (
(!this._firstSelectedAnnotation || currentPage !== this._firstSelectedAnnotation.pageNumber) && (!this._firstSelectedAnnotation || currentPage !== this._firstSelectedAnnotation.pageNumber) &&
this.displayedPages.indexOf(currentPage) >= 0 && this.displayedPages.indexOf(currentPage) >= 0 &&
this.activeAnnotations.length > 0 activeAnnotations.length > 0
) { ) {
this.listingService.selectAnnotations(this.activeAnnotations[0]); this.listingService.selectAnnotations(activeAnnotations[0]);
} }
} }
@ -479,7 +494,7 @@ export class FileWorkloadComponent extends AutoUnsubscribe implements OnInit, On
#nextPageWithAnnotations() { #nextPageWithAnnotations() {
let idx = 0; let idx = 0;
for (const page of this.displayedPages) { for (const page of this.displayedPages) {
if (page > this.pdf.currentPage() && this.displayedAnnotations.get(page)) { if (page > this.pdf.currentPage() && this.displayedAnnotations().get(page)) {
break; break;
} }
++idx; ++idx;
@ -491,7 +506,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 < this.pdf.currentPage() && this.displayedAnnotations.get(page)) { if (page < this.pdf.currentPage() && this.displayedAnnotations().get(page)) {
break; break;
} }
--idx; --idx;
@ -520,4 +535,16 @@ export class FileWorkloadComponent extends AutoUnsubscribe implements OnInit, On
}); });
return listItemsMap; return listItemsMap;
} }
@HostListener('click', ['$event'])
clickInsideWorkloadView($event: MouseEvent) {
$event?.stopPropagation();
}
@HostListener('document: click')
clickOutsideWorkloadView() {
if (this.multiSelectService.active() && !this._dialog.openDialogs.length) {
this.multiSelectService.deactivate();
}
}
} }

View File

@ -1,3 +1,5 @@
@use 'common-mixins';
.components-header { .components-header {
display: flex; display: flex;
flex-direction: row; flex-direction: row;
@ -26,6 +28,7 @@ mat-icon {
font-size: 12px; font-size: 12px;
overflow: scroll; overflow: scroll;
height: calc(100% - 40px); height: calc(100% - 40px);
@include common-mixins.scroll-bar;
.component-row { .component-row {
display: flex; display: flex;
@ -47,6 +50,7 @@ mat-icon {
&:not(:last-child) { &:not(:last-child) {
border-bottom: 1px solid var(--iqser-separator); border-bottom: 1px solid var(--iqser-separator);
} }
border-bottom: 1px solid var(--iqser-separator); border-bottom: 1px solid var(--iqser-separator);
margin-left: 26px; margin-left: 26px;
margin-right: 26px; margin-right: 26px;

View File

@ -27,7 +27,7 @@ export class ViewSwitchComponent {
}); });
protected readonly canSwitchToRedactedView = computed(() => { protected readonly canSwitchToRedactedView = computed(() => {
const file = this._state.file(); const file = this._state.file();
return !file.analysisRequired && !file.excluded && !file.isError; return !file.excluded && !file.isError;
}); });
protected readonly canSwitchToEarmarksView = computed(() => { protected readonly canSwitchToEarmarksView = computed(() => {
const file = this._state.file(); const file = this._state.file();

View File

@ -3,9 +3,56 @@
<div [translate]="'add-hint.dialog.title'" class="dialog-header heading-l"></div> <div [translate]="'add-hint.dialog.title'" class="dialog-header heading-l"></div>
<div class="dialog-content redaction"> <div class="dialog-content redaction">
<div class="iqser-input-group w-450"> <div class="iqser-input-group w-full selected-text-group">
<label class="selected-text" [translate]="'add-hint.dialog.content.selected-text'"></label> <div
{{ form.get('selectedText').value }} [class.fixed-height-36]="dictionaryRequest"
[ngClass]="isEditingSelectedText ? 'flex relative' : 'flex-align-items-center'"
>
<div class="table">
<label>Value</label>
<div class="row">
<span
*ngIf="!isEditingSelectedText"
[innerHTML]="form.controls.selectedText.value"
[ngStyle]="{
'min-width': textWidth > maximumSelectedTextWidth ? '95%' : 'unset',
'max-width': textWidth > maximumSelectedTextWidth ? 0 : 'unset',
}"
></span>
<textarea
*ngIf="isEditingSelectedText"
[rows]="selectedTextRows"
[ngStyle]="{ width: maximumTextAreaWidth + 'px' }"
formControlName="selectedText"
iqserHasScrollbar
name="value"
type="text"
></textarea>
<iqser-circle-button
(action)="toggleEditingSelectedText()"
*ngIf="dictionaryRequest && !isEditingSelectedText"
[tooltip]="'redact-text.dialog.content.edit-text' | translate"
[size]="20"
[iconSize]="13"
icon="iqser:edit"
tooltipPosition="below"
></iqser-circle-button>
<iqser-circle-button
(action)="undoTextChange(); toggleEditingSelectedText()"
*ngIf="isEditingSelectedText"
[showDot]="initialText !== form.get('selectedText').value"
[tooltip]="'redact-text.dialog.content.revert-text' | translate"
[size]="20"
[iconSize]="13"
class="undo-button"
icon="red:undo"
tooltipPosition="below"
></iqser-circle-button>
</div>
</div>
</div>
</div> </div>
<iqser-details-radio <iqser-details-radio

View File

@ -1,3 +1,67 @@
.dialog-content { .dialog-content {
height: 400px; height: 493px;
overflow-y: auto;
}
.selected-text-group > div {
gap: 0.5rem;
span {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}
iqser-circle-button {
padding-left: 10px;
&.undo-button {
margin-left: 8px;
}
::ng-deep mat-icon {
padding: 2px;
}
}
.w-full {
width: 100%;
}
.fixed-height-36 {
min-height: 36px;
}
textarea[name='value'] {
margin-top: 0;
min-height: 0;
line-height: 1;
}
.table {
display: flex;
flex-direction: column;
min-width: calc(100% - 26px);
padding: 0 13px;
label {
opacity: 0.7;
font-weight: normal;
}
.row {
display: inline-flex;
flex-direction: row;
align-items: center;
background-color: var(--iqser-alt-background);
min-width: 100%;
span {
white-space: nowrap;
text-overflow: ellipsis;
list-style-position: inside;
overflow: hidden;
}
}
} }

View File

@ -1,4 +1,4 @@
import { NgForOf, NgIf } from '@angular/common'; import { NgClass, NgForOf, NgIf, NgStyle } from '@angular/common';
import { Component, OnInit } from '@angular/core'; import { Component, OnInit } from '@angular/core';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { FormBuilder, ReactiveFormsModule, UntypedFormGroup } from '@angular/forms'; import { FormBuilder, ReactiveFormsModule, UntypedFormGroup } from '@angular/forms';
@ -23,12 +23,14 @@ import { ActiveDossiersService } from '@services/dossiers/active-dossiers.servic
import { DictionaryService } from '@services/entity-services/dictionary.service'; import { DictionaryService } from '@services/entity-services/dictionary.service';
import { Roles } from '@users/roles'; import { Roles } from '@users/roles';
import { UserPreferenceService } from '@users/user-preference.service'; import { UserPreferenceService } from '@users/user-preference.service';
import { stringToBoolean } from '@utils/functions'; import { calcTextWidthInPixels, stringToBoolean } from '@utils/functions';
import { tap } from 'rxjs/operators'; import { tap } from 'rxjs/operators';
import { SystemDefaultOption, SystemDefaults } from '../../../account/utils/dialog-defaults'; import { SystemDefaultOption, SystemDefaults } from '../../../account/utils/dialog-defaults';
import { getRedactOrHintOptions } from '../../utils/dialog-options'; import { getRedactOrHintOptions } from '../../utils/dialog-options';
import { AddHintData, AddHintResult, RedactOrHintOption, RedactOrHintOptions } from '../../utils/dialog-types'; import { AddHintData, AddHintResult, RedactOrHintOption, RedactOrHintOptions } from '../../utils/dialog-types';
const MAXIMUM_TEXT_AREA_WIDTH = 421;
@Component({ @Component({
templateUrl: './add-hint-dialog.component.html', templateUrl: './add-hint-dialog.component.html',
styleUrls: ['./add-hint-dialog.component.scss'], styleUrls: ['./add-hint-dialog.component.scss'],
@ -49,6 +51,8 @@ import { AddHintData, AddHintResult, RedactOrHintOption, RedactOrHintOptions } f
CircleButtonComponent, CircleButtonComponent,
MatDialogClose, MatDialogClose,
NgForOf, NgForOf,
NgClass,
NgStyle,
], ],
}) })
export class AddHintDialogComponent extends IqserDialogComponent<AddHintDialogComponent, AddHintData, AddHintResult> implements OnInit { export class AddHintDialogComponent extends IqserDialogComponent<AddHintDialogComponent, AddHintData, AddHintResult> implements OnInit {
@ -58,9 +62,15 @@ export class AddHintDialogComponent extends IqserDialogComponent<AddHintDialogCo
readonly roles = Roles; readonly roles = Roles;
readonly iconButtonTypes = IconButtonTypes; readonly iconButtonTypes = IconButtonTypes;
readonly options: DetailsRadioOption<RedactOrHintOption>[]; readonly options: DetailsRadioOption<RedactOrHintOption>[];
readonly initialText = this.data?.manualRedactionEntryWrapper?.manualRedactionEntry?.value;
readonly maximumTextAreaWidth = MAXIMUM_TEXT_AREA_WIDTH;
readonly maximumSelectedTextWidth = 567;
dictionaryRequest = false; dictionaryRequest = false;
dictionaries: Dictionary[] = []; dictionaries: Dictionary[] = [];
form!: UntypedFormGroup; form!: UntypedFormGroup;
isEditingSelectedText = false;
selectedTextRows = 1;
textWidth: number;
constructor( constructor(
private readonly _activeDossiersService: ActiveDossiersService, private readonly _activeDossiersService: ActiveDossiersService,
@ -83,6 +93,7 @@ export class AddHintDialogComponent extends IqserDialogComponent<AddHintDialogCo
); );
this.form = this.#getForm(); this.form = this.#getForm();
this.textWidth = calcTextWidthInPixels(this.form.controls.selectedText.value);
this.form this.form
.get('option') .get('option')
@ -97,6 +108,18 @@ export class AddHintDialogComponent extends IqserDialogComponent<AddHintDialogCo
.subscribe(); .subscribe();
} }
toggleEditingSelectedText() {
this.isEditingSelectedText = !this.isEditingSelectedText;
if (this.isEditingSelectedText) {
const width = calcTextWidthInPixels(this.form.controls.selectedText.value);
this.selectedTextRows = Math.ceil(width / MAXIMUM_TEXT_AREA_WIDTH);
}
}
undoTextChange() {
this.form.patchValue({ selectedText: this.initialText });
}
get displayedDictionaryLabel() { get displayedDictionaryLabel() {
const dictType = this.form.get('dictionary').value; const dictType = this.form.get('dictionary').value;
if (dictType) { if (dictType) {

View File

@ -160,7 +160,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() {

View File

@ -203,7 +203,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 {

View File

@ -30,7 +30,7 @@
<mat-form-field> <mat-form-field>
<mat-select formControlName="dictionary"> <mat-select formControlName="dictionary">
<mat-select-trigger>{{ displayedDictionaryLabel }}</mat-select-trigger> <mat-select-trigger>{{ displayedDictionaryLabel }}</mat-select-trigger>
<mat-option [value]="entity.type"> <mat-option [value]="entity?.type">
<span> {{ redaction.typeLabel }} </span> <span> {{ redaction.typeLabel }} </span>
</mat-option> </mat-option>
</mat-select> </mat-select>

View File

@ -71,6 +71,7 @@ import { TypeFilterComponent } from '@shared/components/type-filter/type-filter.
import { FileHeaderComponent } from './components/file-header/file-header.component'; import { FileHeaderComponent } from './components/file-header/file-header.component';
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 { ANNOTATION_ACTION_ICONS, ANNOTATION_ACTIONS } from './utils/constants';
@Component({ @Component({
templateUrl: './file-preview-screen.component.html', templateUrl: './file-preview-screen.component.html',
@ -152,6 +153,13 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
this._fileDataService.loadAnnotations(file).then(); this._fileDataService.loadAnnotations(file).then();
}); });
effect(() => {
const file = this.state.file();
if (file.analysisRequired && !file.excludedFromAutomaticAnalysis) {
this._reanalysisService.reanalyzeFilesForDossier([file], file.dossierId, { force: true }).then();
}
});
effect( effect(
() => { () => {
if (this._documentViewer.loaded()) { if (this._documentViewer.loaded()) {
@ -295,11 +303,13 @@ 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();
} }
@ -324,6 +334,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();
@ -350,14 +385,12 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
this.userPreferenceService.saveLastOpenedFileForDossier(this.dossierId, this.fileId).then(); this.userPreferenceService.saveLastOpenedFileForDossier(this.dossierId, this.fileId).then();
this.#subscribeToFileUpdates(); this.#subscribeToFileUpdates();
if (file?.analysisRequired && !file.excludedFromAutomaticAnalysis) {
await this._reanalysisService.reanalyzeFilesForDossier([file], this.dossierId, { force: true });
}
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', this.handleEscInsideViewer);
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);
} }
openManualAnnotationDialog(manualRedactionEntryWrapper: ManualRedactionEntryWrapper) { openManualAnnotationDialog(manualRedactionEntryWrapper: ManualRedactionEntryWrapper) {

View File

@ -1,17 +1,16 @@
import { AnnotationWrapper } from '@models/file/annotation.wrapper'; import { AnnotationWrapper } from '@models/file/annotation.wrapper';
import { Injectable, OnDestroy } from '@angular/core'; import { effect, Injectable, untracked } from '@angular/core';
import { EntitiesService, ListingService, SearchService } from '@iqser/common-ui'; import { EntitiesService, ListingService, SearchService } from '@iqser/common-ui';
import { filter, tap } from 'rxjs/operators';
import { MultiSelectService } from './multi-select.service'; import { MultiSelectService } from './multi-select.service';
import { PdfViewer } from '../../pdf-viewer/services/pdf-viewer.service'; import { PdfViewer } from '../../pdf-viewer/services/pdf-viewer.service';
import { REDAnnotationManager } from '../../pdf-viewer/services/annotation-manager.service'; import { REDAnnotationManager } from '../../pdf-viewer/services/annotation-manager.service';
import { Subscription } from 'rxjs';
import { FilterService } from '@iqser/common-ui/lib/filtering'; import { FilterService } from '@iqser/common-ui/lib/filtering';
import { SortingService } from '@iqser/common-ui/lib/sorting'; import { SortingService } from '@iqser/common-ui/lib/sorting';
import { toSignal } from '@angular/core/rxjs-interop';
@Injectable() @Injectable()
export class AnnotationsListingService extends ListingService<AnnotationWrapper> implements OnDestroy { export class AnnotationsListingService extends ListingService<AnnotationWrapper> {
readonly #subscriptions: Subscription; readonly selectedLength = toSignal(this.selectedLength$);
constructor( constructor(
protected readonly _filterService: FilterService, protected readonly _filterService: FilterService,
@ -24,23 +23,22 @@ export class AnnotationsListingService extends ListingService<AnnotationWrapper>
) { ) {
super(_filterService, _searchService, _entitiesService, _sortingService); super(_filterService, _searchService, _entitiesService, _sortingService);
this.#subscriptions = this.selectedLength$ effect(
.pipe( () => {
filter(length => length > 1), if (this.selectedLength() > 1) {
tap(() => this._multiSelectService.activate()), this._multiSelectService.activate();
) }
.subscribe(); },
} { allowSignalWrites: true },
);
ngOnDestroy() {
this.#subscriptions.unsubscribe();
} }
selectAnnotations(annotations: AnnotationWrapper[] | AnnotationWrapper) { selectAnnotations(annotations: AnnotationWrapper[] | AnnotationWrapper) {
annotations = Array.isArray(annotations) ? annotations : [annotations]; annotations = Array.isArray(annotations) ? annotations : [annotations];
const pageNumber = annotations[annotations.length - 1].pageNumber; const pageNumber = annotations[annotations.length - 1].pageNumber;
const annotationsToSelect = this._multiSelectService.active() ? [...this.selected, ...annotations] : annotations; const multiSelectActive = untracked(this._multiSelectService.active);
const annotationsToSelect = multiSelectActive ? [...this.selected, ...annotations] : annotations;
this.#selectAnnotations(annotationsToSelect, pageNumber); this.#selectAnnotations(annotationsToSelect, pageNumber);
} }
@ -49,16 +47,18 @@ export class AnnotationsListingService extends ListingService<AnnotationWrapper>
return; return;
} }
if (this._multiSelectService.inactive()) { const multiSelectInactive = untracked(this._multiSelectService.inactive);
if (multiSelectInactive) {
this._annotationManager.deselect(); this._annotationManager.deselect();
} }
if (pageNumber === this._pdf.currentPage()) { const currentPage = untracked(this._pdf.currentPage);
if (pageNumber === currentPage) {
return this._annotationManager.jumpAndSelect(annotations); return this._annotationManager.jumpAndSelect(annotations);
} }
this._pdf.navigateTo(pageNumber); this._pdf.navigateTo(pageNumber);
// wait for page to be loaded and to draw annotations // wait for page to be loaded and to draw annotations
setTimeout(() => this._annotationManager.jumpAndSelect(annotations), 300); setTimeout(() => this._annotationManager.jumpAndSelect(annotations), 10);
} }
} }

View File

@ -137,7 +137,7 @@ export class FileDataService extends EntitiesService<AnnotationWrapper, Annotati
this.#logger.info('[REDACTION_LOG] Redaction log loaded', redactionLog); this.#logger.info('[REDACTION_LOG] Redaction log loaded', redactionLog);
let annotations = await this.#convertData(redactionLog); let annotations = await this.#convertData(redactionLog);
this.#checkMissingTypes(); if (!this.#annotations().length && annotations.length !== this.missingTypes.size) this.#checkMissingTypes();
annotations = this.#isIqserDevMode ? annotations : annotations.filter(a => !a.isFalsePositive); annotations = this.#isIqserDevMode ? annotations : annotations.filter(a => !a.isFalsePositive);
this.#annotations.set(annotations); this.#annotations.set(annotations);
} }

View File

@ -121,7 +121,8 @@ 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),
); );
} }

View File

@ -1,4 +1,4 @@
import { computed, Injectable, Signal, signal } from '@angular/core'; import { computed, Injectable, Signal, signal, untracked } from '@angular/core';
import { ViewModeService } from './view-mode.service'; import { ViewModeService } from './view-mode.service';
import { FilePreviewStateService } from './file-preview-state.service'; import { FilePreviewStateService } from './file-preview-state.service';
import { ViewMode, ViewModes } from '@red/domain'; import { ViewMode, ViewModes } from '@red/domain';
@ -13,13 +13,17 @@ export class MultiSelectService {
readonly #active = signal(false); readonly #active = signal(false);
constructor(protected readonly _viewModeService: ViewModeService, protected readonly _state: FilePreviewStateService) { constructor(
protected readonly _viewModeService: ViewModeService,
protected readonly _state: FilePreviewStateService,
) {
this.active = this.#active.asReadonly(); this.active = this.#active.asReadonly();
this.inactive = computed(() => !this.#active()); this.inactive = computed(() => !this.#active());
} }
activate() { activate() {
if (this.enabled()) { const enabled = untracked(this.enabled);
if (enabled) {
this.#active.set(true); this.#active.set(true);
} }
} }

View File

@ -1,4 +1,4 @@
import { computed, effect, inject, Injectable, NgZone } from '@angular/core'; import { computed, effect, inject, Injectable, NgZone, untracked } from '@angular/core';
import { getConfig, IqserPermissionsService } from '@iqser/common-ui'; import { getConfig, IqserPermissionsService } from '@iqser/common-ui';
import { getCurrentUser } from '@iqser/common-ui/lib/users'; import { getCurrentUser } from '@iqser/common-ui/lib/users';
import { isJustOne, shareDistinctLast, UI_ROOT_PATH_FN } from '@iqser/common-ui/lib/utils'; import { isJustOne, shareDistinctLast, UI_ROOT_PATH_FN } from '@iqser/common-ui/lib/utils';
@ -387,10 +387,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();
}); });
@ -402,11 +402,14 @@ export class PdfProxyService {
const annotationChangesAllowed = !this.#isDocumine || !this._state.file().excludedFromAutomaticAnalysis; const annotationChangesAllowed = !this.#isDocumine || !this._state.file().excludedFromAutomaticAnalysis;
const somePending = annotationWrappers.some(a => a.pending); const somePending = annotationWrappers.some(a => a.pending);
const selectedFromWorkload = untracked(this._annotationManager.selectedFromWorkload);
actions = actions =
this._multiSelectService.inactive() && !this._documentViewer.selectedText.length && !somePending (this._multiSelectService.inactive() || !selectedFromWorkload) && !this._documentViewer.selectedText.length && !somePending
? [...actions, ...this._pdfAnnotationActionsService.get(annotationWrappers, annotationChangesAllowed)] ? [...actions, ...this._pdfAnnotationActionsService.get(annotationWrappers, annotationChangesAllowed)]
: []; : [];
this._pdf.instance.UI.annotationPopup.update(actions); this._pdf.instance.UI.annotationPopup.update(actions);
this._annotationManager.resetSelectedFromWorkload();
} }
#getTitle(type: ManualRedactionEntryType) { #getTitle(type: ManualRedactionEntryType) {

View File

@ -55,3 +55,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;

View File

@ -152,6 +152,7 @@ export const getRemoveRedactionOptions = (
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,
@ -161,7 +162,11 @@ export const getRemoveRedactionOptions = (
options.push({ options.push({
label: isBulk ? translations.IN_DOSSIER.labelBulk : translations.IN_DOSSIER.label, label: isBulk ? translations.IN_DOSSIER.labelBulk : translations.IN_DOSSIER.label,
description: isBulk ? translations.IN_DOSSIER.descriptionBulk : translations.IN_DOSSIER.description, description: isBulk ? translations.IN_DOSSIER.descriptionBulk : translations.IN_DOSSIER.description,
descriptionParams: { value: redactions[0].value, type: redactions[0].HINT ? 'hint' : redactions[0].typeLabel }, descriptionParams: {
value: redactions[0].value,
type: redactions[0].HINT ? 'hint' : redactions[0].typeLabel,
isImage: redactions[0].isImage ? 'image' : redactions[0].typeLabel,
},
icon: FOLDER_ICON, icon: FOLDER_ICON,
value: RemoveRedactionOptions.IN_DOSSIER, value: RemoveRedactionOptions.IN_DOSSIER,
extraOption: !isDocumine extraOption: !isDocumine
@ -182,6 +187,7 @@ export const getRemoveRedactionOptions = (
value: redactions[0].value, value: redactions[0].value,
type: redactions[0].typeLabel, type: redactions[0].typeLabel,
context: falsePositiveContext[0], context: falsePositiveContext[0],
isImage: redactions[0].isImage ? 'image' : redactions[0].typeLabel,
}, },
icon: FOLDER_ICON, icon: FOLDER_ICON,
value: RemoveRedactionOptions.DO_NOT_RECOMMEND, value: RemoveRedactionOptions.DO_NOT_RECOMMEND,
@ -201,6 +207,7 @@ export const getRemoveRedactionOptions = (
value: redactions[0].value, value: redactions[0].value,
type: redactions[0].typeLabel, type: redactions[0].typeLabel,
context: falsePositiveContext[0], context: falsePositiveContext[0],
isImage: redactions[0].isImage ? 'image' : redactions[0].typeLabel,
}, },
icon: REMOVE_FROM_DICT_ICON, icon: REMOVE_FROM_DICT_ICON,
value: RemoveRedactionOptions.FALSE_POSITIVE, value: RemoveRedactionOptions.FALSE_POSITIVE,

View File

@ -1,5 +1,5 @@
import { inject, Injectable, signal } from '@angular/core'; import { inject, Injectable, signal } from '@angular/core';
import { bool, List } from '@iqser/common-ui/lib/utils'; import { bool, Debounce, List } from '@iqser/common-ui/lib/utils';
import { AnnotationWrapper } from '@models/file/annotation.wrapper'; import { AnnotationWrapper } from '@models/file/annotation.wrapper';
import { Core } from '@pdftron/webviewer'; import { Core } from '@pdftron/webviewer';
import { getLast, urlFileId } from '@utils/functions'; import { getLast, urlFileId } from '@utils/functions';
@ -20,6 +20,7 @@ const MOVE_OPTION = 'move';
@Injectable() @Injectable()
export class REDAnnotationManager { export class REDAnnotationManager {
readonly #hidden = signal(new Set<string>()); readonly #hidden = signal(new Set<string>());
readonly #selectedFromWorkload = signal(false);
#manager: AnnotationManager; #manager: AnnotationManager;
readonly #logger = inject(NGXLogger); readonly #logger = inject(NGXLogger);
readonly #annotationSelected$ = new Subject<[Annotation[], string]>(); readonly #annotationSelected$ = new Subject<[Annotation[], string]>();
@ -27,6 +28,7 @@ export class REDAnnotationManager {
resizingAnnotationId?: string = undefined; resizingAnnotationId?: string = undefined;
annotationHasBeenResized?: boolean = false; annotationHasBeenResized?: boolean = false;
readonly hidden = this.#hidden.asReadonly(); readonly hidden = this.#hidden.asReadonly();
readonly selectedFromWorkload = this.#selectedFromWorkload.asReadonly();
get selected() { get selected() {
return this.#manager.getSelectedAnnotations(); return this.#manager.getSelectedAnnotations();
@ -161,6 +163,15 @@ export class REDAnnotationManager {
}); });
} }
setSelectedFromWorkload() {
this.#selectedFromWorkload.set(true);
}
@Debounce()
resetSelectedFromWorkload() {
this.#selectedFromWorkload.set(false);
}
#getById(annotation: AnnotationWrapper | string) { #getById(annotation: AnnotationWrapper | string) {
return this.#manager.getAnnotationById(getId(annotation)); return this.#manager.getAnnotationById(getId(annotation));
} }

View File

@ -147,7 +147,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 () => {
@ -158,13 +158,14 @@ 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.#listenForEsc();
this.#clearSearchResultsWhenVisibilityChanged(); this.#clearSearchResultsWhenVisibilityChanged();
this.#listenForSpace();
}); });
return this.#instance; return this.#instance;
@ -287,6 +288,19 @@ export class PdfViewer {
}); });
} }
#listenForSpace() {
this.#instance.UI.hotkeys.on('space', {
keydown: e => {
e.preventDefault();
this.#setSelectionMode('rectangular');
},
keyup: e => {
e.preventDefault();
this.#setSelectionMode('structural');
},
});
}
#getSearchOption(optionId: string): boolean { #getSearchOption(optionId: string): boolean {
const iframeWindow = this.#instance.UI.iframeWindow; const iframeWindow = this.#instance.UI.iframeWindow;
const checkbox = iframeWindow.document.getElementById(optionId) as HTMLInputElement; const checkbox = iframeWindow.document.getElementById(optionId) as HTMLInputElement;
@ -348,9 +362,9 @@ export class PdfViewer {
this.#instance.UI.disableElements(USELESS_ELEMENTS); this.#instance.UI.disableElements(USELESS_ELEMENTS);
} }
#setSelectionMode(): void { #setSelectionMode(mode: 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 = mode;
} }
#getInstance(htmlElement: HTMLElement) { #getInstance(htmlElement: HTMLElement) {

View File

@ -42,12 +42,16 @@ export class EditDossierDownloadPackageComponent
{ {
#existsWatermarks$: Observable<boolean>; #existsWatermarks$: Observable<boolean>;
form: FormGroup; form: FormGroup;
downloadTypes: { key: DownloadFileType; label: string }[] = ['ORIGINAL', 'PREVIEW', 'DELTA_PREVIEW', 'REDACTED'].map( downloadTypes: { key: DownloadFileType; label: string }[] = [
(type: DownloadFileType) => ({ 'ORIGINAL',
key: type, 'PREVIEW',
label: downloadTypesTranslations[type], 'OPTIMIZED_PREVIEW',
}), 'DELTA_PREVIEW',
); 'REDACTED',
].map((type: DownloadFileType) => ({
key: type,
label: downloadTypesTranslations[type],
}));
availableReportTypes: IReportTemplate[] = []; availableReportTypes: IReportTemplate[] = [];
readonly roles = Roles; readonly roles = Roles;
@Input() dossier: Dossier; @Input() dossier: Dossier;

View File

@ -10,6 +10,7 @@ import utc from 'dayjs/plugin/utc';
import localeData from 'dayjs/plugin/localeData'; import localeData from 'dayjs/plugin/localeData';
import localizedFormat from 'dayjs/plugin/localizedFormat'; import localizedFormat from 'dayjs/plugin/localizedFormat';
import customParseFormat from 'dayjs/plugin/customParseFormat'; import customParseFormat from 'dayjs/plugin/customParseFormat';
import isSameOrBefore from 'dayjs/plugin/isSameOrBefore';
export interface DayJsDateAdapterOptions { export interface DayJsDateAdapterOptions {
/** /**
@ -232,6 +233,7 @@ export class CustomDateAdapter extends DateAdapter<Dayjs> {
dayjs.extend(localizedFormat); dayjs.extend(localizedFormat);
dayjs.extend(customParseFormat); dayjs.extend(customParseFormat);
dayjs.extend(localeData); dayjs.extend(localeData);
dayjs.extend(isSameOrBefore);
this.setLocale(dateLocale); this.setLocale(dateLocale);
} }

View File

@ -1,7 +1,5 @@
<ng-container *ngIf="!filter.icon"> <ng-container *ngIf="!filter.icon">
<div *ngIf="filter.id === 'comment'"> <mat-icon *ngIf="filter.id === 'comment'" svgIcon="red:comment"></mat-icon>
<mat-icon svgIcon="red:comment"></mat-icon>
</div>
<redaction-annotation-icon <redaction-annotation-icon
*ngIf="filter.id !== 'comment'" *ngIf="filter.id !== 'comment'"

View File

@ -12,6 +12,6 @@
height: 16px; height: 16px;
margin-right: 8px; margin-right: 8px;
opacity: 50%; opacity: 50%;
line-height: 16px; line-height: 8px;
} }
} }

View File

@ -71,12 +71,16 @@ export class AddDossierDialogComponent extends BaseDialogComponent implements On
readonly roles = Roles; readonly roles = Roles;
readonly iconButtonTypes = IconButtonTypes; readonly iconButtonTypes = IconButtonTypes;
hasDueDate = false; hasDueDate = false;
readonly downloadTypes: { key: DownloadFileType; label: string }[] = ['ORIGINAL', 'PREVIEW', 'DELTA_PREVIEW', 'REDACTED'].map( readonly downloadTypes: { key: DownloadFileType; label: string }[] = [
(type: DownloadFileType) => ({ 'ORIGINAL',
key: type, 'PREVIEW',
label: downloadTypesTranslations[type], 'OPTIMIZED_PREVIEW',
}), 'DELTA_PREVIEW',
); 'REDACTED',
].map((type: DownloadFileType) => ({
key: type,
label: downloadTypesTranslations[type],
}));
dossierTemplates: IDossierTemplate[]; dossierTemplates: IDossierTemplate[];
availableReportTypes: IReportTemplate[] = []; availableReportTypes: IReportTemplate[] = [];
dossierTemplateId: string; dossierTemplateId: string;

View File

@ -90,7 +90,7 @@ export class DownloadDialogComponent extends IqserDialogComponent<DownloadDialog
} }
get #formDownloadTypes() { get #formDownloadTypes() {
return ['ORIGINAL', 'PREVIEW', 'DELTA_PREVIEW', 'REDACTED'] return ['ORIGINAL', 'PREVIEW', 'OPTIMIZED_PREVIEW', 'DELTA_PREVIEW', 'REDACTED']
.map((type: DownloadFileType) => ({ .map((type: DownloadFileType) => ({
key: type, key: type,
label: downloadTypesForDownloadTranslations[type], label: downloadTypesForDownloadTranslations[type],

View File

@ -15,6 +15,7 @@ import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
import { HeadersConfiguration } from '@iqser/common-ui/lib/utils'; import { HeadersConfiguration } from '@iqser/common-ui/lib/utils';
import { LicenseService } from '@services/license.service'; import { LicenseService } from '@services/license.service';
import { LicenseFeatures } from '../../admin/screens/license/utils/constants'; import { LicenseFeatures } from '../../admin/screens/license/utils/constants';
import { UserPreferenceService } from '@users/user-preference.service';
export interface ActiveUpload { export interface ActiveUpload {
subscription: Subscription; subscription: Subscription;
@ -43,6 +44,7 @@ export class FileUploadService extends GenericService<IFileUploadResult> impleme
private readonly _errorMessageService: ErrorMessageService, private readonly _errorMessageService: ErrorMessageService,
private readonly _licenseService: LicenseService, private readonly _licenseService: LicenseService,
private readonly _toaster: Toaster, private readonly _toaster: Toaster,
private readonly _userPreferenceService: UserPreferenceService,
) { ) {
super(); super();
const fileFetch$ = this.#fetchFiles$.pipe( const fileFetch$ = this.#fetchFiles$.pipe(
@ -76,7 +78,7 @@ export class FileUploadService extends GenericService<IFileUploadResult> impleme
const maxSizeBytes = maxSizeMB * 1024 * 1024; const maxSizeBytes = maxSizeMB * 1024 * 1024;
const dossierFiles = this._filesMapService.get(dossierId); const dossierFiles = this._filesMapService.get(dossierId);
const supportMsOfficeFormats = this._licenseService.getFeature(LicenseFeatures.SUPPORT_MS_OFFICE_FORMATS)?.value as boolean; const supportMsOfficeFormats = this._licenseService.getFeature(LicenseFeatures.SUPPORT_MS_OFFICE_FORMATS)?.value as boolean;
let option: OverwriteFileOption = localStorage.getItem('overwriteFileOption') as OverwriteFileOption; let option: OverwriteFileOption | 'undefined' = this._userPreferenceService.getOverwriteFileOption();
for (let idx = 0; idx < files.length; ++idx) { for (let idx = 0; idx < files.length; ++idx) {
const file = files[idx]; const file = files[idx];
let currentOption = option; let currentOption = option;
@ -95,14 +97,14 @@ export class FileUploadService extends GenericService<IFileUploadResult> impleme
} }
} }
} else if (dossierFiles.find(pf => pf.filename === file.file.name)) { } else if (dossierFiles.find(pf => pf.filename === file.file.name)) {
if (!option) { if (option === 'undefined') {
const res = await this._dialogService.openOverwriteFileDialog(file.file.name); const res = await this._dialogService.openOverwriteFileDialog(file.file.name);
if (res.cancel) { if (res.cancel) {
return; return;
} }
if (res.rememberChoice) { if (res.rememberChoice) {
localStorage.setItem('overwriteFileOption', res.option); await this._userPreferenceService.saveOverwriteFileOption(res.option);
} }
currentOption = res.option; currentOption = res.option;

View File

@ -1,58 +1,56 @@
import { GenericService, LAST_CHECKED_OFFSET, QueryParam, ROOT_CHANGES_KEY } from '@iqser/common-ui'; import { GenericService, LAST_CHECKED_OFFSET, ROOT_CHANGES_KEY } from '@iqser/common-ui';
import { Dossier, DossierStats, IDossierChanges } from '@red/domain'; import { Dossier, IChangesDetails } from '@red/domain';
import { forkJoin, Observable, of, Subscription, throwError, timer } from 'rxjs'; import { forkJoin, Observable, Subscription, timer } from 'rxjs';
import { catchError, filter, map, switchMap, take, tap } from 'rxjs/operators'; import { filter, map, switchMap, take, tap } from 'rxjs/operators';
import { HttpErrorResponse, HttpStatusCode } from '@angular/common/http';
import { NGXLogger } from 'ngx-logger'; import { NGXLogger } from 'ngx-logger';
import { ActiveDossiersService } from './active-dossiers.service'; import { ActiveDossiersService } from './active-dossiers.service';
import { ArchivedDossiersService } from './archived-dossiers.service'; import { ArchivedDossiersService } from './archived-dossiers.service';
import { inject, Injectable, OnDestroy } from '@angular/core'; import { inject, Injectable, OnDestroy } from '@angular/core';
import { DashboardStatsService } from '../dossier-templates/dashboard-stats.service'; import { DashboardStatsService } from '../dossier-templates/dashboard-stats.service';
import { CHANGED_CHECK_INTERVAL } from '@utils/constants'; import { CHANGED_CHECK_INTERVAL } from '@utils/constants';
import { List } from '@iqser/common-ui/lib/utils';
import { Router } from '@angular/router'; import { Router } from '@angular/router';
import { filterEventsOnPages } from '@utils/operators'; import { filterEventsOnPages } from '@utils/operators';
import { DossierStatsService } from '@services/dossiers/dossier-stats.service';
@Injectable({ providedIn: 'root' }) @Injectable({ providedIn: 'root' })
export class DossiersChangesService extends GenericService<Dossier> implements OnDestroy { export class DossiersChangesService extends GenericService<Dossier> implements OnDestroy {
protected readonly _defaultModelPath = 'dossier';
readonly #subscription = new Subscription(); readonly #subscription = new Subscription();
readonly #activeDossiersService = inject(ActiveDossiersService); readonly #activeDossiersService = inject(ActiveDossiersService);
readonly #archivedDossiersService = inject(ArchivedDossiersService); readonly #archivedDossiersService = inject(ArchivedDossiersService);
readonly #dashboardStatsService = inject(DashboardStatsService); readonly #dashboardStatsService = inject(DashboardStatsService);
readonly #dossierStatsService = inject(DossierStatsService);
readonly #logger = inject(NGXLogger); readonly #logger = inject(NGXLogger);
readonly #router = inject(Router); readonly #router = inject(Router);
protected readonly _defaultModelPath = 'dossier';
loadOnlyChanged(): Observable<IDossierChanges> { loadOnlyChanged(): Observable<IChangesDetails> {
const removeIfNotFound = (id: string) => const load = (changes: IChangesDetails) => this.#load(changes.dossierChanges.map(d => d.dossierId));
catchError((error: unknown) => {
if (error instanceof HttpErrorResponse && error.status === HttpStatusCode.NotFound) {
this.#activeDossiersService.remove(id);
this.#archivedDossiersService.remove(id);
return of([]);
}
return throwError(() => error);
});
const load = (changes: IDossierChanges) => const loadStats = (change: IChangesDetails) => {
changes.map(change => this.#load(change.dossierId).pipe(removeIfNotFound(change.dossierId))); const dossierStatsToLoad = new Set<string>();
change.dossierChanges.forEach(dossierChange => dossierStatsToLoad.add(dossierChange.dossierId));
change.fileChanges.forEach(fileChange => dossierStatsToLoad.add(fileChange.dossierId));
return this.#dossierStatsService.getFor(Array.from(dossierStatsToLoad));
};
return this.hasChangesDetails$().pipe( return this.hasChangesDetails$().pipe(
tap(changes => this.#logger.info('[DOSSIERS_CHANGES] Found changes', changes)), tap(changes => this.#logger.info('[DOSSIERS_CHANGES] Found changes', changes)),
switchMap(dossierChanges => switchMap(dossierChanges =>
forkJoin([...load(dossierChanges), this.#dashboardStatsService.loadAll().pipe(take(1))]).pipe(map(() => dossierChanges)), forkJoin([load(dossierChanges), loadStats(dossierChanges), this.#dashboardStatsService.loadAll().pipe(take(1))]).pipe(
map(() => dossierChanges),
),
), ),
); );
} }
hasChangesDetails$(): Observable<IDossierChanges> { hasChangesDetails$(): Observable<IChangesDetails> {
const body = { value: this._lastCheckedForChanges.get(ROOT_CHANGES_KEY) }; const body = { value: this._lastCheckedForChanges.get(ROOT_CHANGES_KEY) };
const dateBeforeRequest = new Date(Date.now() - LAST_CHECKED_OFFSET).toISOString(); const dateBeforeRequest = new Date(Date.now() - LAST_CHECKED_OFFSET).toISOString();
this.#logger.info('[DOSSIERS_CHANGES] Check with Last Checked Date', body.value); this.#logger.info('[DOSSIERS_CHANGES] Check with Last Checked Date', body.value);
return this._post<IDossierChanges>(body, `${this._defaultModelPath}/changes/details`).pipe( return this._post<IChangesDetails>(body, `${this._defaultModelPath}/changes/details/v2`).pipe(
filter(changes => changes.length > 0), filter(changes => changes.dossierChanges.length > 0 || changes.fileChanges.length > 0),
tap(() => this._lastCheckedForChanges.set(ROOT_CHANGES_KEY, dateBeforeRequest)), tap(() => this._lastCheckedForChanges.set(ROOT_CHANGES_KEY, dateBeforeRequest)),
tap(() => this.#logger.info('[DOSSIERS_CHANGES] Save Last Checked Date value', dateBeforeRequest)), tap(() => this.#logger.info('[DOSSIERS_CHANGES] Save Last Checked Date value', dateBeforeRequest)),
); );
@ -75,17 +73,27 @@ export class DossiersChangesService extends GenericService<Dossier> implements O
this.#subscription.unsubscribe(); this.#subscription.unsubscribe();
} }
#load(id: string): Observable<DossierStats[]> { getByIds(ids: string[]) {
const queryParams: List<QueryParam> = [{ key: 'includeArchived', value: true }]; return super._post<Record<string, Dossier>>({ value: ids }, `${this._defaultModelPath}/by-id`);
return super._getOne([id], this._defaultModelPath, queryParams).pipe( }
map(entity => new Dossier(entity)),
switchMap((dossier: Dossier) => { #load(ids: string[]): Observable<Dossier[]> {
if (dossier.isArchived) { return this.getByIds(ids).pipe(
this.#activeDossiersService.remove(dossier.id); map(entity => {
return this.#archivedDossiersService.updateDossier(dossier); return Object.values(entity).map(dossier => new Dossier(dossier));
} }),
this.#archivedDossiersService.remove(dossier.id); map((dossiers: Dossier[]) => {
return this.#activeDossiersService.updateDossier(dossier); const archivedDossiers = dossiers.filter(dossier => dossier.isArchived);
const deletedDossiers = dossiers.filter(dossier => dossier.isSoftDeleted);
const activeDossiers = dossiers.filter(dossier => !dossier.isArchived && !dossier.isSoftDeleted);
archivedDossiers.forEach(dossier => this.#activeDossiersService.remove(dossier.id));
activeDossiers.forEach(dossier => this.#archivedDossiersService.remove(dossier.id));
deletedDossiers.forEach(dossier => this.#activeDossiersService.remove(dossier.id));
this.#activeDossiersService.updateDossiers(activeDossiers);
this.#archivedDossiersService.updateDossiers(archivedDossiers);
return dossiers;
}), }),
); );
} }

View File

@ -1,5 +1,5 @@
import { EntitiesService, Toaster } from '@iqser/common-ui'; import { EntitiesService, Toaster } from '@iqser/common-ui';
import { Dossier, DossierStats, IDossier, IDossierChanges, IDossierRequest } from '@red/domain'; import { Dossier, DossierFileChanges, DossierStats, IChangesDetails, IDossier, IDossierRequest } from '@red/domain';
import { Observable, of, Subject } from 'rxjs'; import { Observable, of, Subject } from 'rxjs';
import { catchError, map, switchMap, tap } from 'rxjs/operators'; import { catchError, map, switchMap, tap } from 'rxjs/operators';
import { inject } from '@angular/core'; import { inject } from '@angular/core';
@ -17,7 +17,7 @@ export abstract class DossiersService extends EntitiesService<IDossier, Dossier>
protected readonly _toaster = inject(Toaster); protected readonly _toaster = inject(Toaster);
protected readonly _entityClass = Dossier; protected readonly _entityClass = Dossier;
protected abstract readonly _defaultModelPath: string; protected abstract readonly _defaultModelPath: string;
readonly dossierFileChanges$ = new Subject<string>(); readonly dossierFileChanges$ = new Subject<DossierFileChanges>();
abstract readonly routerPath: string; abstract readonly routerPath: string;
createOrUpdate(dossier: IDossierRequest): Observable<Dossier> { createOrUpdate(dossier: IDossierRequest): Observable<Dossier> {
@ -52,7 +52,18 @@ export abstract class DossiersService extends EntitiesService<IDossier, Dossier>
return this._dossierStatsService.getFor([dossier.id]); return this._dossierStatsService.getFor([dossier.id]);
} }
emitFileChanges(dossierChanges: IDossierChanges): void { updateDossiers(dossier: Dossier[]): void {
dossierChanges.filter(change => change.fileChanges).forEach(change => this.dossierFileChanges$.next(change.dossierId)); dossier.forEach(d => this.replace(d));
}
emitFileChanges(changes: IChangesDetails): void {
const changeModel: DossierFileChanges = {};
changes.fileChanges.forEach(change => {
if (!changeModel[change.dossierId]) {
changeModel[change.dossierId] = [];
}
changeModel[change.dossierId].push(change.fileId);
});
this.dossierFileChanges$.next(changeModel);
} }
} }

View File

@ -157,8 +157,9 @@ export class DictionaryService extends EntitiesService<IDictionary, Dictionary>
.filter(d => d.model['typeId'] && (d.hasDictionary || d.addToDictionaryAction)); .filter(d => d.model['typeId'] && (d.hasDictionary || d.addToDictionaryAction));
} }
getRedactTextDictionaries(dossierId: string, dossierDictionaryOnly: boolean): Dictionary[] { getRedactTextDictionaries(dossierId: string, dossierDictionaryOnly: boolean, dossierTemplateId: string): Dictionary[] {
return this.#extractDossierLevelTypes(dossierId) const types = dossierDictionaryOnly ? this.#extractDossierLevelTypes(dossierId) : this.getDictionariesOptions(dossierTemplateId);
return types
.filter(d => d.model['typeId'] && !d.hint && d.addToDictionaryAction && (dossierDictionaryOnly || !d.dossierDictionaryOnly)) .filter(d => d.model['typeId'] && !d.hint && d.addToDictionaryAction && (dossierDictionaryOnly || !d.dossierDictionaryOnly))
.sort((a, b) => a.label.localeCompare(b.label)); .sort((a, b) => a.label.localeCompare(b.label));
} }

View File

@ -1,7 +1,7 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { EntitiesService, isArray, QueryParam } from '@iqser/common-ui'; import { EntitiesService, isArray, QueryParam } from '@iqser/common-ui';
import { List, mapEach } from '@iqser/common-ui/lib/utils'; import { List, mapEach } from '@iqser/common-ui/lib/utils';
import { File, IFile } from '@red/domain'; import { DossierFileChanges, File, IFile } from '@red/domain';
import { UserService } from '@users/user.service'; import { UserService } from '@users/user.service';
import { NGXLogger } from 'ngx-logger'; import { NGXLogger } from 'ngx-logger';
import { firstValueFrom } from 'rxjs'; import { firstValueFrom } from 'rxjs';
@ -27,8 +27,35 @@ export class FilesService extends EntitiesService<IFile, File> {
super(); super();
} }
loadByIds(dossierFileChanges: DossierFileChanges) {
const filesByDossier$ = super
._post<{ value: Record<string, IFile[]> }>({ value: dossierFileChanges }, `${this._defaultModelPath}/by-id`)
.pipe(
map(response => {
const filesByDossier = response.value;
const result: Record<string, File[]> = {};
for (const key of Object.keys(filesByDossier)) {
result[key] = filesByDossier[key].map(file => new File(file, this._userService.getName(file.assignee)));
result[key].forEach(file => this._logger.info('[FILE] Loaded', file));
}
return result;
}),
);
return filesByDossier$.pipe(
tap(files => {
for (const key of Object.keys(files)) {
const notDeletedFiles = files[key].filter(file => !file.deleted);
const deletedFiles = files[key].filter(file => file.deleted);
this._filesMapService.replace(key, notDeletedFiles);
deletedFiles.map(file => file.id).forEach(id => this._filesMapService.delete(key, id));
}
}),
);
}
/** Reload dossier files + stats. */ /** Reload dossier files + stats. */
loadAll(dossierId: string) { loadAll(dossierId: string) {
console.log('loadAll');
const files$ = this.getFor(dossierId).pipe( const files$ = this.getFor(dossierId).pipe(
mapEach(file => new File(file, this._userService.getName(file.assignee))), mapEach(file => new File(file, this._userService.getName(file.assignee))),
tap(file => this._logger.info('[FILE] Loaded', file)), tap(file => this._logger.info('[FILE] Loaded', file)),

View File

@ -95,7 +95,7 @@ export class NotificationsService extends EntitiesService<INotification, Notific
} }
#getDossierHref(dossier: Dossier): string { #getDossierHref(dossier: Dossier): string {
return dossier ? `${dossier.routerLink}` : null; return dossier ? `${this.#appBaseHref}${dossier.routerLink}` : null;
} }
#getUsername(userId: string | undefined) { #getUsername(userId: string | undefined) {

View File

@ -409,11 +409,7 @@ export class PermissionsService {
} }
#canReanalyseFile(file: File, dossier: Dossier): boolean { #canReanalyseFile(file: File, dossier: Dossier): boolean {
return ( return dossier.isActive && ((this.isAssigneeOrApprover(file, dossier) && file.analysisRequired) || file.isError);
dossier.isActive &&
((this.isAssigneeOrApprover(file, dossier) && file.analysisRequired) ||
(file.isError && (this.isOwner(dossier) || this.isFileAssignee(file))))
);
} }
#canEnableAutoAnalysis(file: File, dossier: Dossier): boolean { #canEnableAutoAnalysis(file: File, dossier: Dossier): boolean {

View File

@ -4,6 +4,7 @@ import { DownloadFileType } from '@red/domain';
export const downloadTypesTranslations: { [key in DownloadFileType]: string } = { export const downloadTypesTranslations: { [key in DownloadFileType]: string } = {
ORIGINAL: _('download-type.original'), ORIGINAL: _('download-type.original'),
PREVIEW: _('download-type.preview'), PREVIEW: _('download-type.preview'),
OPTIMIZED_PREVIEW: _('download-type.optimized-preview'),
REDACTED: _('download-type.redacted'), REDACTED: _('download-type.redacted'),
ANNOTATED: _('download-type.annotated'), ANNOTATED: _('download-type.annotated'),
FLATTEN: _('download-type.flatten'), FLATTEN: _('download-type.flatten'),
@ -13,6 +14,7 @@ export const downloadTypesTranslations: { [key in DownloadFileType]: string } =
export const downloadTypesForDownloadTranslations: { [key in DownloadFileType]: string } = { export const downloadTypesForDownloadTranslations: { [key in DownloadFileType]: string } = {
ORIGINAL: _('download-type.original'), ORIGINAL: _('download-type.original'),
PREVIEW: _('download-type.preview'), PREVIEW: _('download-type.preview'),
OPTIMIZED_PREVIEW: _('download-type.optimized-preview'),
REDACTED: _('download-type.redacted-only'), REDACTED: _('download-type.redacted-only'),
ANNOTATED: _('download-type.annotated'), ANNOTATED: _('download-type.annotated'),
FLATTEN: _('download-type.flatten'), FLATTEN: _('download-type.flatten'),

View File

@ -4,7 +4,6 @@ import { ProcessingFileStatus, WorkflowFileStatus } from '@red/domain';
export const workflowFileStatusTranslations: { [key in WorkflowFileStatus]: string } = { export const workflowFileStatusTranslations: { [key in WorkflowFileStatus]: string } = {
APPROVED: _('file-status.approved'), APPROVED: _('file-status.approved'),
NEW: _('file-status.new'), NEW: _('file-status.new'),
UNASSIGNED: _('file-status.unassigned'),
UNDER_APPROVAL: _('file-status.under-approval'), UNDER_APPROVAL: _('file-status.under-approval'),
UNDER_REVIEW: _('file-status.under-review'), UNDER_REVIEW: _('file-status.under-review'),
}; };

View File

@ -2,6 +2,7 @@ import { Injectable } from '@angular/core';
import { IqserUserPreferenceService, ListingMode } from '@iqser/common-ui'; import { IqserUserPreferenceService, ListingMode } from '@iqser/common-ui';
import { SystemDefaultOption, SystemDefaultType } from '../modules/account/utils/dialog-defaults'; import { SystemDefaultOption, SystemDefaultType } from '../modules/account/utils/dialog-defaults';
import { RedactOrHintOption, RemoveRedactionOption } from '../modules/file-preview/utils/dialog-types'; import { RedactOrHintOption, RemoveRedactionOption } from '../modules/file-preview/utils/dialog-types';
import { OverwriteFileOption } from '@red/domain';
export const PreferencesKeys = { export const PreferencesKeys = {
dossierRecent: 'Dossier-Recent', dossierRecent: 'Dossier-Recent',
@ -22,6 +23,7 @@ export const PreferencesKeys = {
removeRedactionDefaultExtraOption: 'Remove-Redaction-Default-Extra', removeRedactionDefaultExtraOption: 'Remove-Redaction-Default-Extra',
removeRecommendationDefaultExtraOption: 'Remove-Recommendation-Default-Extra', removeRecommendationDefaultExtraOption: 'Remove-Recommendation-Default-Extra',
removeHintDefaultExtraOption: 'Remove-Hint-Default-Extra', removeHintDefaultExtraOption: 'Remove-Hint-Default-Extra',
rememberOverwriteFileOption: 'Remember-Overwrite-File',
} as const; } as const;
@Injectable({ @Injectable({
@ -179,4 +181,12 @@ export class UserPreferenceService extends IqserUserPreferenceService {
async saveRemoveRecommendationDefaultExtraOption(defaultOption: boolean | string): Promise<void> { async saveRemoveRecommendationDefaultExtraOption(defaultOption: boolean | string): Promise<void> {
await this.save(PreferencesKeys.removeRecommendationDefaultExtraOption, defaultOption.toString()); await this.save(PreferencesKeys.removeRecommendationDefaultExtraOption, defaultOption.toString());
} }
getOverwriteFileOption(): OverwriteFileOption | 'undefined' {
return this._getAttribute(PreferencesKeys.rememberOverwriteFileOption, 'undefined') as OverwriteFileOption | 'undefined';
}
async saveOverwriteFileOption(preference: OverwriteFileOption | 'undefined') {
await this.save(PreferencesKeys.rememberOverwriteFileOption, preference);
}
} }

View File

@ -206,11 +206,14 @@
"generic": "Speichern des Benutzers fehlgeschlagen." "generic": "Speichern des Benutzers fehlgeschlagen."
}, },
"form": { "form": {
"account-setup": "User account setup",
"email": "E-Mail", "email": "E-Mail",
"first-name": "Vorname", "first-name": "Vorname",
"last-name": "Nachname", "last-name": "Nachname",
"reset-password": "Passwort zurücksetzen", "reset-password": "Passwort zurücksetzen",
"role": "Rolle" "role": "Rolle",
"send-email": "Do not send email requesting the user to set a password",
"send-email-explanation": "Select this option if you use SSO. Please note that you will need to inform the user directly."
}, },
"title": "{type, select, edit{Benutzer bearbeiten} create{Neuen Benutzer erstellen} other{}}" "title": "{type, select, edit{Benutzer bearbeiten} create{Neuen Benutzer erstellen} other{}}"
}, },
@ -271,9 +274,6 @@
"watermarks": "Wasserzeichen" "watermarks": "Wasserzeichen"
}, },
"analysis-disabled": "", "analysis-disabled": "",
"annotation": {
"pending": "(Analyse steht aus)"
},
"annotation-actions": { "annotation-actions": {
"accept-recommendation": { "accept-recommendation": {
"label": "Empfehlung annehmen" "label": "Empfehlung annehmen"
@ -329,14 +329,14 @@
"error": "Rekategorisierung des Bilds fehlgeschlagen: {error}", "error": "Rekategorisierung des Bilds fehlgeschlagen: {error}",
"success": "Bild wurde einer neuen Kategorie zugeordnet." "success": "Bild wurde einer neuen Kategorie zugeordnet."
}, },
"remove": {
"error": "Entfernen der Schwärzung fehlgeschlagen: {error}",
"success": "Schwärzung wurde entfernt"
},
"remove-hint": { "remove-hint": {
"error": "Entfernen des Hinweises fehlgeschlagen: {error}", "error": "Entfernen des Hinweises fehlgeschlagen: {error}",
"success": "Hinweis wurde entfernt" "success": "Hinweis wurde entfernt"
}, },
"remove": {
"error": "Entfernen der Schwärzung fehlgeschlagen: {error}",
"success": "Schwärzung wurde entfernt"
},
"undo": { "undo": {
"error": "Die Aktion konnte nicht rückgängig gemacht werden. Fehler: {error}", "error": "Die Aktion konnte nicht rückgängig gemacht werden. Fehler: {error}",
"success": "Rücksetzung erfolgreich" "success": "Rücksetzung erfolgreich"
@ -349,15 +349,15 @@
"remove-highlights": { "remove-highlights": {
"label": "Ausgewählte Markierungen entfernen" "label": "Ausgewählte Markierungen entfernen"
}, },
"resize": {
"label": "Größe ändern"
},
"resize-accept": { "resize-accept": {
"label": "Neue Größe speichern" "label": "Neue Größe speichern"
}, },
"resize-cancel": { "resize-cancel": {
"label": "Größenänderung abbrechen" "label": "Größenänderung abbrechen"
}, },
"resize": {
"label": "Größe ändern"
},
"see-references": { "see-references": {
"label": "Referenzen anzeigen" "label": "Referenzen anzeigen"
}, },
@ -391,6 +391,9 @@
"skipped": "Ignorierte Schwärzung", "skipped": "Ignorierte Schwärzung",
"text-highlight": "Markierung" "text-highlight": "Markierung"
}, },
"annotation": {
"pending": "(Analyse steht aus)"
},
"annotations": "Annotationen", "annotations": "Annotationen",
"archived-dossiers-listing": { "archived-dossiers-listing": {
"no-data": { "no-data": {
@ -613,18 +616,14 @@
"warning": "Warnung: Wiederherstellung des Benutzers nicht möglich." "warning": "Warnung: Wiederherstellung des Benutzers nicht möglich."
}, },
"confirmation-dialog": { "confirmation-dialog": {
"approve-file": {
"question": "Dieses Dokument enthält ungesehene Änderungen, die sich durch die Reanalyse ergeben haben. <br><br>Möchten Sie es trotzdem freigeben?",
"title": "Warnung!"
},
"approve-file-without-analysis": { "approve-file-without-analysis": {
"confirmationText": "Ohne Analyse freigeben", "confirmationText": "Ohne Analyse freigeben",
"denyText": "Abbrechen", "denyText": "Abbrechen",
"question": "Analyse zur Erkennung neuer Schwärzungen erforderlich.", "question": "Analyse zur Erkennung neuer Schwärzungen erforderlich.",
"title": "Warnung!" "title": "Warnung!"
}, },
"approve-multiple-files": { "approve-file": {
"question": "Mindestens eine der ausgewählten Dateien enthält ungesehene Änderungen, die im Zuge einer Reanalyse hinzugefügt wurden.<br><br>Möchen Sie die Dateien wirklich freigeben?", "question": "Dieses Dokument enthält ungesehene Änderungen, die sich durch die Reanalyse ergeben haben. <br><br>Möchten Sie es trotzdem freigeben?",
"title": "Warnung!" "title": "Warnung!"
}, },
"approve-multiple-files-without-analysis": { "approve-multiple-files-without-analysis": {
@ -633,6 +632,10 @@
"question": "Für mindestens eine Datei ist ein Analyselauf zur Erkennung neuer Schwärzungen erforderlich.", "question": "Für mindestens eine Datei ist ein Analyselauf zur Erkennung neuer Schwärzungen erforderlich.",
"title": "Warnung" "title": "Warnung"
}, },
"approve-multiple-files": {
"question": "Mindestens eine der ausgewählten Dateien enthält ungesehene Änderungen, die im Zuge einer Reanalyse hinzugefügt wurden.<br><br>Möchen Sie die Dateien wirklich freigeben?",
"title": "Warnung!"
},
"assign-file-to-me": { "assign-file-to-me": {
"question": { "question": {
"multiple": "Dieses Dokument wird gerade von einer anderen Person geprüft.<br><br>Möchten Sie sich die Datei dennoch zuweisen?", "multiple": "Dieses Dokument wird gerade von einer anderen Person geprüft.<br><br>Möchten Sie sich die Datei dennoch zuweisen?",
@ -1002,13 +1005,13 @@
"recent": "Neu ({hours} h)", "recent": "Neu ({hours} h)",
"unassigned": "Keinem Bearbeiter zugewiesen" "unassigned": "Keinem Bearbeiter zugewiesen"
}, },
"reanalyse": {
"action": "Datei analysieren"
},
"reanalyse-dossier": { "reanalyse-dossier": {
"error": "Einplanung der Dateien für die Reanalyse fehlgeschlagen. Bitte versuchen Sie es noch einmal.", "error": "Einplanung der Dateien für die Reanalyse fehlgeschlagen. Bitte versuchen Sie es noch einmal.",
"success": "Dateien für Reanalyse vorgesehen." "success": "Dateien für Reanalyse vorgesehen."
}, },
"reanalyse": {
"action": "Datei analysieren"
},
"start-auto-analysis": "Auto-Analyse aktivieren", "start-auto-analysis": "Auto-Analyse aktivieren",
"stop-auto-analysis": "Auto-Analyse anhalten", "stop-auto-analysis": "Auto-Analyse anhalten",
"table-col-names": { "table-col-names": {
@ -1078,19 +1081,10 @@
"total-documents": "Dokumente", "total-documents": "Dokumente",
"total-people": "<strong>{count}</strong> {count, plural, one{Benutzer} other {Benutzer}}" "total-people": "<strong>{count}</strong> {count, plural, one{Benutzer} other {Benutzer}}"
}, },
"dossier-templates": {
"label": "Dossier-Vorlagen",
"status": {
"active": "Aktiv",
"inactive": "Inaktiv",
"incomplete": "Unvollständig"
}
},
"dossier-templates-listing": { "dossier-templates-listing": {
"action": { "action": {
"clone": "Vorlage klonen", "clone": "Vorlage klonen",
"delete": "Vorlage löschen", "delete": "Vorlage löschen"
"edit": "Vorlage bearbeiten"
}, },
"add-new": "Neue Dossier-Vorlage", "add-new": "Neue Dossier-Vorlage",
"bulk": { "bulk": {
@ -1121,6 +1115,14 @@
"title": "{length} {length, plural, one{Dossier-Vorlage} other{Dossier-Vorlagen}}" "title": "{length} {length, plural, one{Dossier-Vorlage} other{Dossier-Vorlagen}}"
} }
}, },
"dossier-templates": {
"label": "Dossier-Vorlagen",
"status": {
"active": "Aktiv",
"inactive": "Inaktiv",
"incomplete": "Unvollständig"
}
},
"dossier-watermark-selector": { "dossier-watermark-selector": {
"heading": "Wasserzeichen auf Dokumenten", "heading": "Wasserzeichen auf Dokumenten",
"no-watermark": "Kein Wasserzeichen in der Dossier-Vorlage verfügbar:<br>Bitten Sie Ihren Admin, eines zu konfigurieren.", "no-watermark": "Kein Wasserzeichen in der Dossier-Vorlage verfügbar:<br>Bitten Sie Ihren Admin, eines zu konfigurieren.",
@ -1152,6 +1154,7 @@
"delta-preview": "Delta-PDF", "delta-preview": "Delta-PDF",
"flatten": "Verflachte PDF", "flatten": "Verflachte PDF",
"label": "{length} Dokumenten{length, plural, one{typ} other{typen}}", "label": "{length} Dokumenten{length, plural, one{typ} other{typen}}",
"optimized-preview": "Optimized Preview PDF",
"original": "Optimierte PDF", "original": "Optimierte PDF",
"preview": "Vorschau-PDF", "preview": "Vorschau-PDF",
"redacted": "Geschwärzte PDF", "redacted": "Geschwärzte PDF",
@ -1316,15 +1319,6 @@
"title": "{length} {length, plural, one{Wörterbuch} other{Wörterbücher}}" "title": "{length} {length, plural, one{Wörterbuch} other{Wörterbücher}}"
} }
}, },
"entity": {
"info": {
"actions": {
"revert": "Zurücksetzen",
"save": "Änderungen speichern"
},
"heading": "Entität bearbeiten"
}
},
"entity-rules-screen": { "entity-rules-screen": {
"error": { "error": {
"generic": "Fehler: Aktualisierung der Entitätsregeln fehlgeschlagen." "generic": "Fehler: Aktualisierung der Entitätsregeln fehlgeschlagen."
@ -1339,19 +1333,28 @@
"warning-text": "Warnung: experimentelle Funktion!", "warning-text": "Warnung: experimentelle Funktion!",
"warnings-found": "{warnings, plural, one{A warning} other{{warnings} warnings}} in Regeln gefunden" "warnings-found": "{warnings, plural, one{A warning} other{{warnings} warnings}} in Regeln gefunden"
}, },
"entity": {
"info": {
"actions": {
"revert": "Zurücksetzen",
"save": "Änderungen speichern"
},
"heading": "Entität bearbeiten"
}
},
"error": { "error": {
"deleted-entity": { "deleted-entity": {
"dossier": { "dossier": {
"action": "Zurück zur Übersicht", "action": "Zurück zur Übersicht",
"label": "Dieses Dossier wurde gelöscht!" "label": "Dieses Dossier wurde gelöscht!"
}, },
"file": {
"action": "Zurück zum Dossier",
"label": "Diese Datei wurde gelöscht!"
},
"file-dossier": { "file-dossier": {
"action": "Zurück zur Übersicht", "action": "Zurück zur Übersicht",
"label": "Das Dossier dieser Datei wurde gelöscht!" "label": "Das Dossier dieser Datei wurde gelöscht!"
},
"file": {
"action": "Zurück zum Dossier",
"label": "Diese Datei wurde gelöscht!"
} }
}, },
"file-preview": { "file-preview": {
@ -1369,12 +1372,6 @@
}, },
"exact-date": "{day}. {month} {year} um {hour}:{minute} Uhr", "exact-date": "{day}. {month} {year} um {hour}:{minute} Uhr",
"file": "Datei", "file": "Datei",
"file-attribute": {
"update": {
"error": "Aktualisierung des Werts für das Datei-Attribut fehlgeschlagen. Bitte versuchen Sie es noch einmal.",
"success": "Der Wert für das Dateiattribut wurde erfolgreich aktualisiert."
}
},
"file-attribute-encoding-types": { "file-attribute-encoding-types": {
"ascii": "ASCII", "ascii": "ASCII",
"iso": "ISO-8859-1", "iso": "ISO-8859-1",
@ -1385,6 +1382,12 @@
"number": "Nummer", "number": "Nummer",
"text": "Freier Text" "text": "Freier Text"
}, },
"file-attribute": {
"update": {
"error": "Aktualisierung des Werts für das Datei-Attribut fehlgeschlagen. Bitte versuchen Sie es noch einmal.",
"success": "Der Wert für das Dateiattribut wurde erfolgreich aktualisiert."
}
},
"file-attributes-configurations": { "file-attributes-configurations": {
"cancel": "Abbrechen", "cancel": "Abbrechen",
"form": { "form": {
@ -1602,15 +1605,6 @@
"csv": "Die Datei-Attribute wurden erfolgreich aus der hochgeladenen CSV-Datei importiert." "csv": "Die Datei-Attribute wurden erfolgreich aus der hochgeladenen CSV-Datei importiert."
} }
}, },
"filter": {
"analysis": "Analyse erforderlich",
"comment": "Kommentare",
"hint": "Nur Hinweise",
"image": "Bilder",
"none": "Keine Annotationen",
"redaction": "Schwärzungen",
"updated": "Aktualisiert"
},
"filter-menu": { "filter-menu": {
"filter-options": "Filteroptionen", "filter-options": "Filteroptionen",
"filter-types": "Filter", "filter-types": "Filter",
@ -1620,6 +1614,15 @@
"unseen-pages": "Nur Annotationen auf ungesehenen Seiten", "unseen-pages": "Nur Annotationen auf ungesehenen Seiten",
"with-comments": "Nur Annotationen mit Kommentaren" "with-comments": "Nur Annotationen mit Kommentaren"
}, },
"filter": {
"analysis": "Analyse erforderlich",
"comment": "Kommentare",
"hint": "Nur Hinweise",
"image": "Bilder",
"none": "Keine Annotationen",
"redaction": "Schwärzungen",
"updated": "Aktualisiert"
},
"filters": { "filters": {
"assigned-people": "Bearbeiter", "assigned-people": "Bearbeiter",
"documents-status": "Dokumentenstatus", "documents-status": "Dokumentenstatus",
@ -1898,13 +1901,6 @@
"user-promoted-to-approver": "<b>{user}</b> wurde im Dossier <b>{dossierHref, select, null{{dossierName}} other{<a href=\"{dossierHref}\" target=\"_blank\">{dossierName}</a>}}</b> zum Genehmiger ernannt!", "user-promoted-to-approver": "<b>{user}</b> wurde im Dossier <b>{dossierHref, select, null{{dossierName}} other{<a href=\"{dossierHref}\" target=\"_blank\">{dossierName}</a>}}</b> zum Genehmiger ernannt!",
"user-removed-as-dossier-member": "<b>{user}</b> wurde als Mitglied von: <b>{dossierHref, select, null{{dossierName}} other{<a href=\"{dossierHref}\" target=\"_blank\">{dossierName}</a>}}</b> entfernt!" "user-removed-as-dossier-member": "<b>{user}</b> wurde als Mitglied von: <b>{dossierHref, select, null{{dossierName}} other{<a href=\"{dossierHref}\" target=\"_blank\">{dossierName}</a>}}</b> entfernt!"
}, },
"notifications": {
"button-text": "Benachrichtigungen",
"deleted-dossier": "Gelöschtes Dossier",
"label": "Benachrichtigungen",
"mark-all-as-read": "Alle als gelesen markieren",
"mark-as": "Als {type, select, read{gelesen} unread{ungelesen} other{}} markieren"
},
"notifications-screen": { "notifications-screen": {
"category": { "category": {
"email-notifications": "E-Mail-Benachrichtigungen", "email-notifications": "E-Mail-Benachrichtigungen",
@ -1918,6 +1914,7 @@
"dossier": "Benachrichtigungen zu Dossiers", "dossier": "Benachrichtigungen zu Dossiers",
"other": "Andere Benachrichtigungen" "other": "Andere Benachrichtigungen"
}, },
"options-title": "Wählen Sie aus, bei welchen Aktivitäten Sie benachrichtigt werden möchten",
"options": { "options": {
"ASSIGN_APPROVER": "Wenn ich einem Dokument als Genehmiger zugewiesen werde", "ASSIGN_APPROVER": "Wenn ich einem Dokument als Genehmiger zugewiesen werde",
"ASSIGN_REVIEWER": "Wenn ich einem Dokument als Prüfer zugewiesen werde", "ASSIGN_REVIEWER": "Wenn ich einem Dokument als Prüfer zugewiesen werde",
@ -1935,7 +1932,6 @@
"USER_PROMOTED_TO_APPROVER": "Wenn ich Genehmiger in einem Dossier werde", "USER_PROMOTED_TO_APPROVER": "Wenn ich Genehmiger in einem Dossier werde",
"USER_REMOVED_AS_DOSSIER_MEMBER": "Wenn ich die Dossier-Mitgliedschaft verliere" "USER_REMOVED_AS_DOSSIER_MEMBER": "Wenn ich die Dossier-Mitgliedschaft verliere"
}, },
"options-title": "Wählen Sie aus, bei welchen Aktivitäten Sie benachrichtigt werden möchten",
"schedule": { "schedule": {
"daily": "Tägliche Zusammenfassung", "daily": "Tägliche Zusammenfassung",
"instant": "Sofort", "instant": "Sofort",
@ -1943,6 +1939,13 @@
}, },
"title": "Benachrichtigungseinstellungen" "title": "Benachrichtigungseinstellungen"
}, },
"notifications": {
"button-text": "Benachrichtigungen",
"deleted-dossier": "Gelöschtes Dossier",
"label": "Benachrichtigungen",
"mark-all-as-read": "Alle als gelesen markieren",
"mark-as": "Als {type, select, read{gelesen} unread{ungelesen} other{}} markieren"
},
"ocr": { "ocr": {
"confirmation-dialog": { "confirmation-dialog": {
"cancel": "Abbrechen", "cancel": "Abbrechen",
@ -2026,6 +2029,7 @@
"help-mode-dialog": "Dialog zur Aktivierung des Hilfemodus", "help-mode-dialog": "Dialog zur Aktivierung des Hilfemodus",
"load-all-annotations-warning": "Warnung bei gleichzeitigem Laden aller Annotationen in der Miniaturansicht", "load-all-annotations-warning": "Warnung bei gleichzeitigem Laden aller Annotationen in der Miniaturansicht",
"open-structured-view-by-default": "Strukturierte Komponentenansicht standardmäßig anzeigen", "open-structured-view-by-default": "Strukturierte Komponentenansicht standardmäßig anzeigen",
"overwrite-file-option": "",
"table-extraction-type": "Art der Tabellenextraktion" "table-extraction-type": "Art der Tabellenextraktion"
}, },
"label": "Präferenzen", "label": "Präferenzen",
@ -2034,16 +2038,16 @@
"warnings-label": "Dialoge und Meldungen", "warnings-label": "Dialoge und Meldungen",
"warnings-subtitle": "„Nicht mehr anzeigen“-Optionen" "warnings-subtitle": "„Nicht mehr anzeigen“-Optionen"
}, },
"processing": {
"basic": "Verarbeitung läuft",
"ocr": "OCR"
},
"processing-status": { "processing-status": {
"ocr": "OCR", "ocr": "OCR",
"pending": "Ausstehend", "pending": "Ausstehend",
"processed": "verarbeitet", "processed": "verarbeitet",
"processing": "Verarbeitung läuft" "processing": "Verarbeitung läuft"
}, },
"processing": {
"basic": "Verarbeitung läuft",
"ocr": "OCR"
},
"readonly": "Lesemodus", "readonly": "Lesemodus",
"readonly-archived": "Lesemodus (archiviert)", "readonly-archived": "Lesemodus (archiviert)",
"redact-text": { "redact-text": {
@ -2279,12 +2283,6 @@
"red-user-admin": "Benutzeradmin", "red-user-admin": "Benutzeradmin",
"regular": "regulärer Benutzer" "regular": "regulärer Benutzer"
}, },
"search": {
"active-dossiers": "Dokumente in aktiven Dossiers",
"all-dossiers": "Alle Dokumente",
"placeholder": "Dokumente durchsuchen...",
"this-dossier": "In diesem Dossier"
},
"search-screen": { "search-screen": {
"cols": { "cols": {
"assignee": "Bearbeiter", "assignee": "Bearbeiter",
@ -2308,6 +2306,12 @@
"no-match": "Suchbegriff wurde in keinem der Dokumente gefunden.", "no-match": "Suchbegriff wurde in keinem der Dokumente gefunden.",
"table-header": "{length} {length, plural, one{Suchergebnis} other{Suchergebnisse}}" "table-header": "{length} {length, plural, one{Suchergebnis} other{Suchergebnisse}}"
}, },
"search": {
"active-dossiers": "Dokumente in aktiven Dossiers",
"all-dossiers": "Alle Dokumente",
"placeholder": "Dokumente durchsuchen...",
"this-dossier": "In diesem Dossier"
},
"seconds": "Sekunden", "seconds": "Sekunden",
"size": "Größe", "size": "Größe",
"smtp-auth-config": { "smtp-auth-config": {
@ -2563,4 +2567,4 @@
} }
}, },
"yesterday": "Gestern" "yesterday": "Gestern"
} }

View File

@ -206,11 +206,14 @@
"generic": "Failed to save user." "generic": "Failed to save user."
}, },
"form": { "form": {
"account-setup": "User account setup",
"email": "E-mail", "email": "E-mail",
"first-name": "First name", "first-name": "First name",
"last-name": "Last name", "last-name": "Last name",
"reset-password": "Reset password", "reset-password": "Reset password",
"role": "Role" "role": "Role",
"send-email": "Do not send email requesting the user to set a password",
"send-email-explanation": "Select this option if you use SSO. Please note that you will need to inform the user directly."
}, },
"title": "{type, select, edit{Edit} create{Add new} other{}} user" "title": "{type, select, edit{Edit} create{Add new} other{}} user"
}, },
@ -1081,8 +1084,7 @@
"dossier-templates-listing": { "dossier-templates-listing": {
"action": { "action": {
"clone": "Clone template", "clone": "Clone template",
"delete": "Delete template", "delete": "Delete template"
"edit": "Edit template"
}, },
"add-new": "New dossier template", "add-new": "New dossier template",
"bulk": { "bulk": {
@ -1152,6 +1154,7 @@
"delta-preview": "Delta PDF", "delta-preview": "Delta PDF",
"flatten": "Flatten PDF", "flatten": "Flatten PDF",
"label": "{length} document {length, plural, one{version} other{versions}}", "label": "{length} document {length, plural, one{version} other{versions}}",
"optimized-preview": "Optimized Preview PDF",
"original": "Optimized PDF", "original": "Optimized PDF",
"preview": "Preview PDF", "preview": "Preview PDF",
"redacted": "Redacted PDF", "redacted": "Redacted PDF",
@ -1518,7 +1521,7 @@
}, },
"reanalyse-notification": "Start reanalysis", "reanalyse-notification": "Start reanalysis",
"redacted": "Preview", "redacted": "Preview",
"redacted-tooltip": "Redaction preview shows only redactions. Consider this a preview for the final redacted version. This view is only available if the file has no pending changes & doesn't require a reanalysis", "redacted-tooltip": "The preview shows only the redactions. It is a preview of the final redacted version.",
"standard": "Standard", "standard": "Standard",
"standard-tooltip": "Standard shows all annotation types and allows for editing.", "standard-tooltip": "Standard shows all annotation types and allows for editing.",
"tabs": { "tabs": {
@ -2026,6 +2029,7 @@
"help-mode-dialog": "Help mode activation dialog", "help-mode-dialog": "Help mode activation dialog",
"load-all-annotations-warning": "Warning regarding simultaneous loading of all annotations in thumbnails", "load-all-annotations-warning": "Warning regarding simultaneous loading of all annotations in thumbnails",
"open-structured-view-by-default": "Display structured component management modal by default", "open-structured-view-by-default": "Display structured component management modal by default",
"overwrite-file-option": "Preferred action when re-uploading of an already existing file",
"table-extraction-type": "Table extraction type" "table-extraction-type": "Table extraction type"
}, },
"label": "Preferences", "label": "Preferences",
@ -2125,28 +2129,28 @@
"comment-placeholder": "Add remarks or notes...", "comment-placeholder": "Add remarks or notes...",
"options": { "options": {
"do-not-recommend": { "do-not-recommend": {
"description": "Do not recommend the selected term in any document of this dossier.", "description": "Do not recommend the selected {isImage, select, image{image} other{term}} in any document of this dossier.",
"description-bulk": "Do not recommend the selected terms in any document of this dossier.", "description-bulk": "Do not recommend the selected {isImage, select, image{images} other{terms}} in any document of this dossier.",
"extraOptionLabel": "Apply to all active and future dossiers", "extraOptionLabel": "Apply to all active and future dossiers",
"label": "Remove from dossier" "label": "Remove from dossier"
}, },
"false-positive": { "false-positive": {
"description": "Mark this redaction as a false-positive. The term will not be redacted in this dossier if it occurs in the same context.", "description": "Mark this redaction as a false-positive. The {isImage, select, image{image} other{term}} will not be redacted in this dossier if it occurs in the same context.",
"description-bulk": "Mark these redactions as false-positives. The terms will not be redacted in this dossier if they occur in the same context.", "description-bulk": "Mark these redactions as false-positives. The {isImage, select, image{images} other{terms}} will not be redacted in this dossier if they occur in the same context.",
"extraOptionDescription": "Dossier template access is required to reverse this action. As a regular user you can only reverse it for this dossier.", "extraOptionDescription": "Dossier template access is required to reverse this action. As a regular user you can only reverse it for this dossier.",
"extraOptionLabel": "Apply to all active and future dossiers", "extraOptionLabel": "Apply to all active and future dossiers",
"label": "Remove from dossier in this context" "label": "Remove from dossier in this context"
}, },
"in-dossier": { "in-dossier": {
"description": "Do not {type, select, hint{annotate} other{auto-redact}} the selected term in any document of this dossier.", "description": "Do not {type, select, hint{annotate} other{auto-redact}} the selected {isImage, select, image{image} other{term}} in any document of this dossier.",
"description-bulk": "Do not {type, select, hint{annotate} other{auto-redact}} the selected terms in this dossier.", "description-bulk": "Do not {type, select, hint{annotate} other{auto-redact}} the selected {isImage, select, image{images} other{terms}} in this dossier.",
"extraOptionLabel": "Apply to all active and future dossiers", "extraOptionLabel": "Apply to all active and future dossiers",
"label": "Remove from dossier", "label": "Remove from dossier",
"label-bulk": "Remove from dossier" "label-bulk": "Remove from dossier"
}, },
"only-here": { "only-here": {
"description": "Do not {type, select, hint{annotate} other{redact}} the term at this position in the current document.", "description": "Do not {type, select, hint{annotate} other{redact}} the {isImage, select, image{image} other{term}} at this position in the current document.",
"description-bulk": "Do not {type, select, hint{annotate} other{redact}} the selected terms at this position in the current document.", "description-bulk": "Do not {type, select, hint{annotate} other{redact}} the selected {isImage, select, image{images} other{terms}} at this position in the current document.",
"label": "Remove here" "label": "Remove here"
} }
} }
@ -2563,4 +2567,4 @@
} }
}, },
"yesterday": "Yesterday" "yesterday": "Yesterday"
} }

View File

@ -206,11 +206,14 @@
"generic": "Benutzer konnte nicht gespeichert werden!" "generic": "Benutzer konnte nicht gespeichert werden!"
}, },
"form": { "form": {
"account-setup": "User account setup",
"email": "E-Mail", "email": "E-Mail",
"first-name": "Vorname", "first-name": "Vorname",
"last-name": "Nachname", "last-name": "Nachname",
"reset-password": "Passwort zurücksetzen", "reset-password": "Passwort zurücksetzen",
"role": "Rolle" "role": "Rolle",
"send-email": "Do not send email requesting the user to set a password",
"send-email-explanation": "Select this option if you use SSO. Please note that you will need to inform the user directly."
}, },
"title": "{type, select, edit{Benutzer bearbeiten} create{Neuen Benutzer hinzufügen} other{}}" "title": "{type, select, edit{Benutzer bearbeiten} create{Neuen Benutzer hinzufügen} other{}}"
}, },
@ -271,9 +274,6 @@
"watermarks": "Watermarks" "watermarks": "Watermarks"
}, },
"analysis-disabled": "Analysis disabled", "analysis-disabled": "Analysis disabled",
"annotation": {
"pending": "(Pending analysis)"
},
"annotation-actions": { "annotation-actions": {
"accept-recommendation": { "accept-recommendation": {
"label": "Empfehlung annehmen" "label": "Empfehlung annehmen"
@ -329,14 +329,14 @@
"error": "Rekategorisierung des Bildes gescheitert: {error}", "error": "Rekategorisierung des Bildes gescheitert: {error}",
"success": "Bild wurde einer neuen Kategorie zugeordnet." "success": "Bild wurde einer neuen Kategorie zugeordnet."
}, },
"remove": {
"error": "Fehler beim Entfernen der Schwärzung: {error}",
"success": "Schwärzung entfernt!"
},
"remove-hint": { "remove-hint": {
"error": "Failed to remove hint: {error}", "error": "Failed to remove hint: {error}",
"success": "Hint removed!" "success": "Hint removed!"
}, },
"remove": {
"error": "Fehler beim Entfernen der Schwärzung: {error}",
"success": "Schwärzung entfernt!"
},
"undo": { "undo": {
"error": "Die Aktion konnte nicht rückgängig gemacht werden. Fehler: {error}", "error": "Die Aktion konnte nicht rückgängig gemacht werden. Fehler: {error}",
"success": "erfolgreich Rückgängig gemacht" "success": "erfolgreich Rückgängig gemacht"
@ -349,15 +349,15 @@
"remove-highlights": { "remove-highlights": {
"label": "Remove selected earmarks" "label": "Remove selected earmarks"
}, },
"resize": {
"label": "Größe ändern"
},
"resize-accept": { "resize-accept": {
"label": "Größe speichern" "label": "Größe speichern"
}, },
"resize-cancel": { "resize-cancel": {
"label": "Größenänderung abbrechen" "label": "Größenänderung abbrechen"
}, },
"resize": {
"label": "Größe ändern"
},
"see-references": { "see-references": {
"label": "See references" "label": "See references"
}, },
@ -391,6 +391,9 @@
"skipped": "Übersprungen", "skipped": "Übersprungen",
"text-highlight": "Earmark" "text-highlight": "Earmark"
}, },
"annotation": {
"pending": "(Pending analysis)"
},
"annotations": "Annotations", "annotations": "Annotations",
"archived-dossiers-listing": { "archived-dossiers-listing": {
"no-data": { "no-data": {
@ -613,18 +616,14 @@
"warning": "Achtung: Diese Aktion kann nicht rückgängig gemacht werden!" "warning": "Achtung: Diese Aktion kann nicht rückgängig gemacht werden!"
}, },
"confirmation-dialog": { "confirmation-dialog": {
"approve-file": {
"question": "Dieses Dokument enthält ungesehene Änderungen. Möchten Sie es trotzdem genehmigen?",
"title": "Warnung!"
},
"approve-file-without-analysis": { "approve-file-without-analysis": {
"confirmationText": "Approve without analysis", "confirmationText": "Approve without analysis",
"denyText": "Cancel", "denyText": "Cancel",
"question": "Analysis required to detect new components.", "question": "Analysis required to detect new components.",
"title": "Warning!" "title": "Warning!"
}, },
"approve-multiple-files": { "approve-file": {
"question": "Mindestens eine der ausgewählten Dateien enthält ungesehene Änderungen. Möchten Sie sie trotzdem genehmigen?", "question": "Dieses Dokument enthält ungesehene Änderungen. Möchten Sie es trotzdem genehmigen?",
"title": "Warnung!" "title": "Warnung!"
}, },
"approve-multiple-files-without-analysis": { "approve-multiple-files-without-analysis": {
@ -633,6 +632,10 @@
"question": "Analysis required to detect new components for at least one file.", "question": "Analysis required to detect new components for at least one file.",
"title": "Warning" "title": "Warning"
}, },
"approve-multiple-files": {
"question": "Mindestens eine der ausgewählten Dateien enthält ungesehene Änderungen. Möchten Sie sie trotzdem genehmigen?",
"title": "Warnung!"
},
"assign-file-to-me": { "assign-file-to-me": {
"question": { "question": {
"multiple": "Dieses Dokument wird gerade von einer anderen Person geprüft. Möchten Sie Reviewer werden und sich selbst dem Dokument zuweisen?", "multiple": "Dieses Dokument wird gerade von einer anderen Person geprüft. Möchten Sie Reviewer werden und sich selbst dem Dokument zuweisen?",
@ -1002,13 +1005,13 @@
"recent": "Neu ({hours} h)", "recent": "Neu ({hours} h)",
"unassigned": "Niemandem zugewiesen" "unassigned": "Niemandem zugewiesen"
}, },
"reanalyse": {
"action": "Datei analysieren"
},
"reanalyse-dossier": { "reanalyse-dossier": {
"error": "Die Dateien konnten nicht für eine Reanalyse eingeplant werden. Bitte versuchen Sie es erneut.", "error": "Die Dateien konnten nicht für eine Reanalyse eingeplant werden. Bitte versuchen Sie es erneut.",
"success": "Dateien für Reanalyse vorgesehen." "success": "Dateien für Reanalyse vorgesehen."
}, },
"reanalyse": {
"action": "Datei analysieren"
},
"start-auto-analysis": "Enable auto-analysis", "start-auto-analysis": "Enable auto-analysis",
"stop-auto-analysis": "Stop auto-analysis", "stop-auto-analysis": "Stop auto-analysis",
"table-col-names": { "table-col-names": {
@ -1078,19 +1081,10 @@
"total-documents": "Anzahl der Dokumente", "total-documents": "Anzahl der Dokumente",
"total-people": "<strong>{count}</strong> {count, plural, one{user} other {users}}" "total-people": "<strong>{count}</strong> {count, plural, one{user} other {users}}"
}, },
"dossier-templates": {
"label": "Dossier-Vorlagen",
"status": {
"active": "Active",
"inactive": "Inactive",
"incomplete": "Incomplete"
}
},
"dossier-templates-listing": { "dossier-templates-listing": {
"action": { "action": {
"clone": "Clone template", "clone": "Clone template",
"delete": "Dossier-Vorlage", "delete": "Dossier-Vorlage"
"edit": "Vorlage bearbeiten"
}, },
"add-new": "Neue Dossier-Vorlage", "add-new": "Neue Dossier-Vorlage",
"bulk": { "bulk": {
@ -1121,6 +1115,14 @@
"title": "{length} dossier {length, plural, one{template} other{templates}}" "title": "{length} dossier {length, plural, one{template} other{templates}}"
} }
}, },
"dossier-templates": {
"label": "Dossier-Vorlagen",
"status": {
"active": "Active",
"inactive": "Inactive",
"incomplete": "Incomplete"
}
},
"dossier-watermark-selector": { "dossier-watermark-selector": {
"heading": "Watermarks on documents", "heading": "Watermarks on documents",
"no-watermark": "There is no watermark defined for the dossier template.<br>Contact your app admin to define one.", "no-watermark": "There is no watermark defined for the dossier template.<br>Contact your app admin to define one.",
@ -1152,6 +1154,7 @@
"delta-preview": "Delta PDF", "delta-preview": "Delta PDF",
"flatten": "PDF verflachen", "flatten": "PDF verflachen",
"label": "{length} document {length, plural, one{version} other{versions}}", "label": "{length} document {length, plural, one{version} other{versions}}",
"optimized-preview": "Optimized Preview PDF",
"original": "Optimiertes PDF", "original": "Optimiertes PDF",
"preview": "PDF-Vorschau", "preview": "PDF-Vorschau",
"redacted": "geschwärztes PDF", "redacted": "geschwärztes PDF",
@ -1316,15 +1319,6 @@
"title": "{length} {length, plural, one{entity} other{entities}}" "title": "{length} {length, plural, one{entity} other{entities}}"
} }
}, },
"entity": {
"info": {
"actions": {
"revert": "Revert",
"save": "Save changes"
},
"heading": "Edit entity"
}
},
"entity-rules-screen": { "entity-rules-screen": {
"error": { "error": {
"generic": "Something went wrong... Entity rules update failed!" "generic": "Something went wrong... Entity rules update failed!"
@ -1339,19 +1333,28 @@
"warning-text": "Warning: experimental feature!", "warning-text": "Warning: experimental feature!",
"warnings-found": "{warnings, plural, one{A warning} other{{warnings} warnings}} found in rules" "warnings-found": "{warnings, plural, one{A warning} other{{warnings} warnings}} found in rules"
}, },
"entity": {
"info": {
"actions": {
"revert": "Revert",
"save": "Save changes"
},
"heading": "Edit entity"
}
},
"error": { "error": {
"deleted-entity": { "deleted-entity": {
"dossier": { "dossier": {
"action": "Zurück zur Übersicht", "action": "Zurück zur Übersicht",
"label": "Dieses Dossier wurde gelöscht!" "label": "Dieses Dossier wurde gelöscht!"
}, },
"file": {
"action": "Zurück zum Dossier",
"label": "Diese Datei wurde gelöscht!"
},
"file-dossier": { "file-dossier": {
"action": "Zurück zur Übersicht", "action": "Zurück zur Übersicht",
"label": "Das Dossier dieser Datei wurde gelöscht!" "label": "Das Dossier dieser Datei wurde gelöscht!"
},
"file": {
"action": "Zurück zum Dossier",
"label": "Diese Datei wurde gelöscht!"
} }
}, },
"file-preview": { "file-preview": {
@ -1369,12 +1372,6 @@
}, },
"exact-date": "{day} {month} {year} um {hour}:{minute} Uhr", "exact-date": "{day} {month} {year} um {hour}:{minute} Uhr",
"file": "Datei", "file": "Datei",
"file-attribute": {
"update": {
"error": "Failed to update file attribute value!",
"success": "File attribute value has been updated successfully!"
}
},
"file-attribute-encoding-types": { "file-attribute-encoding-types": {
"ascii": "ASCII", "ascii": "ASCII",
"iso": "ISO-8859-1", "iso": "ISO-8859-1",
@ -1385,6 +1382,12 @@
"number": "Nummer", "number": "Nummer",
"text": "Freier Text" "text": "Freier Text"
}, },
"file-attribute": {
"update": {
"error": "Failed to update file attribute value!",
"success": "File attribute value has been updated successfully!"
}
},
"file-attributes-configurations": { "file-attributes-configurations": {
"cancel": "Cancel", "cancel": "Cancel",
"form": { "form": {
@ -1602,15 +1605,6 @@
"csv": "File attributes were imported successfully from uploaded CSV file." "csv": "File attributes were imported successfully from uploaded CSV file."
} }
}, },
"filter": {
"analysis": "Analyse erforderlich",
"comment": "Kommentare",
"hint": "Nut Hinweise",
"image": "Bilder",
"none": "Keine Anmerkungen",
"redaction": "Geschwärzt",
"updated": "Aktualisiert"
},
"filter-menu": { "filter-menu": {
"filter-options": "Filteroptionen", "filter-options": "Filteroptionen",
"filter-types": "Filter", "filter-types": "Filter",
@ -1620,6 +1614,15 @@
"unseen-pages": "Nur Anmerkungen auf unsichtbaren Seiten", "unseen-pages": "Nur Anmerkungen auf unsichtbaren Seiten",
"with-comments": "Nur Anmerkungen mit Kommentaren" "with-comments": "Nur Anmerkungen mit Kommentaren"
}, },
"filter": {
"analysis": "Analyse erforderlich",
"comment": "Kommentare",
"hint": "Nut Hinweise",
"image": "Bilder",
"none": "Keine Anmerkungen",
"redaction": "Geschwärzt",
"updated": "Aktualisiert"
},
"filters": { "filters": {
"assigned-people": "Beauftragt", "assigned-people": "Beauftragt",
"documents-status": "Documents state", "documents-status": "Documents state",
@ -1898,13 +1901,6 @@
"user-promoted-to-approver": "<b>{user}</b> wurde im Dossier <b>{dossierHref, select, null{{dossierName}} other{<a href=\"{dossierHref}\" target=\"_blank\">{dossierName}</a>}}</b> zum Genehmiger ernannt!", "user-promoted-to-approver": "<b>{user}</b> wurde im Dossier <b>{dossierHref, select, null{{dossierName}} other{<a href=\"{dossierHref}\" target=\"_blank\">{dossierName}</a>}}</b> zum Genehmiger ernannt!",
"user-removed-as-dossier-member": "<b>{user}</b> wurde als Mitglied von: <b>{dossierHref, select, null{{dossierName}} other{<a href=\"{dossierHref}\" target=\"_blank\">{dossierName}</a>}}</b> entfernt!" "user-removed-as-dossier-member": "<b>{user}</b> wurde als Mitglied von: <b>{dossierHref, select, null{{dossierName}} other{<a href=\"{dossierHref}\" target=\"_blank\">{dossierName}</a>}}</b> entfernt!"
}, },
"notifications": {
"button-text": "Notifications",
"deleted-dossier": "Deleted dossier",
"label": "Benachrichtigungen",
"mark-all-as-read": "Alle als gelesen markieren",
"mark-as": "Mark as {type, select, read{read} unread{unread} other{}}"
},
"notifications-screen": { "notifications-screen": {
"category": { "category": {
"email-notifications": "E-Mail Benachrichtigungen", "email-notifications": "E-Mail Benachrichtigungen",
@ -1918,6 +1914,7 @@
"dossier": "Dossierbezogene Benachrichtigungen", "dossier": "Dossierbezogene Benachrichtigungen",
"other": "Andere Benachrichtigungen" "other": "Andere Benachrichtigungen"
}, },
"options-title": "Wählen Sie aus, in welcher Kategorie Sie benachrichtigt werden möchten",
"options": { "options": {
"ASSIGN_APPROVER": "Wenn ich einem Dokument als Genehmiger zugewiesen bin", "ASSIGN_APPROVER": "Wenn ich einem Dokument als Genehmiger zugewiesen bin",
"ASSIGN_REVIEWER": "Wenn ich einem Dokument als Überprüfer zugewiesen bin", "ASSIGN_REVIEWER": "Wenn ich einem Dokument als Überprüfer zugewiesen bin",
@ -1935,7 +1932,6 @@
"USER_PROMOTED_TO_APPROVER": "Wenn ich Genehmiger in einem Dossier werde", "USER_PROMOTED_TO_APPROVER": "Wenn ich Genehmiger in einem Dossier werde",
"USER_REMOVED_AS_DOSSIER_MEMBER": "Wenn ich die Dossier-Mitgliedschaft verliere" "USER_REMOVED_AS_DOSSIER_MEMBER": "Wenn ich die Dossier-Mitgliedschaft verliere"
}, },
"options-title": "Wählen Sie aus, in welcher Kategorie Sie benachrichtigt werden möchten",
"schedule": { "schedule": {
"daily": "Tägliche Zusammenfassung", "daily": "Tägliche Zusammenfassung",
"instant": "Sofortig", "instant": "Sofortig",
@ -1943,6 +1939,13 @@
}, },
"title": "Benachrichtigungseinstellungen" "title": "Benachrichtigungseinstellungen"
}, },
"notifications": {
"button-text": "Notifications",
"deleted-dossier": "Deleted dossier",
"label": "Benachrichtigungen",
"mark-all-as-read": "Alle als gelesen markieren",
"mark-as": "Mark as {type, select, read{read} unread{unread} other{}}"
},
"ocr": { "ocr": {
"confirmation-dialog": { "confirmation-dialog": {
"cancel": "Cancel", "cancel": "Cancel",
@ -2026,6 +2029,7 @@
"help-mode-dialog": "Help Mode Dialog", "help-mode-dialog": "Help Mode Dialog",
"load-all-annotations-warning": "Warning regarding loading all annotations at once in file preview", "load-all-annotations-warning": "Warning regarding loading all annotations at once in file preview",
"open-structured-view-by-default": "Display Component View by default when opening a document", "open-structured-view-by-default": "Display Component View by default when opening a document",
"overwrite-file-option": "",
"table-extraction-type": "Table extraction type" "table-extraction-type": "Table extraction type"
}, },
"label": "Preferences", "label": "Preferences",
@ -2034,16 +2038,16 @@
"warnings-label": "Prompts and dialogs", "warnings-label": "Prompts and dialogs",
"warnings-subtitle": "Do not show again options" "warnings-subtitle": "Do not show again options"
}, },
"processing": {
"basic": "Processing",
"ocr": "OCR"
},
"processing-status": { "processing-status": {
"ocr": "OCR", "ocr": "OCR",
"pending": "Pending", "pending": "Pending",
"processed": "Processed", "processed": "Processed",
"processing": "Processing" "processing": "Processing"
}, },
"processing": {
"basic": "Processing",
"ocr": "OCR"
},
"readonly": "Lesemodus", "readonly": "Lesemodus",
"readonly-archived": "Read only (archived)", "readonly-archived": "Read only (archived)",
"redact-text": { "redact-text": {
@ -2279,12 +2283,6 @@
"red-user-admin": "Benutzer-Admin", "red-user-admin": "Benutzer-Admin",
"regular": "Regulär" "regular": "Regulär"
}, },
"search": {
"active-dossiers": "ganze Plattform",
"all-dossiers": "all documents",
"placeholder": "Nach Dokumenten oder Dokumenteninhalt suchen",
"this-dossier": "in diesem Dossier"
},
"search-screen": { "search-screen": {
"cols": { "cols": {
"assignee": "Bevollmächtigter", "assignee": "Bevollmächtigter",
@ -2308,6 +2306,12 @@
"no-match": "Keine Dokumente entsprechen Ihren aktuellen Filtern.", "no-match": "Keine Dokumente entsprechen Ihren aktuellen Filtern.",
"table-header": "{length} search {length, plural, one{result} other{results}}" "table-header": "{length} search {length, plural, one{result} other{results}}"
}, },
"search": {
"active-dossiers": "ganze Plattform",
"all-dossiers": "all documents",
"placeholder": "Nach Dokumenten oder Dokumenteninhalt suchen",
"this-dossier": "in diesem Dossier"
},
"seconds": "seconds", "seconds": "seconds",
"size": "Size", "size": "Size",
"smtp-auth-config": { "smtp-auth-config": {
@ -2563,4 +2567,4 @@
} }
}, },
"yesterday": "Gestern" "yesterday": "Gestern"
} }

View File

@ -206,11 +206,14 @@
"generic": "Failed to save user!" "generic": "Failed to save user!"
}, },
"form": { "form": {
"account-setup": "User account setup",
"email": "E-mail", "email": "E-mail",
"first-name": "First name", "first-name": "First name",
"last-name": "Last name", "last-name": "Last name",
"reset-password": "Reset password", "reset-password": "Reset password",
"role": "Role" "role": "Role",
"send-email": "Do not send email requesting the user to set a password",
"send-email-explanation": "Select this option if you use SSO. Please note that you will need to inform the user directly."
}, },
"title": "{type, select, edit{Edit} create{Add New} other{}} user" "title": "{type, select, edit{Edit} create{Add New} other{}} user"
}, },
@ -1081,8 +1084,7 @@
"dossier-templates-listing": { "dossier-templates-listing": {
"action": { "action": {
"clone": "Clone template", "clone": "Clone template",
"delete": "Delete template", "delete": "Delete template"
"edit": "Edit template"
}, },
"add-new": "New dossier template", "add-new": "New dossier template",
"bulk": { "bulk": {
@ -1152,6 +1154,7 @@
"delta-preview": "Delta PDF", "delta-preview": "Delta PDF",
"flatten": "Flatten PDF", "flatten": "Flatten PDF",
"label": "{length} document {length, plural, one{version} other{versions}}", "label": "{length} document {length, plural, one{version} other{versions}}",
"optimized-preview": "Optimized Preview PDF",
"original": "Optimized PDF", "original": "Optimized PDF",
"preview": "Preview PDF", "preview": "Preview PDF",
"redacted": "Redacted PDF", "redacted": "Redacted PDF",
@ -2026,6 +2029,7 @@
"help-mode-dialog": "Help Mode Dialog", "help-mode-dialog": "Help Mode Dialog",
"load-all-annotations-warning": "Warning regarding loading all annotations at once in file preview", "load-all-annotations-warning": "Warning regarding loading all annotations at once in file preview",
"open-structured-view-by-default": "Display Component View by default when opening a document", "open-structured-view-by-default": "Display Component View by default when opening a document",
"overwrite-file-option": "Preferred action when re-uploading of an already existing file",
"table-extraction-type": "Table extraction type" "table-extraction-type": "Table extraction type"
}, },
"label": "Preferences", "label": "Preferences",
@ -2563,4 +2567,4 @@
} }
}, },
"yesterday": "Yesterday" "yesterday": "Yesterday"
} }

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,205 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.-------------------------------------------------------------------
------------[ Log Start: 2019-Oct-10 19:32:29.143159]--------------
System: (Windows, AMD64), PDFNetVersion: 7.1.0.71627
-------------------------------------------------------------------
[ INFO 19:32:29.143159, fontapp, WebFontCreator.cpp(679)]: logging enabled

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Some files were not shown because too many files have changed in this diff Show More