Merge branch 'master' into VM/RED-6801
This commit is contained in:
commit
7a729ae2a3
@ -22,6 +22,7 @@ import { CustomRouteReuseStrategy } from '@iqser/common-ui/lib/utils';
|
||||
import { ifLoggedIn } from '@guards/if-logged-in.guard';
|
||||
import { ifNotLoggedIn } from '@guards/if-not-logged-in.guard';
|
||||
import { TenantSelectComponent } from '@iqser/common-ui/lib/tenants';
|
||||
import { editAttributeGuard } from '@guards/edit-attribute.guard';
|
||||
|
||||
const dossierTemplateIdRoutes: IqserRoutes = [
|
||||
{
|
||||
@ -37,6 +38,7 @@ const dossierTemplateIdRoutes: IqserRoutes = [
|
||||
{
|
||||
path: `:${DOSSIER_ID}`,
|
||||
canActivate: [CompositeRouteGuard, IqserPermissionsGuard],
|
||||
canDeactivate: [editAttributeGuard],
|
||||
data: {
|
||||
routeGuards: [DossierFilesGuard],
|
||||
breadcrumbs: [BreadcrumbTypes.dossierTemplate, BreadcrumbTypes.dossier],
|
||||
@ -115,7 +117,6 @@ const mainRoutes: IqserRoutes = [
|
||||
Roles.any,
|
||||
Roles.templates.read,
|
||||
Roles.fileAttributes.readConfig,
|
||||
Roles.watermarks.read,
|
||||
Roles.dictionaryTypes.read,
|
||||
Roles.colors.read,
|
||||
Roles.states.read,
|
||||
@ -179,7 +180,6 @@ const mainRoutes: IqserRoutes = [
|
||||
Roles.any,
|
||||
Roles.templates.read,
|
||||
Roles.fileAttributes.readConfig,
|
||||
Roles.watermarks.read,
|
||||
Roles.dictionaryTypes.read,
|
||||
Roles.colors.read,
|
||||
Roles.states.read,
|
||||
|
||||
@ -1,13 +1,10 @@
|
||||
import { Component, inject, Renderer2, ViewContainerRef } from '@angular/core';
|
||||
import { Component, Renderer2, ViewContainerRef } from '@angular/core';
|
||||
import { RouterHistoryService } from '@services/router-history.service';
|
||||
import { DOCUMENT } from '@angular/common';
|
||||
import { UserPreferenceService } from '@users/user-preference.service';
|
||||
import { getConfig, IqserPermissionsService } from '@iqser/common-ui';
|
||||
import { getConfig } from '@iqser/common-ui';
|
||||
import { AppConfig } from '@red/domain';
|
||||
import { NavigationEnd, Router } from '@angular/router';
|
||||
import { filter, map, switchMap, take, tap } from 'rxjs/operators';
|
||||
import { Roles } from '@users/roles';
|
||||
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
||||
import { filter, map, switchMap, take } from 'rxjs/operators';
|
||||
|
||||
function loadCustomTheme() {
|
||||
const cssFileName = getConfig<AppConfig>().THEME;
|
||||
@ -37,9 +34,8 @@ export class AppComponent {
|
||||
userPreferenceService: UserPreferenceService,
|
||||
renderer: Renderer2,
|
||||
private readonly _router: Router,
|
||||
iqserPermissionsService: IqserPermissionsService,
|
||||
) {
|
||||
renderer.addClass(inject(DOCUMENT).body, userPreferenceService.getTheme());
|
||||
renderer.addClass(document.body, userPreferenceService.getTheme());
|
||||
loadCustomTheme();
|
||||
|
||||
const removeQueryParams = _router.events.pipe(
|
||||
@ -51,13 +47,9 @@ export class AppComponent {
|
||||
);
|
||||
removeQueryParams.subscribe();
|
||||
|
||||
const changeFavicon = iqserPermissionsService.has$(Roles.getRss).pipe(
|
||||
tap(hasRss => {
|
||||
const faviconUrl = hasRss ? 'assets/icons/documine-logo.ico' : 'favicon.ico';
|
||||
document.getElementById('favicon').setAttribute('href', faviconUrl);
|
||||
}),
|
||||
);
|
||||
changeFavicon.pipe(takeUntilDestroyed()).subscribe();
|
||||
if (getConfig().IS_DOCUMINE) {
|
||||
document.getElementById('favicon').setAttribute('href', 'assets/icons/documine-logo.ico');
|
||||
}
|
||||
}
|
||||
|
||||
#removeKeycloakQueryParams() {
|
||||
|
||||
@ -131,11 +131,17 @@ export const appModuleFactory = (config: AppConfig) => {
|
||||
features: {
|
||||
ANNOTATIONS: {
|
||||
color: 'aqua',
|
||||
enabled: true,
|
||||
enabled: false,
|
||||
level: NgxLoggerLevel.DEBUG,
|
||||
},
|
||||
FILTERS: {
|
||||
enabled: true,
|
||||
enabled: false,
|
||||
},
|
||||
TENANTS: {
|
||||
enabled: false,
|
||||
},
|
||||
ROUTES: {
|
||||
enabled: false,
|
||||
},
|
||||
PDF: {
|
||||
enabled: true,
|
||||
|
||||
@ -11,7 +11,10 @@
|
||||
|
||||
<a [matTooltip]="'top-bar.navigation-items.back-to-dashboard' | translate" [routerLink]="['/'] | tenant" class="logo">
|
||||
<div [attr.help-mode-key]="'home'" class="actions">
|
||||
<iqser-logo (iqserHiddenAction)="userPreferenceService.toggleDevFeatures()" icon="red:logo"></iqser-logo>
|
||||
<iqser-logo
|
||||
(iqserHiddenAction)="userPreferenceService.toggleDevFeatures()"
|
||||
[icon]="config.IS_DOCUMINE ? 'red:documine-logo' : 'red:logo'"
|
||||
></iqser-logo>
|
||||
<div class="app-name">{{ titleService.getTitle() }}</div>
|
||||
</div>
|
||||
</a>
|
||||
|
||||
@ -6,7 +6,7 @@ import { Title } from '@angular/platform-browser';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { SpotlightSearchAction } from '@components/spotlight-search/spotlight-search-action';
|
||||
import { filter, map, startWith } from 'rxjs/operators';
|
||||
import { IqserPermissionsService } from '@iqser/common-ui';
|
||||
import { getConfig, IqserPermissionsService } from '@iqser/common-ui';
|
||||
import { BreadcrumbsService } from '@services/breadcrumbs.service';
|
||||
import { FeaturesService } from '@services/features.service';
|
||||
import { ARCHIVE_ROUTE, DOSSIERS_ARCHIVE, DOSSIERS_ROUTE } from '@red/domain';
|
||||
@ -33,6 +33,7 @@ export class BaseScreenComponent {
|
||||
readonly roles = Roles;
|
||||
readonly documentViewer = inject(REDDocumentViewer);
|
||||
readonly currentUser = this.userService.currentUser;
|
||||
readonly config = getConfig();
|
||||
readonly searchActions: List<SpotlightSearchAction> = [
|
||||
{
|
||||
text: this._translateService.instant('search.this-dossier'),
|
||||
|
||||
@ -4,7 +4,7 @@
|
||||
</div>
|
||||
<a class="logo">
|
||||
<div class="actions">
|
||||
<iqser-logo icon="red:logo"></iqser-logo>
|
||||
<iqser-logo [icon]="config.IS_DOCUMINE ? 'red:documine-logo' : 'red:logo'"></iqser-logo>
|
||||
<div class="app-name">{{ titleService.getTitle() }}</div>
|
||||
</div>
|
||||
</a>
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import { ChangeDetectionStrategy, Component } from '@angular/core';
|
||||
import { Title } from '@angular/platform-browser';
|
||||
import { getConfig } from '@iqser/common-ui';
|
||||
|
||||
@Component({
|
||||
selector: 'redaction-skeleton-top-bar',
|
||||
@ -8,5 +9,7 @@ import { Title } from '@angular/platform-browser';
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class SkeletonTopBarComponent {
|
||||
readonly config = getConfig();
|
||||
|
||||
constructor(readonly titleService: Title) {}
|
||||
}
|
||||
|
||||
7
apps/red-ui/src/app/guards/edit-attribute.guard.ts
Normal file
7
apps/red-ui/src/app/guards/edit-attribute.guard.ts
Normal file
@ -0,0 +1,7 @@
|
||||
import { CanDeactivateFn } from '@angular/router';
|
||||
import { inject } from '@angular/core';
|
||||
import { FileAttributesService } from '@services/entity-services/file-attributes.service';
|
||||
import { DossierOverviewScreenComponent } from '../modules/dossier-overview/screen/dossier-overview-screen.component';
|
||||
|
||||
export const editAttributeGuard: CanDeactivateFn<DossierOverviewScreenComponent> = () =>
|
||||
!inject(FileAttributesService).isEditingFileAttribute();
|
||||
@ -5,50 +5,40 @@ import { Dictionary } from '@red/domain';
|
||||
export const canUndo = (annotation: AnnotationWrapper, isApprover: boolean) =>
|
||||
!isApprover && (annotation.isSuggestion || annotation.pending);
|
||||
|
||||
export const canAcceptSuggestion = (annotation: AnnotationWrapper, isApprover: boolean, canProcessManualRedaction: boolean) => {
|
||||
const can = isApprover && (annotation.isSuggestion || annotation.isDeclinedSuggestion);
|
||||
return annotation.isSuggestionAdd || annotation.isSuggestionRemoveDictionary ? can && canProcessManualRedaction : can;
|
||||
};
|
||||
export const canForceHint = (annotation: AnnotationWrapper, canAddRedaction: boolean) =>
|
||||
canAddRedaction && annotation.isIgnoredHint && !annotation.pending;
|
||||
|
||||
export const canRejectSuggestion = (annotation: AnnotationWrapper, isApprover: boolean, canProcessManualRedaction: boolean) => {
|
||||
const can = isApprover && annotation.isSuggestion;
|
||||
return annotation.isSuggestionAdd || annotation.isSuggestionRemoveDictionary ? can && canProcessManualRedaction : can;
|
||||
};
|
||||
|
||||
export const canForceHint = (annotation: AnnotationWrapper, canAddOrRequestRedaction: boolean) =>
|
||||
canAddOrRequestRedaction && annotation.isIgnoredHint && !annotation.pending;
|
||||
|
||||
export const canForceRedaction = (annotation: AnnotationWrapper, canAddOrRequestRedaction: boolean) =>
|
||||
canAddOrRequestRedaction && annotation.isSkipped && !annotation.isFalsePositive && !annotation.pending;
|
||||
export const canForceRedaction = (annotation: AnnotationWrapper, canAddRedaction: boolean) =>
|
||||
canAddRedaction && annotation.isSkipped && !annotation.isFalsePositive && !annotation.pending;
|
||||
|
||||
export const canAcceptRecommendation = (annotation: AnnotationWrapper) => annotation.isRecommendation && !annotation.pending;
|
||||
|
||||
export const canMarkAsFalsePositive = (annotation: AnnotationWrapper, annotationEntity: Dictionary) =>
|
||||
annotation.canBeMarkedAsFalsePositive && annotationEntity.hasDictionary;
|
||||
|
||||
export const canRemoveOrSuggestToRemoveOnlyHere = (annotation: AnnotationWrapper, canAddOrRequestRedaction: boolean) =>
|
||||
canAddOrRequestRedaction && !annotation.pending && (annotation.isRedacted || (annotation.isHint && !annotation.isImage));
|
||||
export const canRemoveOnlyHere = (annotation: AnnotationWrapper, canAddRedaction: boolean) =>
|
||||
canAddRedaction && !annotation.pending && (annotation.isRedacted || (annotation.isHint && !annotation.isImage));
|
||||
|
||||
export const canRemoveOrSuggestToRemoveFromDictionary = (annotation: AnnotationWrapper) =>
|
||||
export const canRemoveFromDictionary = (annotation: AnnotationWrapper) =>
|
||||
annotation.isModifyDictionary &&
|
||||
(annotation.isRedacted || annotation.isSkipped || annotation.isHint) &&
|
||||
!annotation.pending &&
|
||||
!annotation.hasBeenResized;
|
||||
|
||||
export const canRemoveOrSuggestToRemoveRedaction = (annotations: AnnotationWrapper[], permissions: AnnotationPermissions) =>
|
||||
export const canRemoveRedaction = (annotations: AnnotationWrapper[], permissions: AnnotationPermissions) =>
|
||||
annotations.length === 1 &&
|
||||
(permissions.canRemoveOrSuggestToRemoveOnlyHere ||
|
||||
permissions.canRemoveOrSuggestToRemoveFromDictionary ||
|
||||
permissions.canMarkAsFalsePositive);
|
||||
(permissions.canRemoveOnlyHere || permissions.canRemoveFromDictionary || permissions.canMarkAsFalsePositive);
|
||||
|
||||
export const canChangeLegalBasis = (annotation: AnnotationWrapper, canAddOrRequestRedaction: boolean) =>
|
||||
canAddOrRequestRedaction && annotation.isRedacted && !annotation.pending;
|
||||
export const canChangeLegalBasis = (annotation: AnnotationWrapper, canAddRedaction: boolean) =>
|
||||
canAddRedaction && annotation.isRedacted && !annotation.pending;
|
||||
|
||||
export const canRecategorizeImage = (annotation: AnnotationWrapper) =>
|
||||
((annotation.isImage && !annotation.isSuggestion) || annotation.isSuggestionRecategorizeImage) && !annotation.pending;
|
||||
export const canRecategorizeImage = (annotation: AnnotationWrapper, canRecategorize: boolean) =>
|
||||
canRecategorize &&
|
||||
((annotation.isImage && !annotation.isSuggestion) || annotation.isSuggestionRecategorizeImage) &&
|
||||
!annotation.pending;
|
||||
|
||||
export const canResizeAnnotation = (annotation: AnnotationWrapper, canAddOrRequestRedaction: boolean) =>
|
||||
canAddOrRequestRedaction &&
|
||||
export const canResizeAnnotation = (annotation: AnnotationWrapper, canAddRedaction: boolean) =>
|
||||
canAddRedaction &&
|
||||
(((annotation.isRedacted || annotation.isImage) && !annotation.isSuggestion) ||
|
||||
annotation.isSuggestionResize ||
|
||||
annotation.isRecommendation) &&
|
||||
|
||||
@ -5,16 +5,14 @@ import { IqserPermissionsService } from '@iqser/common-ui';
|
||||
import { Roles } from '@users/roles';
|
||||
import {
|
||||
canAcceptRecommendation,
|
||||
canAcceptSuggestion,
|
||||
canChangeLegalBasis,
|
||||
canForceHint,
|
||||
canForceRedaction,
|
||||
canMarkAsFalsePositive,
|
||||
canRecategorizeImage,
|
||||
canRejectSuggestion,
|
||||
canRemoveOrSuggestToRemoveFromDictionary,
|
||||
canRemoveOrSuggestToRemoveOnlyHere,
|
||||
canRemoveOrSuggestToRemoveRedaction,
|
||||
canRemoveFromDictionary,
|
||||
canRemoveOnlyHere,
|
||||
canRemoveRedaction,
|
||||
canResizeAnnotation,
|
||||
canUndo,
|
||||
} from './annotation-permissions.utils';
|
||||
@ -23,11 +21,9 @@ export class AnnotationPermissions {
|
||||
canUndo = true;
|
||||
canAcceptRecommendation = true;
|
||||
canMarkAsFalsePositive = true;
|
||||
canRemoveOrSuggestToRemoveOnlyHere = true;
|
||||
canRemoveOrSuggestToRemoveFromDictionary = true;
|
||||
canRemoveOrSuggestToRemoveRedaction = true;
|
||||
canAcceptSuggestion = true;
|
||||
canRejectSuggestion = true;
|
||||
canRemoveOnlyHere = true;
|
||||
canRemoveFromDictionary = true;
|
||||
canRemoveRedaction = true;
|
||||
canForceRedaction = true;
|
||||
canChangeLegalBasis = true;
|
||||
canResizeAnnotation = true;
|
||||
@ -46,27 +42,23 @@ export class AnnotationPermissions {
|
||||
|
||||
const summedPermissions: AnnotationPermissions = new AnnotationPermissions();
|
||||
const canAddRedaction = permissionsService.has(Roles.redactions.write);
|
||||
const canRequestRedaction = permissionsService.has(Roles.redactions.request);
|
||||
const canProcessManualRedaction = permissionsService.has(Roles.redactions.processManualRequest);
|
||||
const canAddOrRequestRedaction = canAddRedaction || canRequestRedaction;
|
||||
const canDoAnyApproverAction = canAddRedaction && isApprover;
|
||||
|
||||
for (const annotation of annotations) {
|
||||
const permissions: AnnotationPermissions = new AnnotationPermissions();
|
||||
const annotationEntity = entities.find(entity => entity.type === annotation.type);
|
||||
|
||||
permissions.canUndo = canUndo(annotation, isApprover);
|
||||
permissions.canAcceptSuggestion = canAcceptSuggestion(annotation, isApprover, canProcessManualRedaction);
|
||||
permissions.canRejectSuggestion = canRejectSuggestion(annotation, isApprover, canProcessManualRedaction);
|
||||
permissions.canForceHint = canForceHint(annotation, canAddOrRequestRedaction);
|
||||
permissions.canForceRedaction = canForceRedaction(annotation, canAddOrRequestRedaction);
|
||||
permissions.canForceHint = canForceHint(annotation, canDoAnyApproverAction);
|
||||
permissions.canForceRedaction = canForceRedaction(annotation, canDoAnyApproverAction);
|
||||
permissions.canAcceptRecommendation = canAcceptRecommendation(annotation);
|
||||
permissions.canMarkAsFalsePositive = canMarkAsFalsePositive(annotation, annotationEntity);
|
||||
permissions.canRemoveOrSuggestToRemoveOnlyHere = canRemoveOrSuggestToRemoveOnlyHere(annotation, canAddOrRequestRedaction);
|
||||
permissions.canRemoveOrSuggestToRemoveFromDictionary = canRemoveOrSuggestToRemoveFromDictionary(annotation);
|
||||
permissions.canRemoveOrSuggestToRemoveRedaction = canRemoveOrSuggestToRemoveRedaction(annotations, permissions);
|
||||
permissions.canChangeLegalBasis = canChangeLegalBasis(annotation, canAddOrRequestRedaction);
|
||||
permissions.canRecategorizeImage = canRecategorizeImage(annotation);
|
||||
permissions.canResizeAnnotation = canResizeAnnotation(annotation, canAddOrRequestRedaction);
|
||||
permissions.canRemoveOnlyHere = canRemoveOnlyHere(annotation, canAddRedaction);
|
||||
permissions.canRemoveFromDictionary = canRemoveFromDictionary(annotation);
|
||||
permissions.canRemoveRedaction = canRemoveRedaction(annotations, permissions);
|
||||
permissions.canChangeLegalBasis = canChangeLegalBasis(annotation, canDoAnyApproverAction);
|
||||
permissions.canRecategorizeImage = canRecategorizeImage(annotation, canDoAnyApproverAction);
|
||||
permissions.canResizeAnnotation = canResizeAnnotation(annotation, canDoAnyApproverAction);
|
||||
|
||||
summedPermissions._merge(permissions);
|
||||
}
|
||||
|
||||
@ -371,6 +371,9 @@ export class AnnotationWrapper implements IListable {
|
||||
if (redactionLogEntry.reason?.toLowerCase() === 'false positive') {
|
||||
return annotationTypesTranslations[SuggestionAddFalsePositive];
|
||||
}
|
||||
if (annotation.superType === SuperTypes.ManualRedaction && annotation.hintDictionary) {
|
||||
return _('annotation-type.manual-hint');
|
||||
}
|
||||
return annotationTypesTranslations[annotation.superType];
|
||||
}
|
||||
|
||||
|
||||
@ -6,7 +6,7 @@ import { AccountSideNavComponent } from './account-side-nav/account-side-nav.com
|
||||
import { BaseAccountScreenComponent } from './base-account-screen/base-account-screen-component';
|
||||
import { NotificationPreferencesService } from './services/notification-preferences.service';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { IconButtonComponent, IqserHelpModeModule } from '@iqser/common-ui';
|
||||
import { IconButtonComponent, IqserAllowDirective, IqserHelpModeModule } from '@iqser/common-ui';
|
||||
import { PreferencesComponent } from './screens/preferences/preferences.component';
|
||||
import { SideNavComponent } from '@iqser/common-ui/lib/shared';
|
||||
|
||||
@ -20,6 +20,7 @@ import { SideNavComponent } from '@iqser/common-ui/lib/shared';
|
||||
IqserHelpModeModule,
|
||||
IconButtonComponent,
|
||||
SideNavComponent,
|
||||
IqserAllowDirective,
|
||||
],
|
||||
providers: [NotificationPreferencesService],
|
||||
})
|
||||
|
||||
@ -13,7 +13,7 @@
|
||||
<div *ngFor="let key of notificationGroupsKeys; let i = index" class="group">
|
||||
<div [translate]="translations[key]" class="group-title"></div>
|
||||
<div class="iqser-input-group">
|
||||
<ng-container *ngFor="let preference of notificationGroupsValues[i]">
|
||||
<ng-container *ngFor="let preference of getRssFilteredSettings(notificationGroupsValues[i])">
|
||||
<mat-checkbox
|
||||
*ngIf="!skipPreference(preference)"
|
||||
(change)="addRemovePreference($event.checked, category, preference)"
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, OnInit } from '@angular/core';
|
||||
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, inject, OnInit } from '@angular/core';
|
||||
import { UntypedFormBuilder, UntypedFormGroup } from '@angular/forms';
|
||||
import { NotificationPreferencesService } from '../../../services/notification-preferences.service';
|
||||
import { BaseFormComponent, LoadingService, Toaster } from '@iqser/common-ui';
|
||||
import { BaseFormComponent, IqserPermissionsService, LoadingService, Toaster } from '@iqser/common-ui';
|
||||
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
||||
import {
|
||||
NotificationCategoriesValues,
|
||||
@ -13,6 +13,9 @@ import {
|
||||
import { firstValueFrom } from 'rxjs';
|
||||
import { notificationsSettingsTranslations } from '@translations/notifications-settings-translations';
|
||||
import { getCurrentUser } from '@iqser/common-ui/lib/users';
|
||||
import { Roles } from '@users/roles';
|
||||
|
||||
const RSS_EXCLUDED_SETTINGS = ['USER_PROMOTED_TO_APPROVER', 'USER_DEGRADED_TO_REVIEWER', 'ASSIGN_REVIEWER'];
|
||||
|
||||
@Component({
|
||||
templateUrl: './notifications-screen.component.html',
|
||||
@ -26,18 +29,24 @@ export class NotificationsScreenComponent extends BaseFormComponent implements O
|
||||
readonly translations = notificationsSettingsTranslations;
|
||||
readonly currentUser = getCurrentUser<User>();
|
||||
|
||||
constructor(
|
||||
private readonly _toaster: Toaster,
|
||||
private readonly _formBuilder: UntypedFormBuilder,
|
||||
private readonly _loadingService: LoadingService,
|
||||
private readonly _notificationPreferencesService: NotificationPreferencesService,
|
||||
private readonly _changeRef: ChangeDetectorRef,
|
||||
) {
|
||||
readonly #toaster = inject(Toaster);
|
||||
readonly #formBuilder = inject(UntypedFormBuilder);
|
||||
readonly #loadingService = inject(LoadingService);
|
||||
readonly #notificationPreferencesService = inject(NotificationPreferencesService);
|
||||
readonly #cdRef = inject(ChangeDetectorRef);
|
||||
readonly #iqserPermissionsService = inject(IqserPermissionsService);
|
||||
readonly #isRss = this.#iqserPermissionsService.has(Roles.getRss);
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
async ngOnInit(): Promise<void> {
|
||||
await this._initializeForm();
|
||||
await this.#initializeForm();
|
||||
}
|
||||
|
||||
getRssFilteredSettings(settings: string[]) {
|
||||
return settings.filter(s => (this.#isRss ? !RSS_EXCLUDED_SETTINGS.includes(s) : true));
|
||||
}
|
||||
|
||||
isCategoryActive(category: string) {
|
||||
@ -66,19 +75,19 @@ export class NotificationsScreenComponent extends BaseFormComponent implements O
|
||||
}
|
||||
|
||||
async save() {
|
||||
this._loadingService.start();
|
||||
this.#loadingService.start();
|
||||
try {
|
||||
await firstValueFrom(this._notificationPreferencesService.update(this.form.value));
|
||||
await firstValueFrom(this.#notificationPreferencesService.update(this.form.value));
|
||||
} catch (e) {
|
||||
this._toaster.error(_('notifications-screen.error.generic'));
|
||||
this.#toaster.error(_('notifications-screen.error.generic'));
|
||||
}
|
||||
this.initialFormValue = JSON.parse(JSON.stringify(this.form.getRawValue()));
|
||||
this._changeRef.markForCheck();
|
||||
this._loadingService.stop();
|
||||
this.#cdRef.markForCheck();
|
||||
this.#loadingService.stop();
|
||||
}
|
||||
|
||||
private _getForm(): UntypedFormGroup {
|
||||
return this._formBuilder.group({
|
||||
#getForm(): UntypedFormGroup {
|
||||
return this.#formBuilder.group({
|
||||
inAppNotificationsEnabled: [undefined],
|
||||
emailNotificationsEnabled: [undefined],
|
||||
emailNotificationType: [undefined],
|
||||
@ -87,14 +96,14 @@ export class NotificationsScreenComponent extends BaseFormComponent implements O
|
||||
});
|
||||
}
|
||||
|
||||
private async _initializeForm() {
|
||||
this._loadingService.start();
|
||||
async #initializeForm() {
|
||||
this.#loadingService.start();
|
||||
|
||||
this.form = this._getForm();
|
||||
const notificationPreferences = await firstValueFrom(this._notificationPreferencesService.get());
|
||||
this.form = this.#getForm();
|
||||
const notificationPreferences = await firstValueFrom(this.#notificationPreferencesService.get());
|
||||
this.form.patchValue(notificationPreferences);
|
||||
this.initialFormValue = JSON.parse(JSON.stringify(this.form.getRawValue()));
|
||||
|
||||
this._loadingService.stop();
|
||||
this.#loadingService.stop();
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
<form (submit)="save()" [formGroup]="form">
|
||||
<form [formGroup]="form">
|
||||
<div class="dialog-content">
|
||||
<div *ngIf="currentScreen === screens.WARNING_PREFERENCES" class="content-delimiter"></div>
|
||||
<div class="dialog-content-left">
|
||||
@ -13,6 +13,11 @@
|
||||
{{ 'preferences-screen.form.show-suggestions-in-preview' | translate }}
|
||||
</mat-slide-toggle>
|
||||
</div>
|
||||
<div class="iqser-input-group" *allow="roles.getRss">
|
||||
<mat-slide-toggle color="primary" formControlName="openStructuredComponentManagementDialogByDefault">
|
||||
{{ 'preferences-screen.form.open-structured-view-by-default' | translate }}
|
||||
</mat-slide-toggle>
|
||||
</div>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="currentScreen === screens.WARNING_PREFERENCES">
|
||||
<p class="warnings-subtitle">{{ 'preferences-screen.warnings-subtitle' | translate }}</p>
|
||||
@ -38,7 +43,6 @@
|
||||
(action)="save()"
|
||||
[disabled]="!valid || !changed"
|
||||
[label]="'preferences-screen.actions.save' | translate"
|
||||
[submit]="true"
|
||||
[type]="iconButtonTypes.primary"
|
||||
></iqser-icon-button>
|
||||
</div>
|
||||
|
||||
@ -10,6 +10,7 @@ interface PreferencesForm {
|
||||
// preferences
|
||||
autoExpandFiltersOnActions: boolean;
|
||||
displaySuggestionsInPreview: boolean;
|
||||
openStructuredComponentManagementDialogByDefault: boolean;
|
||||
// warnings preferences
|
||||
unapprovedSuggestionsWarning: boolean;
|
||||
loadAllAnnotationsWarning: boolean;
|
||||
@ -35,6 +36,7 @@ export class PreferencesComponent extends BaseFormComponent {
|
||||
readonly currentScreen: Screen;
|
||||
readonly screens = Screens;
|
||||
initialFormValue: PreferencesForm;
|
||||
readonly roles = Roles;
|
||||
|
||||
constructor(
|
||||
readonly userPreferenceService: UserPreferenceService,
|
||||
@ -48,6 +50,9 @@ export class PreferencesComponent extends BaseFormComponent {
|
||||
// preferences
|
||||
autoExpandFiltersOnActions: [this.userPreferenceService.getAutoExpandFiltersOnActions()],
|
||||
displaySuggestionsInPreview: [this.userPreferenceService.getDisplaySuggestionsInPreview()],
|
||||
openStructuredComponentManagementDialogByDefault: [
|
||||
this.userPreferenceService.getOpenStructuredComponentManagementDialogByDefault(),
|
||||
],
|
||||
// warnings preferences
|
||||
unapprovedSuggestionsWarning: [this.userPreferenceService.getUnapprovedSuggestionsWarning()],
|
||||
loadAllAnnotationsWarning: [this.userPreferenceService.getBool(PreferencesKeys.loadAllAnnotationsWarning)],
|
||||
@ -68,6 +73,12 @@ export class PreferencesComponent extends BaseFormComponent {
|
||||
if (this.form.controls.displaySuggestionsInPreview.value !== this.userPreferenceService.getDisplaySuggestionsInPreview()) {
|
||||
await this.userPreferenceService.toggleDisplaySuggestionsInPreview();
|
||||
}
|
||||
if (
|
||||
this.form.controls.openStructuredComponentManagementDialogByDefault.value !==
|
||||
this.userPreferenceService.getOpenStructuredComponentManagementDialogByDefault()
|
||||
) {
|
||||
await this.userPreferenceService.toggleOpenStructuredComponentManagementDialogByDefault();
|
||||
}
|
||||
if (this.form.controls.unapprovedSuggestionsWarning.value !== this.userPreferenceService.getUnapprovedSuggestionsWarning()) {
|
||||
await this.userPreferenceService.toggleUnapprovedSuggestionsWarning();
|
||||
}
|
||||
@ -83,12 +94,20 @@ export class PreferencesComponent extends BaseFormComponent {
|
||||
}
|
||||
|
||||
await this.userPreferenceService.reload();
|
||||
this.form.patchValue({
|
||||
autoExpandFiltersOnActions: this.userPreferenceService.getAutoExpandFiltersOnActions(),
|
||||
displaySuggestionsInPreview: this.userPreferenceService.getDisplaySuggestionsInPreview(),
|
||||
unapprovedSuggestionsWarning: this.userPreferenceService.getUnapprovedSuggestionsWarning(),
|
||||
});
|
||||
this.#patchValues();
|
||||
|
||||
this.initialFormValue = this.form.getRawValue();
|
||||
this._changeRef.markForCheck();
|
||||
}
|
||||
|
||||
#patchValues() {
|
||||
this.form.patchValue({
|
||||
autoExpandFiltersOnActions: this.userPreferenceService.getAutoExpandFiltersOnActions(),
|
||||
displaySuggestionsInPreview: this.userPreferenceService.getDisplaySuggestionsInPreview(),
|
||||
openStructuredComponentManagementDialogByDefault:
|
||||
this.userPreferenceService.getOpenStructuredComponentManagementDialogByDefault(),
|
||||
unapprovedSuggestionsWarning: this.userPreferenceService.getUnapprovedSuggestionsWarning(),
|
||||
loadAllAnnotationsWarning: this.userPreferenceService.getBool(PreferencesKeys.loadAllAnnotationsWarning),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -56,6 +56,7 @@ import { AuditInfoDialogComponent } from './dialogs/audit-info-dialog/audit-info
|
||||
import { DossierTemplateActionsComponent } from './shared/components/dossier-template-actions/dossier-template-actions.component';
|
||||
import { IqserUsersModule } from '@iqser/common-ui/lib/users';
|
||||
import { TenantPipe } from '@iqser/common-ui/lib/tenants';
|
||||
import { SelectComponent } from '@shared/components/select/select.component';
|
||||
|
||||
const dialogs = [
|
||||
AddEditCloneDossierTemplateDialogComponent,
|
||||
@ -123,6 +124,7 @@ const components = [
|
||||
IqserAllowDirective,
|
||||
IqserDenyDirective,
|
||||
TenantPipe,
|
||||
SelectComponent,
|
||||
],
|
||||
})
|
||||
export class AdminModule {}
|
||||
|
||||
@ -9,10 +9,11 @@ import { ChartConfiguration, ChartDataset } from 'chart.js';
|
||||
export class ChartComponent implements OnChanges {
|
||||
@Input({ required: true }) datasets: ChartDataset[];
|
||||
@Input({ required: true }) labels: string[];
|
||||
@Input() ticksCallback?: (value: number) => string;
|
||||
@Input() valueFormatter?: (value: number) => string;
|
||||
@Input() secondaryAxis = false;
|
||||
@Input() yAxisLabel?: string;
|
||||
@Input() yAxisLabelRight?: string;
|
||||
@Input() reverseLegend = false;
|
||||
|
||||
chartData: ChartConfiguration['data'];
|
||||
chartOptions: ChartConfiguration<'line'>['options'] = {};
|
||||
@ -34,7 +35,7 @@ export class ChartComponent implements OnChanges {
|
||||
scales: {
|
||||
y: {
|
||||
stacked: true,
|
||||
ticks: { callback: this.ticksCallback ? (value: number) => this.ticksCallback(value) : undefined },
|
||||
ticks: { callback: this.valueFormatter ? (value: number) => this.valueFormatter(value) : undefined },
|
||||
title: {
|
||||
display: !!this.yAxisLabel,
|
||||
text: this.yAxisLabel,
|
||||
@ -52,10 +53,10 @@ export class ChartComponent implements OnChanges {
|
||||
},
|
||||
},
|
||||
plugins: {
|
||||
legend: { position: 'right' },
|
||||
legend: { position: 'right', reverse: this.reverseLegend },
|
||||
tooltip: {
|
||||
callbacks: {
|
||||
label: this.ticksCallback ? item => `${item.dataset.label}: ${this.ticksCallback(item.parsed.y)}` : undefined,
|
||||
label: this.valueFormatter ? item => `${item.dataset.label}: ${this.valueFormatter(item.parsed.y)}` : undefined,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@ -42,6 +42,7 @@
|
||||
*ngIf="data$ | async as data"
|
||||
[datasets]="data.datasets"
|
||||
[labels]="data.labels"
|
||||
[ticksCallback]="formatSize"
|
||||
[reverseLegend]="true"
|
||||
[valueFormatter]="formatSize"
|
||||
></redaction-chart>
|
||||
</div>
|
||||
|
||||
@ -5,7 +5,7 @@ import { LicenseService } from '@services/license.service';
|
||||
import { map, tap } from 'rxjs/operators';
|
||||
import type { DonutChartConfig, ILicenseReport } from '@red/domain';
|
||||
import { ChartDataset } from 'chart.js';
|
||||
import { ChartBlue, ChartGreen, ChartGrey, ChartRed } from '../../utils/constants';
|
||||
import { ChartBlack, ChartBlue, ChartGreen, ChartGrey, ChartRed } from '../../utils/constants';
|
||||
import { getLabelsFromLicense, getLineConfig } from '../../utils/functions';
|
||||
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
||||
|
||||
@ -71,21 +71,27 @@ export class LicenseStorageComponent {
|
||||
{
|
||||
data: monthlyData.flatMap(d => d.activeFilesUploadedBytes),
|
||||
label: this._translateService.instant('license-info-screen.storage.active-documents'),
|
||||
...getLineConfig(ChartGreen, 'origin'),
|
||||
...getLineConfig(ChartGreen, false, 'origin'),
|
||||
stack: 'storage',
|
||||
},
|
||||
{
|
||||
data: monthlyData.flatMap(d => d.archivedFilesUploadedBytes),
|
||||
label: this._translateService.instant('license-info-screen.storage.archived-documents'),
|
||||
...getLineConfig(ChartBlue, '-1'),
|
||||
...getLineConfig(ChartBlue, false, '-1'),
|
||||
stack: 'storage',
|
||||
},
|
||||
{
|
||||
data: monthlyData.flatMap(d => d.trashFilesUploadedBytes),
|
||||
label: this._translateService.instant('license-info-screen.storage.trash-documents'),
|
||||
...getLineConfig(ChartRed, '-1'),
|
||||
...getLineConfig(ChartRed, false, '-1'),
|
||||
stack: 'storage',
|
||||
},
|
||||
{
|
||||
data: monthlyData.flatMap(d => d.activeFilesUploadedBytes + d.archivedFilesUploadedBytes + d.trashFilesUploadedBytes),
|
||||
label: this._translateService.instant('license-info-screen.storage.all-documents'),
|
||||
...getLineConfig(ChartBlack, true, false),
|
||||
borderWidth: 2,
|
||||
},
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@ -35,7 +35,7 @@
|
||||
[datasets]="data.datasets"
|
||||
[labels]="data.labels"
|
||||
[secondaryAxis]="true"
|
||||
[yAxisLabelRight]="'license-info-screen.chart.total-pages' | translate"
|
||||
[yAxisLabel]="'license-info-screen.chart.pages-per-month' | translate"
|
||||
[yAxisLabelRight]="'license-info-screen.pages.total-pages' | translate"
|
||||
[yAxisLabel]="'license-info-screen.pages.pages-per-month' | translate"
|
||||
></redaction-chart>
|
||||
</div>
|
||||
|
||||
@ -5,6 +5,7 @@ import type { ILicenseReport } from '@red/domain';
|
||||
import { ChartDataset } from 'chart.js';
|
||||
import { ChartBlue, ChartGreen, ChartRed } from '../../utils/constants';
|
||||
import { getLabelsFromLicense, getLineConfig } from '../../utils/functions';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
|
||||
@Component({
|
||||
selector: 'red-license-usage',
|
||||
@ -21,7 +22,7 @@ export class LicenseUsageComponent {
|
||||
})),
|
||||
);
|
||||
|
||||
constructor(readonly licenseService: LicenseService) {}
|
||||
constructor(readonly licenseService: LicenseService, private readonly _translateService: TranslateService) {}
|
||||
|
||||
getAnalysisPercentageOfLicense() {
|
||||
const totalLicensedNumberOfPages = this.licenseService.totalLicensedNumberOfPages;
|
||||
@ -35,16 +36,16 @@ export class LicenseUsageComponent {
|
||||
return [
|
||||
{
|
||||
data: monthlyData.flatMap(d => d.numberOfAnalyzedPages),
|
||||
label: 'Pages per Month',
|
||||
label: this._translateService.instant('license-info-screen.pages.pages-per-month'),
|
||||
type: 'bar',
|
||||
backgroundColor: ChartBlue,
|
||||
borderColor: ChartBlue,
|
||||
order: 2,
|
||||
},
|
||||
{
|
||||
data: monthlyData.flatMap(() => 200000),
|
||||
label: 'Total Pages',
|
||||
...getLineConfig(ChartRed, false),
|
||||
data: monthlyData.flatMap(() => this.licenseService.totalLicensedNumberOfPages),
|
||||
label: this._translateService.instant('license-info-screen.pages.total-pages'),
|
||||
...getLineConfig(ChartRed, true, false),
|
||||
yAxisID: 'y1',
|
||||
order: 1,
|
||||
},
|
||||
@ -54,10 +55,10 @@ export class LicenseUsageComponent {
|
||||
month.numberOfAnalyzedPages +
|
||||
monthlyData.slice(0, monthIndex).reduce((acc, curr) => acc + curr.numberOfAnalyzedPages, 0),
|
||||
),
|
||||
label: 'Cumulative Pages',
|
||||
label: this._translateService.instant('license-info-screen.pages.cumulative'),
|
||||
yAxisID: 'y1',
|
||||
order: 1,
|
||||
...getLineConfig(ChartGreen, false),
|
||||
...getLineConfig(ChartGreen, true, false),
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
@ -2,3 +2,4 @@ export const ChartRed = '#dd4d50';
|
||||
export const ChartGreen = '#5ce594';
|
||||
export const ChartBlue = '#0389ec';
|
||||
export const ChartGrey = '#ccced3'; // grey-5
|
||||
export const ChartBlack = '#283241'; // grey-1
|
||||
|
||||
@ -10,6 +10,7 @@ export const verboseDate = (date: Dayjs) => `${monthNames[date.month()]} ${date.
|
||||
|
||||
export const getLineConfig: (
|
||||
color: string,
|
||||
displayLine: boolean,
|
||||
target: FillTarget,
|
||||
) => {
|
||||
type: 'line';
|
||||
@ -17,9 +18,9 @@ export const getLineConfig: (
|
||||
backgroundColor: string;
|
||||
pointBackgroundColor: string;
|
||||
fill: ComplexFillTarget;
|
||||
} = (color, target) => ({
|
||||
} = (color, displayLine, target) => ({
|
||||
type: 'line',
|
||||
borderColor: hexToRgba(color, 1),
|
||||
borderColor: hexToRgba(color, displayLine ? 1 : 0),
|
||||
backgroundColor: hexToRgba(color, 1),
|
||||
pointBackgroundColor: hexToRgba(color, 1),
|
||||
fill: {
|
||||
@ -27,6 +28,7 @@ export const getLineConfig: (
|
||||
above: hexToRgba(color, 0.3),
|
||||
below: hexToRgba(color, 0.3),
|
||||
},
|
||||
pointStyle: false,
|
||||
});
|
||||
|
||||
export const getLabelsFromMonthlyData = (monthlyData: ILicenseData[]) => monthlyData.map(data => verboseDate(dayjs(data.startDate)));
|
||||
|
||||
@ -107,7 +107,7 @@ export class AdminSideNavComponent implements OnInit {
|
||||
screen: 'watermarks',
|
||||
label: _('admin-side-nav.watermarks'),
|
||||
helpModeKey: 'watermarks',
|
||||
show: true,
|
||||
show: this._permissionsService.has(Roles.watermarks.read),
|
||||
},
|
||||
{
|
||||
screen: 'file-attributes',
|
||||
|
||||
@ -40,7 +40,7 @@
|
||||
|
||||
<ng-template #input>
|
||||
<div [ngClass]="{ 'workflow-edit-input': mode === 'workflow' }" class="edit-input" iqserStopPropagation>
|
||||
<form [formGroup]="form">
|
||||
<form (ngSubmit)="form.valid && save()" [formGroup]="form">
|
||||
<iqser-dynamic-input
|
||||
(closedDatepicker)="closedDatepicker = $event"
|
||||
(keydown.escape)="close()"
|
||||
|
||||
@ -183,3 +183,9 @@
|
||||
.hide {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 1395px) {
|
||||
.file-attribute .edit-input form .workflow-input {
|
||||
width: 63%;
|
||||
}
|
||||
}
|
||||
|
||||
@ -2,7 +2,7 @@ import { Component, computed, effect, HostListener, Input, OnDestroy } from '@an
|
||||
import { Dossier, File, FileAttributeConfigTypes, IFileAttributeConfig } from '@red/domain';
|
||||
import { BaseFormComponent, HelpModeService, ListingService, Toaster } from '@iqser/common-ui';
|
||||
import { PermissionsService } from '@services/permissions.service';
|
||||
import { FormBuilder, UntypedFormGroup, Validators } from '@angular/forms';
|
||||
import { FormBuilder, UntypedFormGroup } from '@angular/forms';
|
||||
import { FileAttributesService } from '@services/entity-services/file-attributes.service';
|
||||
import { firstValueFrom, Subscription } from 'rxjs';
|
||||
import { FilesService } from '@services/files/files.service';
|
||||
@ -11,6 +11,7 @@ import dayjs from 'dayjs';
|
||||
import { NavigationEnd, Router } from '@angular/router';
|
||||
import { filter, map, tap } from 'rxjs/operators';
|
||||
import { ConfigService } from '../../config.service';
|
||||
import { Debounce } from '@iqser/common-ui/lib/utils';
|
||||
|
||||
@Component({
|
||||
selector: 'redaction-file-attribute [fileAttribute] [file] [dossier]',
|
||||
@ -28,6 +29,8 @@ export class FileAttributeComponent extends BaseFormComponent implements OnDestr
|
||||
readonly #shouldClose = computed(
|
||||
() =>
|
||||
this.fileAttributesService.isEditingFileAttribute() &&
|
||||
!!this.file &&
|
||||
!!this.fileAttribute &&
|
||||
(this.fileAttribute.id !== this.fileAttributesService.openAttributeEdit() ||
|
||||
this.file.fileId !== this.fileAttributesService.fileEdit()),
|
||||
);
|
||||
@ -71,6 +74,7 @@ export class FileAttributeComponent extends BaseFormComponent implements OnDestr
|
||||
return this.file.fileAttributes.attributeIdToValue[this.fileAttribute.id];
|
||||
}
|
||||
|
||||
@Debounce(50)
|
||||
@HostListener('document:click')
|
||||
clickOutside() {
|
||||
if (this.isInEditMode && this.closedDatepicker) {
|
||||
@ -85,6 +89,7 @@ export class FileAttributeComponent extends BaseFormComponent implements OnDestr
|
||||
async editFileAttribute($event: MouseEvent): Promise<void> {
|
||||
if (!this.file.isInitialProcessing && this.permissionsService.canEditFileAttributes(this.file, this.dossier)) {
|
||||
$event.stopPropagation();
|
||||
this.fileAttributesService.openAttributeEdits.mutate(value => value.push(this.fileAttribute.id));
|
||||
this.#toggleEdit();
|
||||
this.fileAttributesService.setFileEdit(this.file.fileId);
|
||||
this.fileAttributesService.setOpenAttributeEdit(this.fileAttribute.id);
|
||||
@ -116,6 +121,13 @@ export class FileAttributeComponent extends BaseFormComponent implements OnDestr
|
||||
if (this.isInEditMode) {
|
||||
this.form = this.#getForm();
|
||||
this.#toggleEdit();
|
||||
this.fileAttributesService.openAttributeEdits.mutate(value => {
|
||||
for (let index = 0; index < value.length; index++) {
|
||||
if (value[index] === this.fileAttribute.id) {
|
||||
value.splice(index, 1);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -133,16 +145,16 @@ export class FileAttributeComponent extends BaseFormComponent implements OnDestr
|
||||
const fileAttributes = this.file.fileAttributes.attributeIdToValue;
|
||||
Object.keys(fileAttributes).forEach(key => {
|
||||
const attrValue = fileAttributes[key];
|
||||
config[key] = [
|
||||
dayjs(attrValue, 'YYYY-MM-DD', true).isValid() ? dayjs(attrValue).toDate() : attrValue,
|
||||
Validators.pattern(/^(\s+\S+\s*)*(?!\s).*$/),
|
||||
];
|
||||
config[key] = [dayjs(attrValue, 'YYYY-MM-DD', true).isValid() ? dayjs(attrValue).toDate() : attrValue];
|
||||
});
|
||||
return this._formBuilder.group(config, {
|
||||
validators: control =>
|
||||
!control.get(this.fileAttribute.id).value?.trim().length && !this.fileAttributeValue ? { emptyString: true } : null,
|
||||
});
|
||||
return this._formBuilder.group(config);
|
||||
}
|
||||
|
||||
#formatAttributeValue(attrValue) {
|
||||
return this.isDate ? attrValue && dayjs(attrValue).format('YYYY-MM-DD') : attrValue.replaceAll(/\s\s+/g, ' ');
|
||||
return this.isDate ? attrValue && dayjs(attrValue).format('YYYY-MM-DD') : attrValue.trim().replaceAll(/\s\s+/g, ' ');
|
||||
}
|
||||
|
||||
#toggleEdit(): void {
|
||||
@ -153,7 +165,6 @@ export class FileAttributeComponent extends BaseFormComponent implements OnDestr
|
||||
}
|
||||
|
||||
this.isInEditMode = !this.isInEditMode;
|
||||
this.fileAttributesService.isEditingFileAttribute.set(this.isInEditMode);
|
||||
|
||||
if (this.isInEditMode) {
|
||||
this.#focusOnEditInput();
|
||||
|
||||
@ -47,5 +47,5 @@
|
||||
</iqser-page-header>
|
||||
|
||||
<ng-template #viewModeSelection>
|
||||
<redaction-view-mode-selection></redaction-view-mode-selection>
|
||||
<redaction-view-mode-selection iqserDisableStopPropagation></redaction-view-mode-selection>
|
||||
</ng-template>
|
||||
|
||||
@ -51,15 +51,6 @@
|
||||
icon="iqser:check"
|
||||
></iqser-circle-button>
|
||||
|
||||
<iqser-circle-button
|
||||
(action)="annotationActionsService.acceptSuggestion(annotations)"
|
||||
*ngIf="annotationPermissions.canAcceptSuggestion"
|
||||
[tooltipPosition]="tooltipPosition"
|
||||
[tooltip]="'annotation-actions.accept-suggestion.label' | translate"
|
||||
[type]="buttonType"
|
||||
icon="iqser:check"
|
||||
></iqser-circle-button>
|
||||
|
||||
<iqser-circle-button
|
||||
(action)="annotationActionsService.convertHighlights(annotations)"
|
||||
*ngIf="viewModeService.isEarmarks()"
|
||||
@ -88,12 +79,12 @@
|
||||
></iqser-circle-button>
|
||||
|
||||
<iqser-circle-button
|
||||
(action)="annotationActionsService.rejectSuggestion(annotations)"
|
||||
*ngIf="annotationPermissions.canRejectSuggestion"
|
||||
(action)="annotationActionsService.undoDirectAction(annotations)"
|
||||
*allow="roles.redactions.deleteManual; if: annotationPermissions.canUndo"
|
||||
[tooltipPosition]="tooltipPosition"
|
||||
[tooltip]="'annotation-actions.reject-suggestion' | translate"
|
||||
[tooltip]="'annotation-actions.undo' | translate"
|
||||
[type]="buttonType"
|
||||
icon="iqser:close"
|
||||
icon="red:undo"
|
||||
></iqser-circle-button>
|
||||
|
||||
<iqser-circle-button
|
||||
@ -152,7 +143,7 @@
|
||||
|
||||
<iqser-circle-button
|
||||
(action)="removeOrSuggestRemoveRedaction()"
|
||||
*ngIf="annotationPermissions.canRemoveOrSuggestToRemoveRedaction"
|
||||
*ngIf="annotationPermissions.canRemoveRedaction"
|
||||
[tooltipPosition]="tooltipPosition"
|
||||
[tooltip]="'annotation-actions.remove-annotation.remove-redaction' | translate"
|
||||
[type]="buttonType"
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
<div
|
||||
(click)="pageSelected.emit(number)"
|
||||
(dblclick)="toggleReadState()"
|
||||
*ngIf="componentContext$ | async"
|
||||
*ngIf="assigneeChanged$ | async"
|
||||
[class.active]="isActive"
|
||||
[class.read]="read"
|
||||
[id]="'quick-nav-page-' + number"
|
||||
|
||||
@ -1,34 +1,31 @@
|
||||
import { Component, EventEmitter, Input, OnChanges, OnInit, Output } from '@angular/core';
|
||||
import { Component, effect, EventEmitter, inject, Input, OnChanges, Output } from '@angular/core';
|
||||
import { PermissionsService } from '@services/permissions.service';
|
||||
import { ViewedPagesService } from '@services/files/viewed-pages.service';
|
||||
import { FilePreviewStateService } from '../../services/file-preview-state.service';
|
||||
import { PageRotationService } from '../../../pdf-viewer/services/page-rotation.service';
|
||||
import { getConfig } from '@iqser/common-ui';
|
||||
import { map, tap } from 'rxjs/operators';
|
||||
import { map, startWith, tap } from 'rxjs/operators';
|
||||
import { AppConfig, ViewedPage } from '@red/domain';
|
||||
import { ViewedPagesMapService } from '@services/files/viewed-pages-map.service';
|
||||
import { pairwise } from 'rxjs';
|
||||
import { Observable, pairwise } from 'rxjs';
|
||||
import { PdfViewer } from '../../../pdf-viewer/services/pdf-viewer.service';
|
||||
import { ContextComponent } from '@iqser/common-ui/lib/utils';
|
||||
|
||||
interface PageIndicatorContext {
|
||||
isRotated: boolean;
|
||||
assigneeChanged: boolean;
|
||||
}
|
||||
import { NGXLogger } from 'ngx-logger';
|
||||
|
||||
@Component({
|
||||
selector: 'redaction-page-indicator',
|
||||
templateUrl: './page-indicator.component.html',
|
||||
styleUrls: ['./page-indicator.component.scss'],
|
||||
})
|
||||
export class PageIndicatorComponent extends ContextComponent<PageIndicatorContext> implements OnChanges, OnInit {
|
||||
export class PageIndicatorComponent implements OnChanges {
|
||||
readonly #config = getConfig<AppConfig>();
|
||||
readonly #logger = inject(NGXLogger);
|
||||
@Input({ required: true }) number: number;
|
||||
@Input() showDottedIcon = false;
|
||||
@Input() activeSelection = false;
|
||||
@Input() read = false;
|
||||
@Output() readonly pageSelected = new EventEmitter<number>();
|
||||
pageReadTimeout: number = null;
|
||||
readonly assigneeChanged$: Observable<boolean>;
|
||||
|
||||
constructor(
|
||||
private readonly _viewedPagesService: ViewedPagesService,
|
||||
@ -38,38 +35,45 @@ export class PageIndicatorComponent extends ContextComponent<PageIndicatorContex
|
||||
private readonly _pdf: PdfViewer,
|
||||
readonly pageRotationService: PageRotationService,
|
||||
) {
|
||||
super();
|
||||
this.assigneeChanged$ = this._state.file$.pipe(
|
||||
pairwise(),
|
||||
map(([prevFile, currFile]) => prevFile.assignee !== currFile.assignee),
|
||||
tap(assigneeChanged => assigneeChanged && this.handlePageRead()),
|
||||
startWith(true),
|
||||
map(() => true),
|
||||
);
|
||||
|
||||
effect(() => {
|
||||
if (this.isActive) {
|
||||
this.handlePageRead();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
get isActive() {
|
||||
return this.number === this._pdf.currentPage();
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
const assigneeChanged$ = this._state.file$.pipe(
|
||||
pairwise(),
|
||||
map(([prevFile, currFile]) => prevFile.assignee !== currFile.assignee),
|
||||
tap(assigneeChanged => assigneeChanged && this.handlePageRead()),
|
||||
);
|
||||
super._initContext({ assigneeChanged: assigneeChanged$ });
|
||||
}
|
||||
|
||||
ngOnChanges() {
|
||||
this.handlePageRead();
|
||||
}
|
||||
|
||||
async toggleReadState() {
|
||||
if (this._permissionService.canMarkPagesAsViewed(this._state.file())) {
|
||||
if (this.read) {
|
||||
await this.#markPageUnread();
|
||||
} else {
|
||||
await this.#markPageRead();
|
||||
}
|
||||
if (!this._permissionService.canMarkPagesAsViewed(this._state.file())) {
|
||||
this.#logger.info('[PAGES] Cannot toggle read state');
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.read) {
|
||||
await this.#markPageUnread();
|
||||
} else {
|
||||
await this.#markPageRead();
|
||||
}
|
||||
}
|
||||
|
||||
handlePageRead(): void {
|
||||
if (!this._permissionService.canMarkPagesAsViewed(this._state.file())) {
|
||||
this.#logger.info('[PAGES] Cannot mark pages as read');
|
||||
return;
|
||||
}
|
||||
|
||||
@ -87,6 +91,8 @@ export class PageIndicatorComponent extends ContextComponent<PageIndicatorContex
|
||||
}
|
||||
|
||||
async #markPageRead() {
|
||||
this.#logger.info('[PAGES] Mark page read', this.number);
|
||||
|
||||
const fileId = this._state.fileId;
|
||||
await this._viewedPagesService.add({ page: this.number }, this._state.dossierId, fileId);
|
||||
const viewedPage = new ViewedPage({ page: this.number, fileId });
|
||||
@ -94,6 +100,8 @@ export class PageIndicatorComponent extends ContextComponent<PageIndicatorContex
|
||||
}
|
||||
|
||||
async #markPageUnread() {
|
||||
this.#logger.info('[PAGES] Mark page unread', this.number);
|
||||
|
||||
const fileId = this._state.fileId;
|
||||
await this._viewedPagesService.remove(this._state.dossierId, fileId, this.number);
|
||||
this._viewedPagesMapService.delete(fileId, this.number);
|
||||
|
||||
@ -177,7 +177,6 @@ export class ManualAnnotationDialogComponent extends BaseDialogComponent impleme
|
||||
addRedactionRequest.addToDictionary = selectedType.hasDictionary;
|
||||
} else {
|
||||
addRedactionRequest.addToDictionary = this.isDictionaryRequest && addRedactionRequest.type !== 'dossier_redaction';
|
||||
addRedactionRequest.addToDossierDictionary = this.isDictionaryRequest && addRedactionRequest.type === 'dossier_redaction';
|
||||
}
|
||||
|
||||
if (!addRedactionRequest.reason) {
|
||||
|
||||
@ -42,7 +42,7 @@
|
||||
</ng-container>
|
||||
|
||||
<ng-container *deny="roles.getRss; if: dictionaryRequest || type === 'HINT'">
|
||||
<div class="iqser-input-group required w-450" *ngIf="dictionaryRequest">
|
||||
<div class="iqser-input-group required w-450">
|
||||
<label [translate]="'redact-text.dialog.content.type'"></label>
|
||||
|
||||
<mat-form-field>
|
||||
|
||||
@ -9,11 +9,7 @@ import { ActiveDossiersService } from '@services/dossiers/active-dossiers.servic
|
||||
import { LegalBasisOption } from '../manual-redaction-dialog/manual-annotation-dialog.component';
|
||||
import { DictionaryService } from '@services/entity-services/dictionary.service';
|
||||
|
||||
import {
|
||||
ManualRedactionEntryType,
|
||||
ManualRedactionEntryTypes,
|
||||
ManualRedactionEntryWrapper,
|
||||
} from '@models/file/manual-redaction-entry.wrapper';
|
||||
import { ManualRedactionEntryType, ManualRedactionEntryWrapper } from '@models/file/manual-redaction-entry.wrapper';
|
||||
import { redactTextTranslations } from '@translations/redact-text-translations';
|
||||
import { RedactTextOption, RedactTextOptions } from './redact-text-options';
|
||||
import { tap } from 'rxjs/operators';
|
||||
@ -29,6 +25,7 @@ interface RedactTextData {
|
||||
file: File;
|
||||
applyToAllDossiers: boolean;
|
||||
isApprover: boolean;
|
||||
hint: boolean;
|
||||
}
|
||||
|
||||
interface DialogResult {
|
||||
@ -59,6 +56,7 @@ export class RedactTextDialogComponent
|
||||
|
||||
readonly #translations = redactTextTranslations;
|
||||
readonly #dossier: Dossier;
|
||||
readonly #isRss = this._iqserPermissionsService.has(Roles.getRss);
|
||||
readonly hint: boolean;
|
||||
|
||||
constructor(
|
||||
@ -71,7 +69,7 @@ export class RedactTextDialogComponent
|
||||
super();
|
||||
this.#dossier = _activeDossiersService.find(this.data.dossierId);
|
||||
this.type = this.data.manualRedactionEntryWrapper.type;
|
||||
this.hint = this.type === ManualRedactionEntryTypes.HINT;
|
||||
this.hint = this.data.hint;
|
||||
this.#applyToAllDossiers = this.data.applyToAllDossiers ?? true;
|
||||
this.#manualRedactionTypeExists = this._dictionaryService.hasManualType(this.#dossier.dossierTemplateId);
|
||||
this.options = this.#options();
|
||||
@ -83,6 +81,7 @@ export class RedactTextDialogComponent
|
||||
.valueChanges.pipe(
|
||||
tap((option: DetailsRadioOption<RedactTextOption>) => {
|
||||
this.dictionaryRequest = option.value === RedactTextOptions.IN_DOSSIER;
|
||||
this.#setDictionaries();
|
||||
this.#resetValues();
|
||||
}),
|
||||
takeUntilDestroyed(),
|
||||
@ -99,19 +98,14 @@ export class RedactTextDialogComponent
|
||||
}
|
||||
|
||||
get disabled() {
|
||||
if (this.dictionaryRequest || this.hint) {
|
||||
if (this.dictionaryRequest || this.hint || this.#isRss) {
|
||||
return !this.form.get('dictionary').value;
|
||||
}
|
||||
return !this.form.get('reason').value;
|
||||
}
|
||||
|
||||
async ngOnInit(): Promise<void> {
|
||||
this.dictionaries = this._dictionaryService.getPossibleDictionaries(
|
||||
this.#dossier.dossierTemplateId,
|
||||
this.hint,
|
||||
!this.#applyToAllDossiers,
|
||||
);
|
||||
|
||||
this.#setDictionaries();
|
||||
const data = await firstValueFrom(this._justificationsService.getForDossierTemplate(this.#dossier.dossierTemplateId));
|
||||
this.legalOptions = data.map(lbm => ({
|
||||
legalBasis: lbm.reason,
|
||||
@ -122,16 +116,13 @@ export class RedactTextDialogComponent
|
||||
|
||||
this.#selectReason();
|
||||
this.#formatSelectedTextValue();
|
||||
this.#resetValues();
|
||||
}
|
||||
|
||||
extraOptionChanged(option: DetailsRadioOption<RedactTextOption>): void {
|
||||
this.#applyToAllDossiers = option.extraOption.checked;
|
||||
|
||||
this.dictionaries = this._dictionaryService.getPossibleDictionaries(
|
||||
this.#dossier.dossierTemplateId,
|
||||
this.hint,
|
||||
!this.#applyToAllDossiers,
|
||||
);
|
||||
this.#setDictionaries();
|
||||
if (this.#applyToAllDossiers && this.form.get('dictionary').value) {
|
||||
const selectedDictionaryLabel = this.form.get('dictionary').value;
|
||||
const selectedDictionary = this.dictionaries.find(d => d.type === selectedDictionaryLabel);
|
||||
@ -155,10 +146,18 @@ export class RedactTextDialogComponent
|
||||
this.dialogRef.close({
|
||||
redaction,
|
||||
dictionary: this.dictionaries.find(d => d.type === this.form.get('dictionary').value),
|
||||
applyToAllDossiers: this.dictionaryRequest ? this.#applyToAllDossiers : null,
|
||||
});
|
||||
}
|
||||
|
||||
#setDictionaries() {
|
||||
this.dictionaries = this._dictionaryService.getPossibleDictionaries(
|
||||
this.#dossier.dossierTemplateId,
|
||||
this.hint,
|
||||
!this.#applyToAllDossiers,
|
||||
this.dictionaryRequest,
|
||||
);
|
||||
}
|
||||
|
||||
#getForm(): UntypedFormGroup {
|
||||
return this._formBuilder.group({
|
||||
selectedText: this.data?.manualRedactionEntryWrapper?.manualRedactionEntry?.value,
|
||||
@ -194,12 +193,12 @@ export class RedactTextDialogComponent
|
||||
addRedactionRequest.legalBasis = legalOption.legalBasis;
|
||||
}
|
||||
|
||||
if (this._iqserPermissionsService.has(Roles.getRss)) {
|
||||
const selectedType = this.dictionaries.find(d => d.type === addRedactionRequest.type);
|
||||
const selectedType = this.dictionaries.find(d => d.type === addRedactionRequest.type);
|
||||
|
||||
if (selectedType) {
|
||||
addRedactionRequest.addToDictionary = selectedType.hasDictionary;
|
||||
} else {
|
||||
addRedactionRequest.addToDictionary = this.dictionaryRequest && addRedactionRequest.type !== 'dossier_redaction';
|
||||
addRedactionRequest.addToDossierDictionary = this.dictionaryRequest && addRedactionRequest.type === 'dossier_redaction';
|
||||
}
|
||||
|
||||
if (!addRedactionRequest.reason) {
|
||||
@ -223,7 +222,7 @@ export class RedactTextDialogComponent
|
||||
value: RedactTextOptions.ONLY_HERE,
|
||||
},
|
||||
];
|
||||
if (!this._iqserPermissionsService.has(Roles.getRss)) {
|
||||
if (!this.#isRss) {
|
||||
options.push({
|
||||
label: this.#translations[this.type].inDossier.label,
|
||||
description: this.#translations[this.type].inDossier.description,
|
||||
@ -242,8 +241,10 @@ export class RedactTextDialogComponent
|
||||
|
||||
#resetValues() {
|
||||
this.#applyToAllDossiers = this.data.applyToAllDossiers ?? true;
|
||||
this.options[1].extraOption.checked = this.#applyToAllDossiers;
|
||||
if (this.dictionaryRequest) {
|
||||
if (!this.#isRss) {
|
||||
this.options[1].extraOption.checked = this.#applyToAllDossiers;
|
||||
}
|
||||
if (this.dictionaryRequest || this.hint) {
|
||||
this.form.get('reason').setValue(null);
|
||||
this.form.get('dictionary').setValue(null);
|
||||
return;
|
||||
|
||||
@ -16,8 +16,8 @@ const FOLDER_ICON = 'red:folder';
|
||||
const REMOVE_FROM_DICT_ICON = 'red:remove-from-dict';
|
||||
|
||||
export interface RemoveRedactionPermissions {
|
||||
canRemoveOrSuggestToRemoveOnlyHere: boolean;
|
||||
canRemoveOrSuggestToRemoveFromDictionary: boolean;
|
||||
canRemoveOnlyHere: boolean;
|
||||
canRemoveFromDictionary: boolean;
|
||||
canMarkAsFalsePositive: boolean;
|
||||
}
|
||||
|
||||
@ -49,6 +49,7 @@ export class RemoveRedactionDialogComponent extends IqserDialogComponent<
|
||||
readonly options: DetailsRadioOption<RemoveRedactionOption>[];
|
||||
|
||||
form!: UntypedFormGroup;
|
||||
hint: boolean;
|
||||
|
||||
readonly #redaction: AnnotationWrapper;
|
||||
readonly #permissions: RemoveRedactionPermissions;
|
||||
@ -58,6 +59,7 @@ export class RemoveRedactionDialogComponent extends IqserDialogComponent<
|
||||
constructor(private readonly _formBuilder: FormBuilder, private readonly _permissionsService: PermissionsService) {
|
||||
super();
|
||||
this.#redaction = this.data.redaction;
|
||||
this.hint = this.#redaction.hint;
|
||||
this.#permissions = this.data.permissions;
|
||||
this.options = this.#options();
|
||||
this.form = this.#getForm();
|
||||
@ -91,20 +93,20 @@ export class RemoveRedactionDialogComponent extends IqserDialogComponent<
|
||||
|
||||
#options() {
|
||||
const options: DetailsRadioOption<RemoveRedactionOption>[] = [];
|
||||
if (this.#permissions.canRemoveOrSuggestToRemoveOnlyHere) {
|
||||
if (this.#permissions.canRemoveOnlyHere) {
|
||||
options.push({
|
||||
label: this.#translations.ONLY_HERE.label,
|
||||
description: this.#translations.ONLY_HERE.description,
|
||||
descriptionParams: { value: this.#redaction.value },
|
||||
descriptionParams: { type: this.hint ? 'hint' : 'redact', value: this.#redaction.value },
|
||||
icon: PIN_ICON,
|
||||
value: RemoveRedactionOptions.ONLY_HERE,
|
||||
});
|
||||
}
|
||||
if (this.#permissions.canRemoveOrSuggestToRemoveFromDictionary) {
|
||||
if (this.#permissions.canRemoveFromDictionary) {
|
||||
options.push({
|
||||
label: this.#translations.IN_DOSSIER.label,
|
||||
description: this.#translations.IN_DOSSIER.description,
|
||||
descriptionParams: { value: this.#redaction.value },
|
||||
descriptionParams: { type: this.hint ? 'hint' : 'redact', value: this.#redaction.value },
|
||||
icon: FOLDER_ICON,
|
||||
value: RemoveRedactionOptions.IN_DOSSIER,
|
||||
extraOption: {
|
||||
|
||||
@ -1,6 +1,9 @@
|
||||
<section class="dialog">
|
||||
<form (submit)="save()" [formGroup]="form">
|
||||
<div [translate]="'remove-redaction.dialog.title'" class="dialog-header heading-l"></div>
|
||||
<div
|
||||
[innerHTML]="'remove-redaction.dialog.title' | translate : { type: hint ? 'hint' : 'redaction' }"
|
||||
class="dialog-header heading-l"
|
||||
></div>
|
||||
|
||||
<div class="dialog-content">
|
||||
<iqser-details-radio [options]="options" formControlName="option"></iqser-details-radio>
|
||||
|
||||
@ -4,10 +4,7 @@
|
||||
<hr />
|
||||
<div class="dialog-content">
|
||||
<div *ngIf="rssData() | log as rssEntry" class="table output-data">
|
||||
<div class="table-header">Component</div>
|
||||
<div class="table-header">Value</div>
|
||||
<div class="table-header">Transformation</div>
|
||||
<div class="table-header">Annotations</div>
|
||||
<div class="table-header" *ngFor="let cell of tableHeaderCells">{{ getHeaderCellTranslation(cell) | translate }}</div>
|
||||
|
||||
<ng-container *ngFor="let entry of rssEntry.result | keyvalue : originalOrder">
|
||||
<div class="bold">{{ entry.key }}</div>
|
||||
@ -80,6 +77,13 @@
|
||||
></iqser-icon-button>
|
||||
|
||||
<div [translate]="'rss-dialog.actions.close'" class="all-caps-label cancel" mat-dialog-close></div>
|
||||
<mat-checkbox
|
||||
class="ml-auto"
|
||||
color="primary"
|
||||
(change)="toggleOpenStructuredComponentManagementDialogByDefault()"
|
||||
[checked]="openStructuredComponentManagementDialogByDefault()"
|
||||
>Display by default when opening documents</mat-checkbox
|
||||
>
|
||||
</div>
|
||||
|
||||
<iqser-circle-button (action)="close()" class="dialog-close" icon="iqser:close"></iqser-circle-button>
|
||||
|
||||
@ -63,3 +63,7 @@ ul {
|
||||
.output-data > div:nth-child(8n + 12) {
|
||||
background: var(--iqser-grey-8);
|
||||
}
|
||||
|
||||
.ml-auto {
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
@ -17,8 +17,11 @@ interface RssData {
|
||||
})
|
||||
export class RssDialogComponent extends BaseDialogComponent implements OnInit {
|
||||
readonly circleButtonTypes = CircleButtonTypes;
|
||||
|
||||
readonly rssData = signal<RssEntry | undefined>(undefined);
|
||||
readonly openStructuredComponentManagementDialogByDefault = signal(
|
||||
this.userPreferences.getOpenStructuredComponentManagementDialogByDefault(),
|
||||
);
|
||||
readonly tableHeaderCells = ['component', 'value', 'transformation-rule', 'annotation-references'] as const;
|
||||
|
||||
constructor(
|
||||
protected readonly _dialogRef: MatDialogRef<RssDialogComponent>,
|
||||
@ -34,6 +37,10 @@ export class RssDialogComponent extends BaseDialogComponent implements OnInit {
|
||||
await this.#loadData();
|
||||
}
|
||||
|
||||
getHeaderCellTranslation(cell: string) {
|
||||
return `rss-dialog.table-header.${cell}`;
|
||||
}
|
||||
|
||||
originalOrder = (): number => 0;
|
||||
|
||||
exportJSON() {
|
||||
@ -56,6 +63,14 @@ export class RssDialogComponent extends BaseDialogComponent implements OnInit {
|
||||
return this.exportJSON();
|
||||
}
|
||||
|
||||
async toggleOpenStructuredComponentManagementDialogByDefault() {
|
||||
await this.userPreferences.toggleOpenStructuredComponentManagementDialogByDefault();
|
||||
await this.userPreferences.reload();
|
||||
this.openStructuredComponentManagementDialogByDefault.set(
|
||||
this.userPreferences.getOpenStructuredComponentManagementDialogByDefault(),
|
||||
);
|
||||
}
|
||||
|
||||
async undo(originalKey: string) {
|
||||
this._loadingService.start();
|
||||
await firstValueFrom(this._rssService.revertOverride(this.data.file.dossierId, this.data.file.fileId, [originalKey]));
|
||||
|
||||
@ -25,7 +25,7 @@ import {
|
||||
Toaster,
|
||||
} from '@iqser/common-ui';
|
||||
import { MatDialog } from '@angular/material/dialog';
|
||||
import { ManualRedactionEntryWrapper } from '@models/file/manual-redaction-entry.wrapper';
|
||||
import { ManualRedactionEntryTypes, ManualRedactionEntryWrapper } from '@models/file/manual-redaction-entry.wrapper';
|
||||
import { AnnotationWrapper } from '@models/file/annotation.wrapper';
|
||||
import { AnnotationDrawService } from '../pdf-viewer/services/annotation-draw.service';
|
||||
import { AnnotationProcessingService } from './services/annotation-processing.service';
|
||||
@ -317,6 +317,8 @@ export class FilePreviewScreenComponent
|
||||
this.pdfProxyService.configureElements();
|
||||
this.#restoreOldFilters();
|
||||
document.documentElement.addEventListener('fullscreenchange', this.fullscreenListener);
|
||||
|
||||
this.#openRssDialogIfDefault();
|
||||
}
|
||||
|
||||
ngAfterViewInit() {
|
||||
@ -342,7 +344,7 @@ export class FilePreviewScreenComponent
|
||||
result.annotations.map(w => w.manualRedactionEntry).filter(e => e.positions[0].page <= file.numberOfPages),
|
||||
this.dossierId,
|
||||
this.fileId,
|
||||
result.dictionary?.label,
|
||||
{ dictionaryLabel: result.dictionary?.label },
|
||||
);
|
||||
|
||||
const addAndReload$ = add$.pipe(switchMap(() => this._filesService.reload(this.dossierId, file)));
|
||||
@ -355,13 +357,16 @@ export class FilePreviewScreenComponent
|
||||
const file = this.state.file();
|
||||
const dossierTemplate = this._dossierTemplatesService.find(this.state.dossierTemplateId);
|
||||
const isApprover = this.permissionsService.isApprover(this.state.dossier());
|
||||
const applyDictionaryUpdatesToAllDossiersByDefault = dossierTemplate.applyDictionaryUpdatesToAllDossiersByDefault;
|
||||
const hint = manualRedactionEntryWrapper.type === ManualRedactionEntryTypes.HINT;
|
||||
const ref = this._iqserDialog.openDefault(RedactTextDialogComponent, {
|
||||
data: {
|
||||
manualRedactionEntryWrapper,
|
||||
dossierId: this.dossierId,
|
||||
file,
|
||||
applyToAllDossiers: isApprover ? dossierTemplate.applyDictionaryUpdatesToAllDossiersByDefault : false,
|
||||
applyToAllDossiers: isApprover ? applyDictionaryUpdatesToAllDossiersByDefault : false,
|
||||
isApprover,
|
||||
hint,
|
||||
},
|
||||
});
|
||||
|
||||
@ -370,13 +375,10 @@ export class FilePreviewScreenComponent
|
||||
return;
|
||||
}
|
||||
|
||||
const add$ = this._manualRedactionService.addAnnotation([result.redaction], this.dossierId, this.fileId, result.dictionary?.label);
|
||||
|
||||
if (isApprover && result.applyToAllDossiers !== null) {
|
||||
const { ...body } = dossierTemplate;
|
||||
body.applyDictionaryUpdatesToAllDossiersByDefault = result.applyToAllDossiers;
|
||||
await this._dossierTemplatesService.createOrUpdate(body);
|
||||
}
|
||||
const add$ = this._manualRedactionService.addAnnotation([result.redaction], this.dossierId, this.fileId, {
|
||||
hint,
|
||||
dictionaryLabel: result.dictionary?.label,
|
||||
});
|
||||
|
||||
const addAndReload$ = add$.pipe(
|
||||
tap(() => this._documentViewer.clearSelection()),
|
||||
@ -831,4 +833,13 @@ export class FilePreviewScreenComponent
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
#openRssDialogIfDefault() {
|
||||
if (
|
||||
this.permissionsService.canViewRssDialog() &&
|
||||
this.userPreferenceService.getOpenStructuredComponentManagementDialogByDefault()
|
||||
) {
|
||||
this.openRSSView();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -123,8 +123,8 @@ export class AnnotationActionsService {
|
||||
|
||||
async removeOrSuggestRemoveRedaction(redaction: AnnotationWrapper, permissions) {
|
||||
const removePermissions: RemoveRedactionPermissions = {
|
||||
canRemoveOrSuggestToRemoveOnlyHere: permissions.canRemoveOrSuggestToRemoveOnlyHere,
|
||||
canRemoveOrSuggestToRemoveFromDictionary: permissions.canRemoveOrSuggestToRemoveFromDictionary,
|
||||
canRemoveOnlyHere: permissions.canRemoveOnlyHere,
|
||||
canRemoveFromDictionary: permissions.canRemoveFromDictionary,
|
||||
canMarkAsFalsePositive: permissions.canMarkAsFalsePositive,
|
||||
};
|
||||
const dossierTemplate = this._dossierTemplatesService.find(this._state.dossierTemplateId);
|
||||
@ -149,12 +149,6 @@ export class AnnotationActionsService {
|
||||
} else {
|
||||
this.#removeRedaction(redaction, result);
|
||||
}
|
||||
|
||||
if (isApprover && result.option.extraOption) {
|
||||
const { ...body } = dossierTemplate;
|
||||
body.applyDictionaryUpdatesToAllDossiersByDefault = result.option.extraOption.checked;
|
||||
await this._dossierTemplatesService.createOrUpdate(body);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -94,8 +94,15 @@ export class ManualRedactionService extends GenericService<IManualAddResponse> {
|
||||
return this.requestRecategorize(body, dossierId, fileId).pipe(this.#showToast('request-image-recategorization'));
|
||||
}
|
||||
|
||||
addAnnotation(requests: List<IAddRedactionRequest>, dossierId: string, fileId: string, dictionaryLabel?: string) {
|
||||
const toast = requests[0].addToDictionary ? this.#showAddToDictionaryToast(requests, dictionaryLabel) : this.#showToast('add');
|
||||
addAnnotation(
|
||||
requests: List<IAddRedactionRequest>,
|
||||
dossierId: string,
|
||||
fileId: string,
|
||||
options?: { hint?: boolean; dictionaryLabel?: string },
|
||||
) {
|
||||
const toast = requests[0].addToDictionary
|
||||
? this.#showAddToDictionaryToast(requests, options?.dictionaryLabel)
|
||||
: this.#showToast(options?.hint ? 'force-hint' : 'add');
|
||||
const canAddRedaction = this._iqaerPermissionsService.has(Roles.redactions.write);
|
||||
if (canAddRedaction) {
|
||||
return this.add(requests, dossierId, fileId).pipe(toast);
|
||||
|
||||
@ -63,15 +63,6 @@ export class PdfAnnotationActionsService {
|
||||
availableActions.push(recategorizeButton);
|
||||
}
|
||||
|
||||
// if (permissions.canRemoveOrSuggestToRemoveFromDictionary) {
|
||||
// const removeFromDictButton = this.#getButton(
|
||||
// 'remove-from-dict',
|
||||
// _('annotation-actions.remove-annotation.remove-from-dict'),
|
||||
// () => this.#annotationActionsService.removeOrSuggestRemoveAnnotation(annotations, true),
|
||||
// );
|
||||
// availableActions.push(removeFromDictButton);
|
||||
// }
|
||||
|
||||
if (permissions.canAcceptRecommendation) {
|
||||
const acceptRecommendationButton = this.#getButton('check', _('annotation-actions.accept-recommendation.label'), () =>
|
||||
this.#annotationActionsService.convertRecommendationToAnnotation(annotations),
|
||||
@ -79,27 +70,6 @@ export class PdfAnnotationActionsService {
|
||||
availableActions.push(acceptRecommendationButton);
|
||||
}
|
||||
|
||||
if (permissions.canAcceptSuggestion) {
|
||||
const acceptSuggestionButton = this.#getButton('check', _('annotation-actions.accept-suggestion.label'), () =>
|
||||
this.#annotationActionsService.acceptSuggestion(annotations),
|
||||
);
|
||||
availableActions.push(acceptSuggestionButton);
|
||||
}
|
||||
|
||||
if (permissions.canUndo) {
|
||||
const undoButton = this.#getButton('undo', _('annotation-actions.undo'), () =>
|
||||
this.#annotationActionsService.undoDirectAction(annotations),
|
||||
);
|
||||
availableActions.push(undoButton);
|
||||
}
|
||||
|
||||
// if (permissions.canMarkAsFalsePositive) {
|
||||
// const markAsFalsePositiveButton = this.#getButton('thumb-down', _('annotation-actions.remove-annotation.false-positive'), () =>
|
||||
// this.#annotationActionsService.markAsFalsePositive(annotations),
|
||||
// );
|
||||
// availableActions.push(markAsFalsePositiveButton);
|
||||
// }
|
||||
|
||||
if (permissions.canForceRedaction) {
|
||||
const forceRedactionButton = this.#getButton('thumb-up', _('annotation-actions.force-redaction.label'), () =>
|
||||
this.#annotationActionsService.forceAnnotation(annotations),
|
||||
@ -114,27 +84,11 @@ export class PdfAnnotationActionsService {
|
||||
availableActions.push(forceHintButton);
|
||||
}
|
||||
|
||||
if (permissions.canRejectSuggestion) {
|
||||
const rejectSuggestionButton = this.#getButton('close', _('annotation-actions.reject-suggestion'), () =>
|
||||
this.#annotationActionsService.rejectSuggestion(annotations),
|
||||
);
|
||||
availableActions.push(rejectSuggestionButton);
|
||||
}
|
||||
|
||||
// if (permissions.canRemoveOrSuggestToRemoveOnlyHere) {
|
||||
// const removeOrSuggestToRemoveOnlyHereButton = this.#getButton(
|
||||
// 'trash',
|
||||
// _('annotation-actions.remove-annotation.only-here'),
|
||||
// () => this.#annotationActionsService.removeOrSuggestRemoveAnnotation(annotations, false),
|
||||
// );
|
||||
// availableActions.push(removeOrSuggestToRemoveOnlyHereButton);
|
||||
// }
|
||||
|
||||
if (permissions.canRemoveOrSuggestToRemoveRedaction) {
|
||||
const removeOrSuggestToRemoveButton = this.#getButton('trash', _('annotation-actions.remove-annotation.remove-redaction'), () =>
|
||||
if (permissions.canRemoveRedaction) {
|
||||
const removeRedactionButton = this.#getButton('trash', _('annotation-actions.remove-annotation.remove-redaction'), () =>
|
||||
this.#annotationActionsService.removeOrSuggestRemoveRedaction(annotations[0], permissions),
|
||||
);
|
||||
availableActions.push(removeOrSuggestToRemoveButton);
|
||||
availableActions.push(removeRedactionButton);
|
||||
}
|
||||
|
||||
return availableActions;
|
||||
@ -158,21 +112,13 @@ export class PdfAnnotationActionsService {
|
||||
canResize: permissions.length === 1 && permissions[0].canResizeAnnotation,
|
||||
canChangeLegalBasis: permissions.reduce((acc, next) => acc && next.canChangeLegalBasis, true),
|
||||
canRecategorizeImage: permissions.reduce((acc, next) => acc && next.canRecategorizeImage, true),
|
||||
canRemoveOrSuggestToRemoveFromDictionary: permissions.reduce(
|
||||
(acc, next) => acc && next.canRemoveOrSuggestToRemoveFromDictionary,
|
||||
true,
|
||||
),
|
||||
canRemoveFromDictionary: permissions.reduce((acc, next) => acc && next.canRemoveFromDictionary, true),
|
||||
canAcceptRecommendation: permissions.reduce((acc, next) => acc && next.canAcceptRecommendation, true),
|
||||
canAcceptSuggestion: permissions.reduce((acc, next) => acc && next.canAcceptSuggestion, true),
|
||||
canUndo:
|
||||
this.#iqserPermissionsService.has(Roles.redactions.deleteManual) &&
|
||||
permissions.reduce((acc, next) => acc && next.canUndo, true),
|
||||
canMarkAsFalsePositive: permissions.reduce((acc, next) => acc && next.canMarkAsFalsePositive, true),
|
||||
canForceRedaction: permissions.reduce((acc, next) => acc && next.canForceRedaction, true),
|
||||
canForceHint: permissions.reduce((acc, next) => acc && next.canForceHint, true),
|
||||
canRejectSuggestion: permissions.reduce((acc, next) => acc && next.canRejectSuggestion, true),
|
||||
canRemoveOrSuggestToRemoveOnlyHere: permissions.reduce((acc, next) => acc && next.canRemoveOrSuggestToRemoveOnlyHere, true),
|
||||
canRemoveOrSuggestToRemoveRedaction: permissions.reduce((acc, next) => acc && next.canRemoveOrSuggestToRemoveRedaction, true),
|
||||
canRemoveOnlyHere: permissions.reduce((acc, next) => acc && next.canRemoveOnlyHere, true),
|
||||
canRemoveRedaction: permissions.reduce((acc, next) => acc && next.canRemoveRedaction, true),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@ -36,6 +36,7 @@ export class IconsModule {
|
||||
'dictionary',
|
||||
'denied',
|
||||
'disable-analysis',
|
||||
'documine-logo',
|
||||
'double-chevron-right',
|
||||
'enable-analysis',
|
||||
'enter',
|
||||
|
||||
@ -136,7 +136,7 @@ export class REDAnnotationManager {
|
||||
|
||||
#autoSelectRectangleAfterCreation() {
|
||||
this.#manager.addEventListener('annotationChanged', (annotations: Annotation[], action: string, options) => {
|
||||
this.#logger.info('[PDF] Annotations changed: ', annotations, action, options);
|
||||
this.#logger.info('[ANNOTATIONS] Annotations changed: ', annotations, action, options);
|
||||
// when a rectangle is drawn,
|
||||
// it returns one annotation with tool name 'AnnotationCreateRectangle;
|
||||
// this will auto select rectangle after drawing
|
||||
|
||||
@ -29,8 +29,9 @@ export function webViewerLoadedGuard(): CanActivateFn | ResolveFn<boolean> {
|
||||
try {
|
||||
instance = await pdf.init(inject(DOCUMENT).getElementById('viewer'));
|
||||
} catch (e) {
|
||||
logger.warn('[PDF] WebViewerGuard error: ', e, 'redirecting to', state.url);
|
||||
return router.navigateByUrl(state.url);
|
||||
const redirectUrl = state.url.split('/').slice(0, -2).join('/');
|
||||
logger.warn('[PDF] WebViewerGuard error: ', e, 'redirecting to', redirectUrl);
|
||||
return router.navigateByUrl(redirectUrl); // redirect to dissier page
|
||||
}
|
||||
|
||||
annotationManager.init(instance.Core.annotationManager);
|
||||
|
||||
@ -18,26 +18,26 @@
|
||||
formControlName="downloadFileTypes"
|
||||
></redaction-select>
|
||||
</div>
|
||||
<ng-container *allow="roles.watermarks.read">
|
||||
<p class="heading">{{ 'dossier-watermark-selector.heading' | translate }}</p>
|
||||
<ng-container *ngIf="ctx.existsWatermarks; else noWatermarks">
|
||||
<redaction-watermark-selector
|
||||
[dossierTemplateId]="dossier.dossierTemplateId"
|
||||
[isReadonly]="!canEditDossier"
|
||||
[label]="'dossier-watermark-selector.watermark' | translate"
|
||||
formControlName="watermarkId"
|
||||
>
|
||||
</redaction-watermark-selector>
|
||||
|
||||
<p class="heading">{{ 'dossier-watermark-selector.heading' | translate }}</p>
|
||||
<ng-container *ngIf="ctx.existsWatermarks; else noWatermarks">
|
||||
<redaction-watermark-selector
|
||||
[dossierTemplateId]="dossier.dossierTemplateId"
|
||||
[isReadonly]="!canEditDossier"
|
||||
[label]="'dossier-watermark-selector.watermark' | translate"
|
||||
formControlName="watermarkId"
|
||||
>
|
||||
</redaction-watermark-selector>
|
||||
|
||||
<redaction-watermark-selector
|
||||
[dossierTemplateId]="dossier.dossierTemplateId"
|
||||
[isReadonly]="!canEditDossier"
|
||||
[label]="'dossier-watermark-selector.preview' | translate"
|
||||
formControlName="previewWatermarkId"
|
||||
>
|
||||
</redaction-watermark-selector>
|
||||
<redaction-watermark-selector
|
||||
[dossierTemplateId]="dossier.dossierTemplateId"
|
||||
[isReadonly]="!canEditDossier"
|
||||
[label]="'dossier-watermark-selector.preview' | translate"
|
||||
formControlName="previewWatermarkId"
|
||||
>
|
||||
</redaction-watermark-selector>
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
|
||||
<ng-template #noWatermarks>
|
||||
<p [innerHTML]="'dossier-watermark-selector.no-watermark' | translate" class="no-watermark"></p>
|
||||
</ng-template>
|
||||
|
||||
@ -44,7 +44,7 @@ export class EditDossierTeamComponent implements EditDossierSectionInterface, On
|
||||
}
|
||||
|
||||
get changed() {
|
||||
const { owner, members, approvers } = this.form.value;
|
||||
const { owner, members, approvers } = this.form.getRawValue();
|
||||
|
||||
if (this.dossier.ownerId !== owner) {
|
||||
return true;
|
||||
@ -66,7 +66,7 @@ export class EditDossierTeamComponent implements EditDossierSectionInterface, On
|
||||
}
|
||||
this.#updateLists();
|
||||
}
|
||||
const { owner, members, approvers } = this.form.value;
|
||||
const { owner, members, approvers } = this.form.getRawValue();
|
||||
const dossier = {
|
||||
...this.dossier,
|
||||
memberIds: members,
|
||||
@ -129,7 +129,7 @@ export class EditDossierTeamComponent implements EditDossierSectionInterface, On
|
||||
const possibleMembers = this.#userService.all.filter(user => user.isUser || user.isManager);
|
||||
this.membersSelectOptions = possibleMembers
|
||||
.filter(user => this.#userService.getName(user.id).toLowerCase().includes(value.toLowerCase()))
|
||||
.filter(user => this.form.value.owner !== user.id)
|
||||
.filter(user => this.form.getRawValue().owner !== user.id)
|
||||
.map(user => user.id)
|
||||
.sort((a, b) => this.#userService.getName(a).localeCompare(this.#userService.getName(b)));
|
||||
}
|
||||
|
||||
@ -28,6 +28,7 @@ import { TranslateModule } from '@ngx-translate/core';
|
||||
import { DossiersListingActionsComponent } from './components/dossiers-listing-actions/dossiers-listing-actions.component';
|
||||
import { IqserUsersModule } from '@iqser/common-ui/lib/users';
|
||||
import { SideNavComponent, SmallChipComponent, StatusBarComponent } from '@iqser/common-ui/lib/shared';
|
||||
import { SelectComponent } from '@shared/components/select/select.component';
|
||||
|
||||
const components = [
|
||||
FileActionsComponent,
|
||||
@ -65,6 +66,7 @@ const dialogs = [EditDossierDialogComponent, AssignReviewerApproverDialogCompone
|
||||
DynamicInputComponent,
|
||||
IqserAllowDirective,
|
||||
IqserDenyDirective,
|
||||
SelectComponent,
|
||||
],
|
||||
})
|
||||
export class SharedDossiersModule {}
|
||||
|
||||
@ -39,7 +39,7 @@ export class FileDownloadBtnComponent implements OnChanges {
|
||||
}
|
||||
|
||||
async downloadFiles() {
|
||||
const ref = this._dialog.open(DownloadDialogComponent, {
|
||||
const ref = this._dialog.openDefault(DownloadDialogComponent, {
|
||||
data: { dossier: this.dossier, files: this.files },
|
||||
});
|
||||
const result = await ref.result();
|
||||
|
||||
@ -79,7 +79,7 @@ export class ExpandableFileActionsComponent implements OnChanges {
|
||||
}
|
||||
|
||||
async #downloadFiles(files: File[], dossier: Dossier) {
|
||||
const ref = this._dialog.open(DownloadDialogComponent, {
|
||||
const ref = this._dialog.openDefault(DownloadDialogComponent, {
|
||||
data: { dossier, files },
|
||||
});
|
||||
const result = await ref.result();
|
||||
|
||||
@ -1,11 +1,16 @@
|
||||
import { AfterViewInit, ChangeDetectorRef, Component, ElementRef, HostBinding, Input, TemplateRef, ViewChild } from '@angular/core';
|
||||
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
|
||||
import { MatChipListbox, MatChipSelectionChange } from '@angular/material/chips';
|
||||
import { MatChipListbox, MatChipSelectionChange, MatChipsModule } from '@angular/material/chips';
|
||||
import { StopPropagationDirective } from '@iqser/common-ui';
|
||||
import { NgForOf, NgTemplateOutlet } from '@angular/common';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
|
||||
@Component({
|
||||
selector: 'redaction-select',
|
||||
templateUrl: './select.component.html',
|
||||
styleUrls: ['./select.component.scss'],
|
||||
standalone: true,
|
||||
providers: [
|
||||
{
|
||||
provide: NG_VALUE_ACCESSOR,
|
||||
@ -13,16 +18,17 @@ import { MatChipListbox, MatChipSelectionChange } from '@angular/material/chips'
|
||||
multi: true,
|
||||
},
|
||||
],
|
||||
imports: [StopPropagationDirective, NgTemplateOutlet, TranslateModule, NgForOf, MatChipsModule, MatIconModule],
|
||||
})
|
||||
export class SelectComponent implements AfterViewInit, ControlValueAccessor {
|
||||
private _value: any[] = [];
|
||||
private _onChange: (value: any[]) => void;
|
||||
@Input() optionTemplate?: TemplateRef<{ option: any }>;
|
||||
@Input() label: string;
|
||||
@Input() options: any[];
|
||||
@Input() disabled = false;
|
||||
@Input() multiple = true;
|
||||
@ViewChild(MatChipListbox) chipList: MatChipListbox;
|
||||
private _value: any[] = [];
|
||||
private _onChange: (value: any[]) => void;
|
||||
|
||||
constructor(private readonly _changeDetector: ChangeDetectorRef, private readonly _elementRef: ElementRef) {}
|
||||
|
||||
|
||||
@ -24,7 +24,7 @@
|
||||
formControlName="downloadFileTypes"
|
||||
></redaction-select>
|
||||
|
||||
<div class="iqser-input-group required">
|
||||
<div class="iqser-input-group required" *deny="roles.getRss">
|
||||
<label translate="download-dialog.form.redaction-preview-color"></label>
|
||||
<input
|
||||
[placeholder]="'download-dialog.form.redaction-preview-color-placeholder' | translate"
|
||||
|
||||
@ -1,12 +1,18 @@
|
||||
import { Component } from '@angular/core';
|
||||
import { Component, inject } from '@angular/core';
|
||||
import { Dossier, DownloadFileType, DownloadFileTypes, File, IReportTemplate, WorkflowFileStatuses } from '@red/domain';
|
||||
import { downloadTypesForDownloadTranslations } from '@translations/download-types-translations';
|
||||
import { ReportTemplateService } from '@services/report-template.service';
|
||||
import { AbstractControl, FormBuilder } from '@angular/forms';
|
||||
import { AbstractControl, FormBuilder, ReactiveFormsModule } from '@angular/forms';
|
||||
import { DefaultColorsService } from '@services/entity-services/default-colors.service';
|
||||
import { IconButtonTypes, IqserDialogComponent } from '@iqser/common-ui';
|
||||
import { CircleButtonComponent, IconButtonComponent, IconButtonTypes, IqserDenyDirective, IqserDialogComponent } from '@iqser/common-ui';
|
||||
import { Roles } from '@users/roles';
|
||||
import { List } from '@iqser/common-ui/lib/utils';
|
||||
import { NGXLogger } from 'ngx-logger';
|
||||
import { AsyncPipe, NgIf } from '@angular/common';
|
||||
import { SelectComponent } from '@shared/components/select/select.component';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { ColorPickerModule } from 'ngx-color-picker';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
|
||||
export interface DownloadDialogData {
|
||||
readonly dossier: Dossier;
|
||||
@ -22,12 +28,28 @@ export interface DownloadDialogResult {
|
||||
@Component({
|
||||
templateUrl: './download-dialog.component.html',
|
||||
styleUrls: ['./download-dialog.component.scss'],
|
||||
standalone: true,
|
||||
imports: [
|
||||
NgIf,
|
||||
ReactiveFormsModule,
|
||||
SelectComponent,
|
||||
TranslateModule,
|
||||
IqserDenyDirective,
|
||||
ColorPickerModule,
|
||||
MatIconModule,
|
||||
IconButtonComponent,
|
||||
CircleButtonComponent,
|
||||
AsyncPipe,
|
||||
],
|
||||
})
|
||||
export class DownloadDialogComponent extends IqserDialogComponent<DownloadDialogComponent, DownloadDialogData, DownloadDialogResult> {
|
||||
readonly #logger = inject(NGXLogger);
|
||||
readonly iconButtonTypes = IconButtonTypes;
|
||||
readonly downloadTypes: { key: DownloadFileType; label: string }[] = this.#formDownloadTypes;
|
||||
readonly hasApprovedFiles: boolean;
|
||||
readonly downloadTypes: { key: DownloadFileType; label: string }[];
|
||||
readonly availableReportTypes = this.#availableReportTypes;
|
||||
readonly form = this.#getForm();
|
||||
|
||||
initialFormValue = this.form.getRawValue();
|
||||
readonly roles = Roles;
|
||||
|
||||
@ -37,6 +59,8 @@ export class DownloadDialogComponent extends IqserDialogComponent<DownloadDialog
|
||||
private readonly _formBuilder: FormBuilder,
|
||||
) {
|
||||
super();
|
||||
this.hasApprovedFiles = this.data.files.some(file => file.workflowStatus === WorkflowFileStatuses.APPROVED);
|
||||
this.downloadTypes = this.#formDownloadTypes;
|
||||
}
|
||||
|
||||
get reportTypesLength() {
|
||||
@ -52,12 +76,12 @@ export class DownloadDialogComponent extends IqserDialogComponent<DownloadDialog
|
||||
}
|
||||
|
||||
get invalidDownload() {
|
||||
return (
|
||||
!this.form.controls.reportTemplateIds.value.length &&
|
||||
!!this.form.controls.downloadFileTypes.value.length &&
|
||||
this.form.controls.downloadFileTypes.value.every(type => type === DownloadFileTypes.REDACTED) &&
|
||||
!this.data.files.some(file => file.workflowStatus === WorkflowFileStatuses.APPROVED)
|
||||
);
|
||||
const hasReportTypes = this.form.controls.reportTemplateIds.value.length;
|
||||
const downloadFileTypes = this.form.controls.downloadFileTypes.value;
|
||||
const onlyRedactedVersionSelected =
|
||||
downloadFileTypes.length && downloadFileTypes.every(type => type === DownloadFileTypes.REDACTED);
|
||||
|
||||
return !hasReportTypes || (onlyRedactedVersionSelected && !this.hasApprovedFiles);
|
||||
}
|
||||
|
||||
override get valid() {
|
||||
@ -77,7 +101,7 @@ export class DownloadDialogComponent extends IqserDialogComponent<DownloadDialog
|
||||
label: downloadTypesForDownloadTranslations[type],
|
||||
}))
|
||||
.filter(type => {
|
||||
if (!this.data.files.some(file => file.workflowStatus === WorkflowFileStatuses.APPROVED)) {
|
||||
if (!this.hasApprovedFiles) {
|
||||
return type.key !== DownloadFileTypes.REDACTED;
|
||||
}
|
||||
return true;
|
||||
@ -86,7 +110,7 @@ export class DownloadDialogComponent extends IqserDialogComponent<DownloadDialog
|
||||
|
||||
get #selectedDownloadTypes() {
|
||||
return this.data.dossier.downloadFileTypes.filter(type => {
|
||||
if (!this.data.files.some(file => file.workflowStatus === WorkflowFileStatuses.APPROVED)) {
|
||||
if (!this.hasApprovedFiles) {
|
||||
return type !== DownloadFileTypes.REDACTED;
|
||||
}
|
||||
return true;
|
||||
@ -99,18 +123,20 @@ export class DownloadDialogComponent extends IqserDialogComponent<DownloadDialog
|
||||
|
||||
reportTemplateValueMapper = (reportTemplate: IReportTemplate) => reportTemplate.templateId;
|
||||
|
||||
close() {
|
||||
override close() {
|
||||
const result: DownloadDialogResult = {
|
||||
reportTemplateIds: this.form.controls.reportTemplateIds.value,
|
||||
downloadFileTypes: this.form.controls.downloadFileTypes.value,
|
||||
redactionPreviewColor: this.form.controls.redactionPreviewColor.value,
|
||||
};
|
||||
|
||||
this.#logger.info('[DOWNLOAD] Closing with result', result);
|
||||
|
||||
if (result.reportTemplateIds.length === 0) {
|
||||
return this.dialogRef.close();
|
||||
return super.close();
|
||||
}
|
||||
|
||||
this.dialogRef.close(result);
|
||||
super.close(result);
|
||||
}
|
||||
|
||||
#hasReportTemplateOrDownloadType(control: AbstractControl) {
|
||||
|
||||
@ -17,7 +17,6 @@ import {
|
||||
IqserHelpModeModule,
|
||||
StopPropagationDirective,
|
||||
} from '@iqser/common-ui';
|
||||
import { SelectComponent } from './components/select/select.component';
|
||||
import { NavigateLastDossiersScreenDirective } from './directives/navigate-last-dossiers-screen.directive';
|
||||
import { DictionaryManagerComponent } from './components/dictionary-manager/dictionary-manager.component';
|
||||
import { MonacoEditorModule } from '@materia-ui/ngx-monaco-editor';
|
||||
@ -43,17 +42,16 @@ import { AddEditEntityComponent } from './components/add-edit-entity/add-edit-en
|
||||
import { ColorPickerModule } from 'ngx-color-picker';
|
||||
import { WatermarkSelectorComponent } from './components/dossier-watermark-selector/watermark-selector.component';
|
||||
import { OcrProgressBarComponent } from './components/ocr-progress-bar/ocr-progress-bar.component';
|
||||
import { DownloadDialogComponent } from './dialogs/download-dialog/download-dialog.component';
|
||||
import { CustomDateAdapter } from '@shared/CustomDateAdapter';
|
||||
import { IqserUsersModule } from '@iqser/common-ui/lib/users';
|
||||
import { SmallChipComponent } from '@iqser/common-ui/lib/shared';
|
||||
import { SelectComponent } from '@shared/components/select/select.component';
|
||||
|
||||
const buttons = [FileDownloadBtnComponent];
|
||||
|
||||
const components = [
|
||||
PaginationComponent,
|
||||
AnnotationIconComponent,
|
||||
SelectComponent,
|
||||
DictionaryManagerComponent,
|
||||
AssignUserDropdownComponent,
|
||||
TypeFilterComponent,
|
||||
@ -82,7 +80,7 @@ const modules = [MatConfigModule, ScrollingModule, IconsModule, FormsModule, Rea
|
||||
const deleteThisWhenAllComponentsAreStandalone = [DonutChartComponent];
|
||||
|
||||
@NgModule({
|
||||
declarations: [...components, ...utils, EditorComponent, DownloadDialogComponent],
|
||||
declarations: [...components, ...utils, EditorComponent],
|
||||
imports: [
|
||||
CommonModule,
|
||||
...modules,
|
||||
@ -99,6 +97,7 @@ const deleteThisWhenAllComponentsAreStandalone = [DonutChartComponent];
|
||||
HasScrollbarDirective,
|
||||
IqserAllowDirective,
|
||||
IqserDenyDirective,
|
||||
SelectComponent,
|
||||
],
|
||||
exports: [...modules, ...components, ...utils, ...deleteThisWhenAllComponentsAreStandalone],
|
||||
providers: [
|
||||
|
||||
@ -12,6 +12,7 @@ import { DossierDictionariesMapService } from '@services/entity-services/dossier
|
||||
import { List } from '@iqser/common-ui/lib/utils';
|
||||
|
||||
const MIN_WORD_LENGTH = 2;
|
||||
const IMAGE_TYPES = ['image', 'formula', 'ocr'];
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
@ -146,7 +147,12 @@ export class DictionaryService extends EntitiesService<IDictionary, Dictionary>
|
||||
return !!this._dictionariesMapService.get(dossierTemplateId).find(e => e.type === SuperTypes.ManualRedaction && !e.virtual);
|
||||
}
|
||||
|
||||
getPossibleDictionaries(dossierTemplateId: string, hintTypes: boolean, dossierDictionaryOnly = false): Dictionary[] {
|
||||
getPossibleDictionaries(
|
||||
dossierTemplateId: string,
|
||||
hintTypes: boolean,
|
||||
dossierDictionaryOnly = false,
|
||||
dictionaryRequest = false,
|
||||
): Dictionary[] {
|
||||
const possibleDictionaries: Dictionary[] = [];
|
||||
|
||||
this._dictionariesMapService.get(dossierTemplateId).forEach((d: Dictionary) => {
|
||||
@ -157,8 +163,17 @@ export class DictionaryService extends EntitiesService<IDictionary, Dictionary>
|
||||
) {
|
||||
possibleDictionaries.push(d);
|
||||
}
|
||||
} else if ((d.hint && d.hasDictionary && d.addToDictionaryAction) || (dossierDictionaryOnly && d.dossierDictionaryOnly)) {
|
||||
possibleDictionaries.push(d);
|
||||
} else if (
|
||||
(d.hint && d.hasDictionary && d.addToDictionaryAction && d.type) ||
|
||||
(dictionaryRequest && dossierDictionaryOnly && d.dossierDictionaryOnly)
|
||||
) {
|
||||
if (!dictionaryRequest) {
|
||||
if (!IMAGE_TYPES.includes(d.type)) {
|
||||
possibleDictionaries.push(d);
|
||||
}
|
||||
} else {
|
||||
possibleDictionaries.push(d);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { EntitiesService } from '@iqser/common-ui';
|
||||
import { Injectable, signal } from '@angular/core';
|
||||
import { computed, Injectable, signal } from '@angular/core';
|
||||
import { BehaviorSubject, Observable, of } from 'rxjs';
|
||||
import { catchError, tap } from 'rxjs/operators';
|
||||
import { FileAttributeConfig, FileAttributes, IFileAttributeConfig, IFileAttributesConfig } from '@red/domain';
|
||||
@ -12,8 +12,9 @@ export type FileAttributesConfigMap = Readonly<Record<string, IFileAttributesCon
|
||||
})
|
||||
export class FileAttributesService extends EntitiesService<IFileAttributeConfig, FileAttributeConfig> {
|
||||
readonly fileAttributesConfig$ = new BehaviorSubject<FileAttributesConfigMap>({});
|
||||
readonly isEditingFileAttribute = signal(false);
|
||||
readonly isEditingFileAttribute = computed(() => this.openAttributeEdits().length > 0);
|
||||
|
||||
readonly openAttributeEdits = signal([]);
|
||||
readonly openAttributeEdit = signal('');
|
||||
readonly fileEdit = signal('');
|
||||
|
||||
|
||||
@ -1,10 +1,11 @@
|
||||
import { inject, Injectable } from '@angular/core';
|
||||
import { GenericService, QueryParam } from '@iqser/common-ui';
|
||||
import { GenericService, IqserPermissionsService, QueryParam } from '@iqser/common-ui';
|
||||
import { IWatermark, Watermark } from '@red/domain';
|
||||
import { firstValueFrom, forkJoin, Observable } from 'rxjs';
|
||||
import { firstValueFrom, forkJoin, Observable, of } from 'rxjs';
|
||||
import { map, tap } from 'rxjs/operators';
|
||||
import { WatermarksMapService } from '@services/entity-services/watermarks-map.service';
|
||||
import { mapEach } from '@iqser/common-ui/lib/utils';
|
||||
import { Roles } from '@users/roles';
|
||||
|
||||
interface IsUsedResponse {
|
||||
value: boolean;
|
||||
@ -14,9 +15,11 @@ interface IsUsedResponse {
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class WatermarkService extends GenericService<IWatermark> {
|
||||
readonly #watermarksMapService = inject(WatermarksMapService);
|
||||
protected readonly _defaultModelPath = 'watermark';
|
||||
|
||||
readonly #watermarksMapService = inject(WatermarksMapService);
|
||||
readonly #allowed = inject(IqserPermissionsService).has(Roles.watermarks.read);
|
||||
|
||||
async deleteWatermark(dossierTemplateId: string, watermarkId: number) {
|
||||
await firstValueFrom(super.delete(null, `${this._defaultModelPath}/${watermarkId}`));
|
||||
return firstValueFrom(this.loadForDossierTemplate(dossierTemplateId));
|
||||
@ -37,6 +40,9 @@ export class WatermarkService extends GenericService<IWatermark> {
|
||||
}
|
||||
|
||||
loadAll(dossierTemplateIds: string[]): Observable<Watermark[]> {
|
||||
if (!this.#allowed) {
|
||||
return of([]);
|
||||
}
|
||||
return forkJoin(dossierTemplateIds.map(id => this.loadForDossierTemplate(id))).pipe(map(arrays => [].concat(...arrays)));
|
||||
}
|
||||
|
||||
|
||||
@ -31,7 +31,7 @@ export class FilesService extends EntitiesService<IFile, File> {
|
||||
loadAll(dossierId: string) {
|
||||
const files$ = this.getFor(dossierId).pipe(
|
||||
mapEach(file => new File(file, this._userService.getName(file.assignee))),
|
||||
tap(() => this._logger.info('[FILE] Loaded')),
|
||||
tap(file => this._logger.info('[FILE] Loaded', file)),
|
||||
);
|
||||
const loadStats$ = files$.pipe(switchMap(files => this._dossierStatsService.getFor([dossierId]).pipe(map(() => files))));
|
||||
return loadStats$.pipe(tap(files => this._filesMapService.set(dossierId, files)));
|
||||
@ -41,7 +41,7 @@ export class FilesService extends EntitiesService<IFile, File> {
|
||||
const _file = await firstValueFrom(super._getOne([dossierId, file.id]));
|
||||
const reloadedFile = new File(_file, this._userService.getName(_file.assignee));
|
||||
await firstValueFrom(this._dossierStatsService.getFor([dossierId]));
|
||||
this._logger.info('[FILE] Reloaded');
|
||||
this._logger.info('[FILE] Reloaded', reloadedFile);
|
||||
return this._filesMapService.replace(dossierId, [reloadedFile]);
|
||||
}
|
||||
|
||||
|
||||
@ -354,6 +354,10 @@ export class PermissionsService {
|
||||
return this._iqserPermissionsService.has(Roles.rules.write) && this.isAdmin();
|
||||
}
|
||||
|
||||
canViewRssDialog() {
|
||||
return this._iqserPermissionsService.has(Roles.getRss);
|
||||
}
|
||||
|
||||
#canDeleteEntity(entity: Dictionary): boolean {
|
||||
return !entity.systemManaged;
|
||||
}
|
||||
|
||||
@ -10,6 +10,7 @@ export const PreferencesKeys = {
|
||||
displaySuggestionsInPreview: 'Display-Suggestions-In-Preview',
|
||||
unapprovedSuggestionsWarning: 'Unapproved-Suggestions-Warning',
|
||||
loadAllAnnotationsWarning: 'Load-All-Annotations-Warning',
|
||||
openStructuredComponentManagementDialogByDefault: 'Open-Structured-Component-Management-By-Default',
|
||||
} as const;
|
||||
|
||||
@Injectable({
|
||||
@ -53,6 +54,15 @@ export class UserPreferenceService extends IqserUserPreferenceService {
|
||||
await this.save(PreferencesKeys.autoExpandFiltersOnActions, nextValue);
|
||||
}
|
||||
|
||||
getOpenStructuredComponentManagementDialogByDefault(): boolean {
|
||||
return this._getAttribute(PreferencesKeys.openStructuredComponentManagementDialogByDefault, 'false') === 'true';
|
||||
}
|
||||
|
||||
async toggleOpenStructuredComponentManagementDialogByDefault(): Promise<void> {
|
||||
const nextValue = (!this.getOpenStructuredComponentManagementDialogByDefault()).toString();
|
||||
await this.save(PreferencesKeys.openStructuredComponentManagementDialogByDefault, nextValue);
|
||||
}
|
||||
|
||||
getDisplaySuggestionsInPreview(): boolean {
|
||||
return this._getAttribute(PreferencesKeys.displaySuggestionsInPreview, 'false') === 'true';
|
||||
}
|
||||
|
||||
@ -1,8 +1,9 @@
|
||||
{
|
||||
"ADMIN_CONTACT_NAME": null,
|
||||
"ADMIN_CONTACT_URL": null,
|
||||
"API_URL": "https://dev-04.iqser.cloud",
|
||||
"APP_NAME": "RedactManager",
|
||||
"API_URL": "https://dan.iqser.cloud",
|
||||
"APP_NAME": "RedactManager very very very very long",
|
||||
"IS_DOCUMINE": false,
|
||||
"AUTO_READ_TIME": 3,
|
||||
"BACKEND_APP_VERSION": "4.4.40",
|
||||
"EULA_URL": "EULA_URL",
|
||||
@ -11,7 +12,7 @@
|
||||
"MAX_RETRIES_ON_SERVER_ERROR": 3,
|
||||
"OAUTH_CLIENT_ID": "redaction",
|
||||
"OAUTH_IDP_HINT": null,
|
||||
"OAUTH_URL": "https://dev-04.iqser.cloud/auth",
|
||||
"OAUTH_URL": "https://dan.iqser.cloud/auth",
|
||||
"RECENT_PERIOD_IN_HOURS": 24,
|
||||
"SELECTION_MODE": "structural",
|
||||
"MANUAL_BASE_URL": "https://docs.redactmanager.com/preview",
|
||||
|
||||
@ -368,6 +368,7 @@
|
||||
"declined-suggestion": "Abgelehnter Vorschlag",
|
||||
"hint": "Hinweis",
|
||||
"ignored-hint": "Ignorierter Hinweis",
|
||||
"manual-hint": "",
|
||||
"manual-redaction": "Manuelle Schwärzung",
|
||||
"recommendation": "Empfehlung",
|
||||
"redaction": "Schwärzung",
|
||||
@ -1642,10 +1643,6 @@
|
||||
},
|
||||
"license-info-screen": {
|
||||
"backend-version": "Backend-Version der Anwendung",
|
||||
"chart": {
|
||||
"pages-per-month": "Seiten pro Monat",
|
||||
"total-pages": "Gesamtzahl der Seiten"
|
||||
},
|
||||
"copyright-claim-text": "Copyright © 2020 - {currentYear} knecon AG (powered by IQSER)",
|
||||
"copyright-claim-title": "Copyright",
|
||||
"current-analyzed": "In aktuellem Lizenzzeitraum analysierte Seiten",
|
||||
@ -1666,6 +1663,11 @@
|
||||
"licensing-details": "Lizenzdetails",
|
||||
"licensing-period": "Laufzeit der Lizenz",
|
||||
"ocr-analyzed-pages": "Mit OCR konvertierte Seiten",
|
||||
"pages": {
|
||||
"cumulative": "Seiten insgesamt",
|
||||
"pages-per-month": "Seiten pro Monat",
|
||||
"total-pages": "Gesamtzahl der Seiten"
|
||||
},
|
||||
"status": {
|
||||
"active": "Aktiv",
|
||||
"inactive": ""
|
||||
@ -1864,6 +1866,7 @@
|
||||
"form": {
|
||||
"auto-expand-filters-on-action": "",
|
||||
"load-all-annotations-warning": "",
|
||||
"open-structured-view-by-default": "",
|
||||
"show-suggestions-in-preview": "",
|
||||
"unapproved-suggestions-warning": ""
|
||||
},
|
||||
|
||||
@ -368,6 +368,7 @@
|
||||
"declined-suggestion": "Declined Suggestion",
|
||||
"hint": "Hint",
|
||||
"ignored-hint": "Ignored Hint",
|
||||
"manual-hint": "Manual Hint",
|
||||
"manual-redaction": "Manual Redaction",
|
||||
"recommendation": "Recommendation",
|
||||
"redaction": "Redaction",
|
||||
@ -1642,10 +1643,6 @@
|
||||
},
|
||||
"license-info-screen": {
|
||||
"backend-version": "Backend Application Version",
|
||||
"chart": {
|
||||
"pages-per-month": "Pages per Month",
|
||||
"total-pages": "Total Pages"
|
||||
},
|
||||
"copyright-claim-text": "Copyright © 2020 - {currentYear} knecon AG (powered by IQSER)",
|
||||
"copyright-claim-title": "Copyright Claim",
|
||||
"current-analyzed": "Analyzed Pages in Licensing Period",
|
||||
@ -1666,6 +1663,11 @@
|
||||
"licensing-details": "Licensing Details",
|
||||
"licensing-period": "Licensing Period",
|
||||
"ocr-analyzed-pages": "OCR Processed Pages in Licensing Period",
|
||||
"pages": {
|
||||
"cumulative": "Cumulative Pages",
|
||||
"pages-per-month": "Pages per Month",
|
||||
"total-pages": "Total Pages"
|
||||
},
|
||||
"status": {
|
||||
"active": "Active",
|
||||
"inactive": "Inactive"
|
||||
@ -1864,6 +1866,7 @@
|
||||
"form": {
|
||||
"auto-expand-filters-on-action": "Auto-expand filters on my actions",
|
||||
"load-all-annotations-warning": "Warning regarding loading all annotations at once in file preview",
|
||||
"open-structured-view-by-default": "Display structured component management modal by default",
|
||||
"show-suggestions-in-preview": "Display suggestions in document preview",
|
||||
"unapproved-suggestions-warning": "Warning regarding unapproved suggestions in document Preview mode"
|
||||
},
|
||||
@ -1959,11 +1962,11 @@
|
||||
"label": "False positive"
|
||||
},
|
||||
"in-dossier": {
|
||||
"description": "Do not redact \"{value}\" in any document of the current dossier.",
|
||||
"label": "Remove in dossier"
|
||||
"description": "Do not {type} \"{value}\" in any document of the current dossier.",
|
||||
"label": "Remove from dossier"
|
||||
},
|
||||
"only-here": {
|
||||
"description": "Do not redact \"{value}\" at this position in the current document.",
|
||||
"description": "Do not {type} \"{value}\" at this position in the current document.",
|
||||
"label": "Remove here"
|
||||
},
|
||||
"redact": {
|
||||
@ -1976,7 +1979,7 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"title": "Remove redaction"
|
||||
"title": "Remove {type}"
|
||||
}
|
||||
},
|
||||
"report-type": {
|
||||
|
||||
@ -208,9 +208,6 @@
|
||||
"accept-recommendation": {
|
||||
"label": "Empfehlung annehmen"
|
||||
},
|
||||
"accept-suggestion": {
|
||||
"label": "Genehmigen und zum Wörterbuch hinzufügen"
|
||||
},
|
||||
"convert-highlights": {
|
||||
"label": ""
|
||||
},
|
||||
@ -328,7 +325,6 @@
|
||||
}
|
||||
},
|
||||
"recategorize-image": "neu kategorisieren",
|
||||
"reject-suggestion": "Vorschlag ablehnen",
|
||||
"remove-annotation": {
|
||||
"remove-redaction": ""
|
||||
},
|
||||
@ -368,6 +364,7 @@
|
||||
"declined-suggestion": "Abgelehnter Vorschlag",
|
||||
"hint": "Hinweis",
|
||||
"ignored-hint": "Ignorierter Hinweis",
|
||||
"manual-hint": "",
|
||||
"manual-redaction": "Manuelle Schwärzung",
|
||||
"recommendation": "Empfehlung",
|
||||
"redaction": "Schwärzung",
|
||||
@ -1642,10 +1639,6 @@
|
||||
},
|
||||
"license-info-screen": {
|
||||
"backend-version": "Backend-Version der Anwendung",
|
||||
"chart": {
|
||||
"pages-per-month": "Seiten pro Monat",
|
||||
"total-pages": "Gesamtzahl der Seiten"
|
||||
},
|
||||
"copyright-claim-text": "Copyright © 2020 - {currentYear} knecon AG (powered by IQSER)",
|
||||
"copyright-claim-title": "Copyright",
|
||||
"current-analyzed": "In aktuellem Lizenzzeitraum analysierte Seiten",
|
||||
@ -1666,6 +1659,11 @@
|
||||
"licensing-details": "Lizenzdetails",
|
||||
"licensing-period": "Laufzeit der Lizenz",
|
||||
"ocr-analyzed-pages": "Mit OCR konvertierte Seiten",
|
||||
"pages": {
|
||||
"cumulative": "Seiten insgesamt",
|
||||
"pages-per-month": "Seiten pro Monat",
|
||||
"total-pages": "Gesamtzahl der Seiten"
|
||||
},
|
||||
"status": {
|
||||
"active": "Aktiv",
|
||||
"inactive": ""
|
||||
@ -1864,6 +1862,7 @@
|
||||
"form": {
|
||||
"auto-expand-filters-on-action": "",
|
||||
"load-all-annotations-warning": "",
|
||||
"open-structured-view-by-default": "",
|
||||
"show-suggestions-in-preview": "",
|
||||
"unapproved-suggestions-warning": ""
|
||||
},
|
||||
@ -2075,6 +2074,12 @@
|
||||
"undo": ""
|
||||
},
|
||||
"annotations": "",
|
||||
"table-header": {
|
||||
"annotation": "",
|
||||
"component": "",
|
||||
"transformation": "",
|
||||
"value": ""
|
||||
},
|
||||
"title": ""
|
||||
},
|
||||
"rules-screen": {
|
||||
|
||||
@ -208,9 +208,6 @@
|
||||
"accept-recommendation": {
|
||||
"label": "Accept Recommendation"
|
||||
},
|
||||
"accept-suggestion": {
|
||||
"label": "Approve Suggestion"
|
||||
},
|
||||
"convert-highlights": {
|
||||
"label": "Convert Selected Earmarks"
|
||||
},
|
||||
@ -258,8 +255,8 @@
|
||||
},
|
||||
"manual-redaction": {
|
||||
"add": {
|
||||
"error": "Failed to save component: {error}",
|
||||
"success": "Component added!"
|
||||
"error": "Failed to save annotation: {error}",
|
||||
"success": "Annotation added!"
|
||||
},
|
||||
"approve": {
|
||||
"error": "Failed to approve suggestion: {error}",
|
||||
@ -290,8 +287,8 @@
|
||||
"success": "Hint removed!"
|
||||
},
|
||||
"remove": {
|
||||
"error": "Failed to remove component: {error}",
|
||||
"success": "Component removed!"
|
||||
"error": "Failed to remove annotation: {error}",
|
||||
"success": "Annotation removed!"
|
||||
},
|
||||
"request-change-legal-basis": {
|
||||
"error": "Failed to request annotation reason change: {error}",
|
||||
@ -328,7 +325,6 @@
|
||||
}
|
||||
},
|
||||
"recategorize-image": "Recategorize",
|
||||
"reject-suggestion": "Reject Suggestion",
|
||||
"remove-annotation": {
|
||||
"remove-redaction": "Remove"
|
||||
},
|
||||
@ -368,9 +364,10 @@
|
||||
"declined-suggestion": "Declined Suggestion",
|
||||
"hint": "Hint",
|
||||
"ignored-hint": "Ignored Hint",
|
||||
"manual-redaction": "Manual Component",
|
||||
"manual-hint": "Manual Hint",
|
||||
"manual-redaction": "Manual Annotation",
|
||||
"recommendation": "Recommendation",
|
||||
"redaction": "Component",
|
||||
"redaction": "Annotation",
|
||||
"skipped": "Skipped",
|
||||
"suggestion-add": "Suggested component",
|
||||
"suggestion-add-dictionary": "Suggested add to Dictionary",
|
||||
@ -1424,7 +1421,7 @@
|
||||
"text-highlights": "Earmarks",
|
||||
"text-highlights-tooltip": "Shows all text-earmarks and allows removing or importing them as components",
|
||||
"toggle-analysis": {
|
||||
"disable": "Disable component",
|
||||
"disable": "Disable extraction",
|
||||
"enable": "Enable for component",
|
||||
"only-managers": "Enabling / disabling is permitted only for managers"
|
||||
}
|
||||
@ -1642,10 +1639,6 @@
|
||||
},
|
||||
"license-info-screen": {
|
||||
"backend-version": "Backend Application Version",
|
||||
"chart": {
|
||||
"pages-per-month": "Pages per Month",
|
||||
"total-pages": "Total Pages"
|
||||
},
|
||||
"copyright-claim-text": "Copyright © 2020 - {currentYear} knecon AG (powered by IQSER)",
|
||||
"copyright-claim-title": "Copyright Claim",
|
||||
"current-analyzed": "Analyzed Pages in Licensing Period",
|
||||
@ -1666,6 +1659,11 @@
|
||||
"licensing-details": "Licensing Details",
|
||||
"licensing-period": "Licensing Period",
|
||||
"ocr-analyzed-pages": "OCR Processed Pages in Licensing Period",
|
||||
"pages": {
|
||||
"cumulative": "Cumulative Pages",
|
||||
"pages-per-month": "Pages per Month",
|
||||
"total-pages": "Total Pages"
|
||||
},
|
||||
"status": {
|
||||
"active": "Active",
|
||||
"inactive": "Inactive"
|
||||
@ -1716,7 +1714,7 @@
|
||||
"force-hint": "Force Hint",
|
||||
"force-redaction": "Force Component",
|
||||
"hint": "Add hint",
|
||||
"redact": "Redact",
|
||||
"redact": "Annotation",
|
||||
"redaction": "Redaction"
|
||||
}
|
||||
}
|
||||
@ -1724,9 +1722,9 @@
|
||||
"minutes": "minutes",
|
||||
"no-active-license": "Invalid or corrupt license – Please contact your administrator",
|
||||
"notification": {
|
||||
"assign-approver": "You have been assigned as approver for <b>{fileHref, select, null{{fileName}} other{<a href=\"{fileHref}\" target=\"_blank\">{fileName}</a>}}</b> in dossier: <b>{dossierHref, select, null{{dossierName}} other{<a href=\"{dossierHref}\" target=\"_blank\">{dossierName}</a>}}</b>!",
|
||||
"assign-approver": "You have been assigned to <b>{fileHref, select, null{{fileName}} other{<a href=\"{fileHref}\" target=\"_blank\">{fileName}</a>}}</b> in dossier: <b>{dossierHref, select, null{{dossierName}} other{<a href=\"{dossierHref}\" target=\"_blank\">{dossierName}</a>}}</b>!",
|
||||
"assign-reviewer": "You have been assigned as reviewer for <b>{fileHref, select, null{{fileName}} other{<a href=\"{fileHref}\" target=\"_blank\">{fileName}</a>}}</b> in dossier: <b>{dossierHref, select, null{{dossierName}} other{<a href=\"{dossierHref}\" target=\"_blank\">{dossierName}</a>}}</b>!",
|
||||
"document-approved": " <b>{fileHref, select, null{{fileName}} other{<a href=\"{fileHref}\" target=\"_blank\">{fileName}</a>}}</b> has been approved!",
|
||||
"document-approved": " <b>{fileHref, select, null{{fileName}} other{<a href=\"{fileHref}\" target=\"_blank\">{fileName}</a>}}</b> has been moved to Done!",
|
||||
"dossier-deleted": "Dossier: <b>{dossierName}</b> has been deleted!",
|
||||
"dossier-owner-deleted": "The owner of dossier: <b>{dossierName}</b> has been deleted!",
|
||||
"dossier-owner-removed": "You have been removed as dossier owner from <b>{dossierHref, select, null{{dossierName}} other{<a href=\"{dossierHref}\" target=\"_blank\">{dossierName}</a>}}</b>!",
|
||||
@ -1754,9 +1752,9 @@
|
||||
},
|
||||
"options-title": "Choose on which action you want to be notified",
|
||||
"options": {
|
||||
"ASSIGN_APPROVER": "When I am assigned to a document as Approver",
|
||||
"ASSIGN_APPROVER": "When I am assigned to a document",
|
||||
"ASSIGN_REVIEWER": "When I am assigned to a document as Reviewer",
|
||||
"DOCUMENT_APPROVED": "When the document status changes to Approved (only for dossier owners)",
|
||||
"DOCUMENT_APPROVED": "When the document status changes to Done (only for dossier owners)",
|
||||
"DOCUMENT_UNDER_APPROVAL": "When the document status changes to Under Approval",
|
||||
"DOCUMENT_UNDER_REVIEW": "When the document status changes to In Progress",
|
||||
"DOSSIER_DELETED": "When a dossier was deleted",
|
||||
@ -1864,6 +1862,7 @@
|
||||
"form": {
|
||||
"auto-expand-filters-on-action": "Auto expand filters on my actions",
|
||||
"load-all-annotations-warning": "Warning regarding loading all annotations at once in file preview",
|
||||
"open-structured-view-by-default": "Display structured component management modal by default",
|
||||
"show-suggestions-in-preview": "Display suggestions in document preview",
|
||||
"unapproved-suggestions-warning": "Warning regarding unapproved suggestions in document Preview mode"
|
||||
},
|
||||
@ -1959,11 +1958,11 @@
|
||||
"label": "False positive"
|
||||
},
|
||||
"in-dossier": {
|
||||
"description": "Do not redact \"{value}\" in any document of the current dossier.",
|
||||
"label": "Remove in dossier"
|
||||
"description": "Do not {type} \"{value}\" in any document of the current dossier.",
|
||||
"label": "Remove from dossier"
|
||||
},
|
||||
"only-here": {
|
||||
"description": "Do not redact \"{value}\" at this position in the current document.",
|
||||
"description": "Do not {type} \"{value}\" at this position in the current document.",
|
||||
"label": "Remove here"
|
||||
},
|
||||
"redact": {
|
||||
@ -1976,7 +1975,7 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"title": "Remove redaction"
|
||||
"title": "Remove {type}"
|
||||
}
|
||||
},
|
||||
"report-type": {
|
||||
@ -2075,6 +2074,12 @@
|
||||
"undo": "Undo to: {value}"
|
||||
},
|
||||
"annotations": "<strong>{type}</strong> found on {pageCount, plural, one{page} other{pages}} {pages} by rule #{ruleNumber}",
|
||||
"table-header": {
|
||||
"annotation-references": "Annotation references",
|
||||
"component": "Component",
|
||||
"transformation-rule": "Transformation rule",
|
||||
"value": "Value"
|
||||
},
|
||||
"title": "Structured Component Management"
|
||||
},
|
||||
"rules-screen": {
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 105 KiB After Width: | Height: | Size: 6.9 KiB |
19
apps/red-ui/src/assets/icons/general/documine-logo.svg
Normal file
19
apps/red-ui/src/assets/icons/general/documine-logo.svg
Normal file
@ -0,0 +1,19 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" id="Layer_1" version="1.1" x="0px" xml:space="preserve" y="0px" viewBox="398.3 453.2 132.2 117.1">
|
||||
<style type="text/css">
|
||||
.st0 {
|
||||
opacity: 0.5;
|
||||
fill: #504FDC;
|
||||
}
|
||||
|
||||
.st1 {
|
||||
fill: #504FDC;
|
||||
}
|
||||
</style>
|
||||
<g>
|
||||
<polygon class="st0" points="441.3,453.2 441.3,496.8 398.3,496.8 "/>
|
||||
<path class="st1" d="M530.5,475.7c0,1.2-0.5,2.4-1.3,3.2c-0.8,0.8-1.9,1.3-3.2,1.3h-5.8c-2.5,0-4.5-2-4.5-4.5 c0-1.2,0.5-2.4,1.3-3.2c0.8-0.8,1.9-1.3,3.2-1.3h5.8C528.5,471.2,530.5,473.3,530.5,475.7z"/>
|
||||
<path class="st1" d="M512.1,489.2h-32.2c-2.5,0-4.5-2-4.5-4.5c0-1.2,0.5-2.4,1.3-3.2c0.8-0.8,1.9-1.3,3.2-1.3h22.4 c1.2,0,2.4-0.5,3.2-1.3c0.8-0.8,1.3-1.9,1.3-3.2c0-2.5-2-4.5-4.5-4.5h-14.4c-2.5,0-4.5-2-4.5-4.5c0-1.2,0.5-2.4,1.3-3.2 c0.8-0.8,1.9-1.3,3.2-1.3h9.4c1.2,0,2.4-0.5,3.2-1.3c0.8-0.8,1.3-1.9,1.3-3.2c0-2.5-2-4.5-4.5-4.5h-56v43.6h-43v73.5h90 c1.2,0,2.4-0.5,3.2-1.3c0.8-0.8,1.3-1.9,1.3-3.2c0-2.5-2-4.5-4.5-4.5h-14.8l0,0h-0.6c-2.5,0-4.5-2-4.5-4.5c0-1.2,0.5-2.4,1.3-3.2 c0.8-0.8,1.9-1.3,3.2-1.3h14.4c1.2,0,2.4-0.5,3.2-1.3c0.8-0.8,1.3-1.9,1.3-3.2c0-2.5-2-4.5-4.5-4.5h-30.4c-2.5,0-4.5-2-4.5-4.5 c0-1.2,0.5-2.4,1.3-3.2c0.8-0.8,1.9-1.3,3.2-1.3h46.4c1.2,0,2.4-0.5,3.2-1.3c0.8-0.8,1.3-1.9,1.3-3.2c0-2.5-2-4.5-4.5-4.5h-38.4 c-2.5,0-4.5-2-4.5-4.5c0-1.2,0.5-2.4,1.3-3.2c0.8-0.8,1.9-1.3,3.2-1.3h22.4c1.2,0,2.4-0.5,3.2-1.3c0.8-0.8,1.3-1.9,1.3-3.2 c0-2.5-2-4.5-4.5-4.5h-14.4c-2.5,0-4.5-2-4.5-4.5c0-1.2,0.5-2.4,1.3-3.2c0.8-0.8,1.9-1.3,3.2-1.3h39.2c1.2,0,2.4-0.5,3.2-1.3 c0.8-0.8,1.3-1.9,1.3-3.2C516.6,491.3,514.5,489.2,512.1,489.2z M455.3,552.2c2.5,0,4.5,2,4.5,4.5c0,1.2-0.5,2.4-1.3,3.2 c-0.8,0.8-1.9,1.3-3.2,1.3h-5.8c-2.5,0-4.5-2-4.5-4.5c0-1.2,0.5-2.4,1.3-3.2c0.8-0.8,1.9-1.3,3.2-1.3H455.3z"/>
|
||||
<path class="st1" d="M516.6,565.8c0,1.2-0.5,2.4-1.3,3.2c-0.8,0.8-1.9,1.3-3.2,1.3h-5.8c-2.5,0-4.5-2-4.5-4.5 c0-1.2,0.5-2.4,1.3-3.2c0.8-0.8,1.9-1.3,3.2-1.3h5.8C514.5,561.3,516.6,563.3,516.6,565.8z"/>
|
||||
<path class="st1" d="M514.2,514.9c-0.8,0.8-1.9,1.3-3.2,1.3h-5.8c-2.5,0-4.5-2-4.5-4.5c0-1.2,0.5-2.4,1.3-3.2 c0.8-0.8,1.9-1.3,3.2-1.3h5.8c2.5,0,4.5,2,4.5,4.5C515.6,513,515.1,514.1,514.2,514.9z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.1 KiB |
@ -4,6 +4,7 @@ ADMIN_CONTACT_NAME="${ADMIN_CONTACT_NAME:-}"
|
||||
ADMIN_CONTACT_URL="${ADMIN_CONTACT_URL:-}"
|
||||
API_URL="${API_URL:-}"
|
||||
APP_NAME="${APP_NAME:-}"
|
||||
IS_DOCUMINE="${IS_DOCUMINE:-}"
|
||||
AUTO_READ_TIME="${AUTO_READ_TIME:-1.5}"
|
||||
BACKEND_APP_VERSION="${BACKEND_APP_VERSION:-4.7.0}"
|
||||
EULA_URL="${EULA_URL:-}"
|
||||
@ -25,13 +26,12 @@ BASE_TRANSLATIONS_DIRECTORY="${BASE_TRANSLATIONS_DIRECTORY:-/assets/i18n/redact/
|
||||
THEME="${THEME:-theme-template}"
|
||||
WATERMARK_PREVIEW_PAPER_FORMAT="${WATERMARK_PREVIEW_PAPER_FORMAT:-a4}"
|
||||
|
||||
|
||||
|
||||
echo '{
|
||||
"ADMIN_CONTACT_NAME":"'"$ADMIN_CONTACT_NAME"'",
|
||||
"ADMIN_CONTACT_URL":"'"$ADMIN_CONTACT_URL"'",
|
||||
"API_URL":"'"$API_URL"'",
|
||||
"APP_NAME":"'"$APP_NAME"'",
|
||||
"IS_DOCUMINE":"'"$IS_DOCUMINE"'",
|
||||
"AUTO_READ_TIME":"'"$AUTO_READ_TIME"'",
|
||||
"BACKEND_APP_VERSION":"'"$BACKEND_APP_VERSION"'",
|
||||
"EULA_URL":"'"$EULA_URL:"'",
|
||||
@ -51,7 +51,7 @@ echo '{
|
||||
"AVAILABLE_OLD_NOTIFICATIONS_MINUTES":"'"$AVAILABLE_OLD_NOTIFICATIONS_MINUTES"'",
|
||||
"NOTIFICATIONS_THRESHOLD":"'"$NOTIFICATIONS_THRESHOLD"'",
|
||||
"WATERMARK_PREVIEW_PAPER_FORMAT":"'"$WATERMARK_PREVIEW_PAPER_FORMAT"'"
|
||||
}' > /usr/share/nginx/html/ui/assets/config/config.json
|
||||
}' >/usr/share/nginx/html/ui/assets/config/config.json
|
||||
|
||||
echo 'Env variables: '
|
||||
cat /usr/share/nginx/html/ui/assets/config/config.json
|
||||
|
||||
@ -1 +1 @@
|
||||
Subproject commit a8f5fb2e25cd1f150c1099d387b4f9dece3b922c
|
||||
Subproject commit e69f8b3f0df8701bbc3dbc79e2239aaad3acc889
|
||||
@ -4,7 +4,6 @@ import { DictionaryEntryType } from './dictionary-entry-types';
|
||||
|
||||
export interface IAddRedactionRequest {
|
||||
addToDictionary?: boolean;
|
||||
addToDossierDictionary?: boolean;
|
||||
dictionaryEntryType?: DictionaryEntryType;
|
||||
comment?: { text: string };
|
||||
legalBasis?: string;
|
||||
|
||||
@ -3,7 +3,6 @@ import { LogEntryStatus } from './types';
|
||||
|
||||
export interface IManualRedactionEntry {
|
||||
addToDictionary?: boolean;
|
||||
addToDossierDictionary?: boolean;
|
||||
annotationId?: string;
|
||||
fileId?: string;
|
||||
legalBasis?: string;
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { IqserAppConfig } from '@iqser/common-ui/lib/utils';
|
||||
import { IqserAppConfig } from '@common-ui/utils';
|
||||
|
||||
export interface AppConfig extends IqserAppConfig {
|
||||
readonly ADMIN_CONTACT_NAME: string;
|
||||
|
||||
6
renovate.json
Normal file
6
renovate.json
Normal file
@ -0,0 +1,6 @@
|
||||
{
|
||||
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
||||
"extends": [
|
||||
"config:base"
|
||||
]
|
||||
}
|
||||
@ -24,6 +24,7 @@
|
||||
"@i18n/*": ["apps/red-ui/src/app/i18n/*"],
|
||||
"@iqser/common-ui": ["libs/common-ui/src/index.ts"],
|
||||
"@iqser/common-ui/*": ["libs/common-ui/src/*"],
|
||||
"@common-ui/*": ["libs/common-ui/src/lib/*"],
|
||||
"@models/*": ["apps/red-ui/src/app/models/*"],
|
||||
"@red/domain": ["libs/red-domain/src/index.ts"],
|
||||
"@services/*": ["apps/red-ui/src/app/services/*"],
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user