Merge branch 'master' into VM/RED-3332

This commit is contained in:
Valentin Mihai 2022-02-08 17:24:00 +02:00
commit 35d3d58aa9
62 changed files with 583 additions and 570 deletions

View File

@ -31,7 +31,14 @@ import { SpotlightSearchComponent } from '@components/spotlight-search/spotlight
import { PruningTranslationLoader } from '@utils/pruning-translation-loader';
import { DatePipe } from '@shared/pipes/date.pipe';
import * as links from '../assets/help-mode/links.json';
import { HELP_DOCS, IqserHelpModeModule, MAX_RETRIES_ON_SERVER_ERROR, ServerErrorInterceptor, ToastComponent } from '@iqser/common-ui';
import {
HELP_DOCS,
IqserHelpModeModule,
MANUAL_BASE_URL,
MAX_RETRIES_ON_SERVER_ERROR,
ServerErrorInterceptor,
ToastComponent,
} from '@iqser/common-ui';
import { KeycloakService } from 'keycloak-angular';
import { GeneralSettingsService } from '@services/general-settings.service';
import { BreadcrumbsComponent } from '@components/breadcrumbs/breadcrumbs.component';
@ -127,6 +134,11 @@ const components = [AppComponent, AuthErrorComponent, NotificationsComponent, Sp
provide: HELP_DOCS,
useValue: links,
},
{
provide: MANUAL_BASE_URL,
useFactory: (configService: ConfigService) => configService.values.MANUAL_BASE_URL,
deps: [ConfigService],
},
{
provide: MAX_RETRIES_ON_SERVER_ERROR,
useFactory: (configService: ConfigService) => configService.values.MAX_RETRIES_ON_SERVER_ERROR,

View File

@ -16,19 +16,16 @@ import { RedactionLogEntry } from './redaction-log.entry';
export class FileDataModel {
static readonly DELTA_VIEW_TIME = 10 * 60 * 1000; // 10 minutes;
allAnnotations: AnnotationWrapper[];
allAnnotations: AnnotationWrapper[] = [];
readonly hasChangeLog$ = new BehaviorSubject<boolean>(false);
readonly blob$ = new BehaviorSubject<Blob>(undefined);
constructor(
private readonly _file: File,
private readonly _blob: Blob,
private _redactionLog: IRedactionLog,
public viewedPages?: IViewedPage[],
private _dictionaryData?: { [p: string]: Dictionary },
private _areDevFeaturesEnabled?: boolean,
) {
this.blob$.next(_blob);
this._buildAllAnnotations();
}
@ -56,7 +53,7 @@ export class FileDataModel {
private _buildAllAnnotations() {
const entries: RedactionLogEntry[] = this._convertData();
const previousAnnotations = this.allAnnotations || [];
const previousAnnotations = [...this.allAnnotations];
this.allAnnotations = entries
.map(entry => AnnotationWrapper.fromData(entry))
.filter(ann => ann.manual || !this._file.excludedPages.includes(ann.pageNumber));

View File

@ -28,21 +28,16 @@ export class RedactionLogEntry implements IRedactionLogEntry {
readonly textBefore?: string;
readonly type?: string;
readonly value?: string;
readonly changeLogType?: 'ADDED' | 'REMOVED' | 'CHANGED';
readonly isChangeLogEntry?: boolean;
readonly hidden?: boolean;
readonly legalBasisList: ILegalBasis[];
readonly hintDictionary: boolean;
reason?: string;
constructor(
redactionLogEntry: IRedactionLogEntry,
changeLogType: 'ADDED' | 'REMOVED' | 'CHANGED',
isChangeLogEntry: boolean,
hidden: boolean,
hintDictionary: boolean,
legalBasisList: ILegalBasis[],
readonly changeLogType: 'ADDED' | 'REMOVED' | 'CHANGED',
readonly isChangeLogEntry: boolean,
readonly hidden: boolean,
readonly hintDictionary: boolean,
readonly legalBasisList: ILegalBasis[],
) {
this.changes = redactionLogEntry.changes;
this.manualChanges = redactionLogEntry.manualChanges;
@ -72,10 +67,5 @@ export class RedactionLogEntry implements IRedactionLogEntry {
this.textBefore = redactionLogEntry.textBefore;
this.type = redactionLogEntry.type;
this.value = redactionLogEntry.value;
this.changeLogType = changeLogType;
this.isChangeLogEntry = isChangeLogEntry;
this.hidden = hidden;
this.hintDictionary = hintDictionary;
this.legalBasisList = legalBasisList;
}
}

View File

@ -1,5 +1,5 @@
@use 'apps/red-ui/src/assets/styles/variables';
@use 'libs/common-ui/src/assets/styles/common-mixins';
@use 'variables';
@use 'common-mixins';
.content-container {
background-color: variables.$grey-2;

View File

@ -1,5 +1,5 @@
@use 'variables';
@use 'libs/common-ui/src/assets/styles/common-mixins';
@use 'common-mixins';
.dialog-content {
flex-direction: column;

View File

@ -6,16 +6,13 @@ import { AppStateGuard } from '@state/app-state.guard';
import { DictionaryListingScreenComponent } from './screens/dictionary-listing/dictionary-listing-screen.component';
import { DictionaryOverviewScreenComponent } from './screens/dictionary-overview/dictionary-overview-screen.component';
import { PendingChangesGuard } from '@guards/can-deactivate.guard';
import { RulesScreenComponent } from './screens/rules/rules-screen.component';
import { FileAttributesListingScreenComponent } from './screens/file-attributes-listing/file-attributes-listing-screen.component';
import { WatermarkScreenComponent } from './screens/watermark/watermark-screen.component';
import { DefaultColorsScreenComponent } from './screens/default-colors/default-colors-screen.component';
import { UserListingScreenComponent } from './screens/user-listing/user-listing-screen.component';
import { LicenseInformationScreenComponent } from './screens/license-information/license-information-screen.component';
import { DigitalSignatureScreenComponent } from './screens/digital-signature/digital-signature-screen.component';
import { AuditScreenComponent } from './screens/audit/audit-screen.component';
import { RouterModule, Routes } from '@angular/router';
import { ReportsScreenComponent } from './screens/reports/reports-screen.component';
import { DossierAttributesListingScreenComponent } from './screens/dossier-attributes-listing/dossier-attributes-listing-screen.component';
import { TrashScreenComponent } from './screens/trash/trash-screen.component';
import { GeneralConfigScreenComponent } from './screens/general-config/general-config-screen.component';
@ -72,12 +69,13 @@ const routes: Routes = [
},
{
path: 'rules',
component: RulesScreenComponent,
component: BaseDossierTemplateScreenComponent,
canActivate: [CompositeRouteGuard],
canDeactivate: [PendingChangesGuard],
data: {
routeGuards: [AuthGuard, RedRoleGuard, AppStateGuard],
},
loadChildren: () => import('./screens/rules/rules.module').then(m => m.RulesModule),
},
{
path: 'file-attributes',
@ -89,19 +87,21 @@ const routes: Routes = [
},
{
path: 'watermark',
component: WatermarkScreenComponent,
component: BaseDossierTemplateScreenComponent,
canActivate: [CompositeRouteGuard],
data: {
routeGuards: [AuthGuard, RedRoleGuard, AppStateGuard],
},
loadChildren: () => import('./screens/watermark/watermark.module').then(m => m.WatermarkModule),
},
{
path: 'reports',
component: ReportsScreenComponent,
component: BaseDossierTemplateScreenComponent,
canActivate: [CompositeRouteGuard],
data: {
routeGuards: [AuthGuard, RedRoleGuard, AppStateGuard],
},
loadChildren: () => import('./screens/reports/reports.module').then(m => m.ReportsModule),
},
{
path: 'dossier-attributes',

View File

@ -1,7 +1,6 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { AdminRoutingModule } from './admin-routing.module';
import { RulesScreenComponent } from './screens/rules/rules-screen.component';
import { SharedModule } from '@shared/shared.module';
import { AuditScreenComponent } from './screens/audit/audit-screen.component';
import { DefaultColorsScreenComponent } from './screens/default-colors/default-colors-screen.component';
@ -11,7 +10,6 @@ import { DigitalSignatureScreenComponent } from './screens/digital-signature/dig
import { FileAttributesListingScreenComponent } from './screens/file-attributes-listing/file-attributes-listing-screen.component';
import { LicenseInformationScreenComponent } from './screens/license-information/license-information-screen.component';
import { UserListingScreenComponent } from './screens/user-listing/user-listing-screen.component';
import { WatermarkScreenComponent } from './screens/watermark/watermark-screen.component';
import { DossierTemplateBreadcrumbsComponent } from './components/dossier-template-breadcrumbs/dossier-template-breadcrumbs.component';
import { ColorPickerModule } from 'ngx-color-picker';
import { AddEditFileAttributeDialogComponent } from './dialogs/add-edit-file-attribute-dialog/add-edit-file-attribute-dialog.component';
@ -31,7 +29,6 @@ import { FileAttributesCsvImportDialogComponent } from './dialogs/file-attribute
import { ActiveFieldsListingComponent } from './dialogs/file-attributes-csv-import-dialog/active-fields-listing/active-fields-listing.component';
import { AdminSideNavComponent } from './admin-side-nav/admin-side-nav.component';
import { MonacoEditorModule } from '@materia-ui/ngx-monaco-editor';
import { ReportsScreenComponent } from './screens/reports/reports-screen.component';
import { ResetPasswordComponent } from './dialogs/add-edit-user-dialog/reset-password/reset-password.component';
import { UserDetailsComponent } from './dialogs/add-edit-user-dialog/user-details/user-details.component';
import { AddEditDossierAttributeDialogComponent } from './dialogs/add-edit-dossier-attribute-dialog/add-edit-dossier-attribute-dialog.component';
@ -66,7 +63,6 @@ const dialogs = [
];
const screens = [
RulesScreenComponent,
AuditScreenComponent,
DefaultColorsScreenComponent,
DictionaryListingScreenComponent,
@ -75,9 +71,7 @@ const screens = [
FileAttributesListingScreenComponent,
LicenseInformationScreenComponent,
UserListingScreenComponent,
WatermarkScreenComponent,
GeneralConfigScreenComponent,
ReportsScreenComponent,
DossierAttributesListingScreenComponent,
TrashScreenComponent,
];

View File

@ -55,7 +55,7 @@ export class DefaultColorsScreenComponent extends ListingComponent<ListItem> imp
await this._loadColors();
}
openEditColorDialog($event: any, color: { key: DefaultColorType | string; value: string }) {
openEditColorDialog($event: MouseEvent, color: { key: DefaultColorType | string; value: string }) {
this._dialogService.openDialog(
'editColor',
$event,

View File

@ -1,6 +1,6 @@
<iqser-page-header
(closeAction)="routerHistoryService.navigateToLastDossiersScreen()"
[pageLabel]="'dossier-templates' | translate"
[pageLabel]="'dossier-templates.label' | translate"
[showCloseButton]="currentUser.isUser"
></iqser-page-header>

View File

@ -16,7 +16,7 @@
<div class="action-buttons">
<iqser-circle-button
(action)="openEditJustificationDialog()"
*ngIf="userService.currentUser.isAdmin"
*ngIf="userPreferenceService.areDevFeaturesEnabled && userService.currentUser.isAdmin"
[tooltip]="'justifications-listing.actions.edit' | translate"
[type]="circleButtonTypes.dark"
icon="iqser:edit"

View File

@ -1,88 +0,0 @@
<section>
<div class="page-header">
<redaction-dossier-template-breadcrumbs class="flex-1"></redaction-dossier-template-breadcrumbs>
<div class="actions flex-1">
<redaction-dossier-template-actions></redaction-dossier-template-actions>
<iqser-circle-button
[routerLink]="['../..']"
[tooltip]="'common.close' | translate"
icon="iqser:close"
tooltipPosition="below"
></iqser-circle-button>
</div>
</div>
<div class="content-inner">
<div class="overlay-shadow"></div>
<redaction-admin-side-nav type="dossierTemplates"></redaction-admin-side-nav>
<div class="content-container" iqserHasScrollbar>
<div class="heading-xl" translate="reports-screen.title"></div>
<div class="description" translate="reports-screen.description"></div>
<div class="document-setup">
<div class="all-caps-label" translate="reports-screen.document-setup-heading"></div>
<div translate="reports-screen.document-setup-description"></div>
</div>
<div class="placeholders">
<div class="all-caps-label" translate="reports-screen.table-header.placeholders"></div>
<div class="all-caps-label" translate="reports-screen.table-header.description"></div>
<ng-container *ngFor="let placeholder of placeholders">
<div class="placeholder">{{ placeholder.placeholder }}</div>
<div
[innerHTML]="placeholder.descriptionTranslation | translate: { attribute: placeholder.attributeName }"
class="description"
></div>
</ng-container>
</div>
</div>
<div class="right-container" iqserHasScrollbar>
<div class="header">
<div class="heading" translate="reports-screen.report-documents"></div>
<iqser-circle-button
(action)="fileInput.click()"
*ngIf="permissionsService.isAdmin()"
[tooltip]="'reports-screen.upload-document' | translate"
icon="iqser:upload"
></iqser-circle-button>
</div>
<div
(click)="fileInput.click()"
*ngIf="permissionsService.isAdmin() && !availableTemplates?.length"
class="template upload-button"
translate="reports-screen.upload-document"
></div>
<div *ngFor="let template of availableTemplates" class="template">
<div class="name">
{{ template.fileName }} {{ template.multiFileReport ? ('reports-screen.multi-file-report' | translate) : '' }}
</div>
<div class="actions">
<iqser-circle-button
(action)="download(template)"
[iconSize]="12"
[size]="18"
icon="iqser:download"
></iqser-circle-button>
<iqser-circle-button
(action)="deleteTemplate(template)"
*ngIf="permissionsService.isAdmin()"
[iconSize]="12"
[size]="18"
icon="iqser:trash"
></iqser-circle-button>
</div>
</div>
</div>
</div>
</section>
<input #fileInput (change)="uploadTemplate($event)" hidden type="file" />

View File

@ -0,0 +1,60 @@
<div class="content-container" iqserHasScrollbar>
<div class="heading-xl" translate="reports-screen.title"></div>
<div class="description" translate="reports-screen.description"></div>
<div class="document-setup">
<div class="all-caps-label" translate="reports-screen.document-setup-heading"></div>
<div translate="reports-screen.document-setup-description"></div>
</div>
<div *ngIf="placeholders$ | async as placeholders" class="placeholders">
<div class="all-caps-label" translate="reports-screen.table-header.placeholders"></div>
<div class="all-caps-label" translate="reports-screen.table-header.description"></div>
<ng-container *ngFor="let placeholder of placeholders">
<div class="placeholder">{{ placeholder.placeholder }}</div>
<div
[innerHTML]="placeholder.descriptionTranslation | translate: { attribute: placeholder.attributeName }"
class="description"
></div>
</ng-container>
</div>
</div>
<div *ngIf="availableTemplates$ | async as availableTemplates" class="right-container" iqserHasScrollbar>
<div class="header">
<div class="heading" translate="reports-screen.report-documents"></div>
<iqser-circle-button
(action)="fileInput.click()"
*ngIf="permissionsService.isAdmin()"
[tooltip]="'reports-screen.upload-document' | translate"
icon="iqser:upload"
></iqser-circle-button>
</div>
<div
(click)="fileInput.click()"
*ngIf="permissionsService.isAdmin() && !availableTemplates?.length"
class="template upload-button"
translate="reports-screen.upload-document"
></div>
<div *ngFor="let template of availableTemplates" class="template">
<div class="name">
{{ template.fileName }} {{ template.multiFileReport ? ('reports-screen.multi-file-report' | translate) : '' }}
</div>
<div class="actions">
<iqser-circle-button (action)="download(template)" [iconSize]="12" [size]="18" icon="iqser:download"></iqser-circle-button>
<iqser-circle-button
(action)="deleteTemplate(template)"
*ngIf="permissionsService.isAdmin()"
[iconSize]="12"
[size]="18"
icon="iqser:trash"
></iqser-circle-button>
</div>
</div>
</div>
<input #fileInput (change)="uploadTemplate($event)" hidden type="file" />

View File

@ -1,6 +1,12 @@
@use 'variables';
@use 'common-mixins';
:host {
flex-grow: 1;
overflow: hidden;
display: flex;
}
.content-container,
.right-container {
flex: 1;

View File

@ -1,4 +1,4 @@
import { Component, ElementRef, OnInit, ViewChild } from '@angular/core';
import { ChangeDetectionStrategy, Component, ElementRef, OnInit, ViewChild } from '@angular/core';
import { IPlaceholdersResponse, IReportTemplate } from '@red/domain';
import { download } from '@utils/file-download-utils';
import { ConfirmationDialogInput, LoadingService, Toaster } from '@iqser/common-ui';
@ -6,13 +6,13 @@ import { PermissionsService } from '@services/permissions.service';
import {
generalPlaceholdersDescriptionsTranslations,
placeholdersDescriptionsTranslations,
} from '../../translations/placeholders-descriptions-translations';
} from '../../../translations/placeholders-descriptions-translations';
import { removeBraces } from '@utils/functions';
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
import { AdminDialogService } from '../../services/admin-dialog.service';
import { AdminDialogService } from '../../../services/admin-dialog.service';
import { DossierTemplatesService } from '@services/entity-services/dossier-templates.service';
import { ReportTemplateService } from '@services/report-template.service';
import { firstValueFrom } from 'rxjs';
import { BehaviorSubject, firstValueFrom } from 'rxjs';
interface Placeholder {
placeholder: string;
@ -27,10 +27,11 @@ const placeholderTypes: PlaceholderType[] = ['generalPlaceholders', 'fileAttribu
selector: 'redaction-reports-screen',
templateUrl: './reports-screen.component.html',
styleUrls: ['./reports-screen.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ReportsScreenComponent implements OnInit {
placeholders: Placeholder[];
availableTemplates: IReportTemplate[];
placeholders$ = new BehaviorSubject<Placeholder[]>([]);
availableTemplates$ = new BehaviorSubject<IReportTemplate[]>([]);
@ViewChild('fileInput') private _fileInput: ElementRef;
@ -85,7 +86,7 @@ export class ReportsScreenComponent implements OnInit {
}
private async _uploadTemplate($event) {
const file = $event.target.files[0];
const file: File = $event.target.files[0];
if (!this._isValidFile(file)) {
this._toaster.error(_('reports-screen.invalid-upload'));
@ -94,7 +95,7 @@ export class ReportsScreenComponent implements OnInit {
const dossierTemplateId = this._dossierTemplatesService.activeDossierTemplateId;
if (this.availableTemplates.some(template => template.fileName === file.name)) {
if (this.availableTemplates$.value.some(template => template.fileName === file.name)) {
const data = new ConfirmationDialogInput({
title: _('confirmation-dialog.report-template-same-name.title'),
question: _('confirmation-dialog.report-template-same-name.question'),
@ -147,8 +148,10 @@ export class ReportsScreenComponent implements OnInit {
}
private async _loadReportTemplates() {
this.availableTemplates = await firstValueFrom(
this._reportTemplateService.getAvailableReportTemplates(this._dossierTemplatesService.activeDossierTemplateId),
this.availableTemplates$.next(
await firstValueFrom(
this._reportTemplateService.getAvailableReportTemplates(this._dossierTemplatesService.activeDossierTemplateId),
),
);
}
@ -156,12 +159,14 @@ export class ReportsScreenComponent implements OnInit {
const placeholdersResponse: IPlaceholdersResponse = await firstValueFrom(
this._reportTemplateService.getAvailablePlaceholders(this._dossierTemplatesService.activeDossierTemplateId),
);
this.placeholders = placeholderTypes.flatMap(type =>
placeholdersResponse[type].map(placeholder => ({
placeholder,
descriptionTranslation: this._getPlaceholderDescriptionTranslation(type, placeholder),
attributeName: this._getAttributeName(placeholder),
})),
this.placeholders$.next(
placeholderTypes.flatMap(type =>
placeholdersResponse[type].map(placeholder => ({
placeholder,
descriptionTranslation: this._getPlaceholderDescriptionTranslation(type, placeholder),
attributeName: this._getAttributeName(placeholder),
})),
),
);
}

View File

@ -0,0 +1,13 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { RouterModule } from '@angular/router';
import { SharedModule } from '../../../shared/shared.module';
import { ReportsScreenComponent } from './reports-screen/reports-screen.component';
const routes = [{ path: '', component: ReportsScreenComponent }];
@NgModule({
declarations: [ReportsScreenComponent],
imports: [RouterModule.forChild(routes), CommonModule, SharedModule],
})
export class ReportsModule {}

View File

@ -1,35 +0,0 @@
<section>
<div class="page-header">
<redaction-dossier-template-breadcrumbs class="flex-1"></redaction-dossier-template-breadcrumbs>
<div class="flex-1 actions">
<redaction-dossier-template-actions></redaction-dossier-template-actions>
<iqser-circle-button
[routerLink]="['../..']"
[tooltip]="'common.close' | translate"
icon="iqser:close"
tooltipPosition="below"
></iqser-circle-button>
</div>
</div>
<div class="content-inner">
<div class="overlay-shadow"></div>
<redaction-admin-side-nav type="dossierTemplates"></redaction-admin-side-nav>
<div class="editor-container">
<ngx-monaco-editor (init)="onCodeEditorInit($event)" [(ngModel)]="codeEditorText" [options]="editorOptions"></ngx-monaco-editor>
</div>
<div *ngIf="changed && permissionsService.isAdmin() && !isLeaving" class="changes-box">
<iqser-icon-button
(action)="save()"
[label]="'rules-screen.save-changes' | translate"
[type]="iconButtonTypes.primary"
icon="iqser:check"
></iqser-icon-button>
<div (click)="revert()" class="all-caps-label cancel" translate="rules-screen.revert-changes"></div>
</div>
</div>
</section>

View File

@ -1,10 +0,0 @@
.editor-container {
width: 100%;
padding-top: 15px;
padding-left: 15px;
}
ngx-monaco-editor {
height: 100%;
width: 100%;
}

View File

@ -0,0 +1,11 @@
<ngx-monaco-editor (init)="onCodeEditorInit($event)" [(ngModel)]="codeEditorText" [options]="editorOptions"></ngx-monaco-editor>
<div *ngIf="changed && permissionsService.isAdmin() && !isLeaving" class="changes-box">
<iqser-icon-button
(action)="save()"
[label]="'rules-screen.save-changes' | translate"
[type]="iconButtonTypes.primary"
icon="iqser:check"
></iqser-icon-button>
<div (click)="revert()" class="all-caps-label cancel" translate="rules-screen.revert-changes"></div>
</div>

View File

@ -0,0 +1,10 @@
:host {
flex-grow: 1;
overflow: hidden;
padding: 15px 0 0 15px;
}
ngx-monaco-editor {
height: 100%;
width: 100%;
}

View File

@ -5,7 +5,7 @@ import { TranslateService } from '@ngx-translate/core';
import { saveAs } from 'file-saver';
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
import { DossierTemplatesService } from '@services/entity-services/dossier-templates.service';
import { RulesService } from '../../services/rules.service';
import { RulesService } from '../../../services/rules.service';
import { firstValueFrom } from 'rxjs';
import ICodeEditor = monaco.editor.ICodeEditor;
import IModelDeltaDecoration = monaco.editor.IModelDeltaDecoration;

View File

@ -0,0 +1,14 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { RouterModule } from '@angular/router';
import { SharedModule } from '../../../shared/shared.module';
import { RulesScreenComponent } from './rules-screen/rules-screen.component';
import { MonacoEditorModule } from '@materia-ui/ngx-monaco-editor';
const routes = [{ path: '', component: RulesScreenComponent }];
@NgModule({
declarations: [RulesScreenComponent],
imports: [RouterModule.forChild(routes), CommonModule, SharedModule, MonacoEditorModule],
})
export class RulesModule {}

View File

@ -1,127 +0,0 @@
<section>
<div class="page-header">
<redaction-dossier-template-breadcrumbs class="flex-1"></redaction-dossier-template-breadcrumbs>
<div class="actions flex-1">
<redaction-dossier-template-actions></redaction-dossier-template-actions>
<iqser-circle-button
[routerLink]="['../..']"
[tooltip]="'common.close' | translate"
icon="iqser:close"
tooltipPosition="below"
></iqser-circle-button>
</div>
</div>
<div class="content-inner">
<div class="overlay-shadow"></div>
<redaction-admin-side-nav type="dossierTemplates"></redaction-admin-side-nav>
<div class="content-container">
<div #viewer class="viewer"></div>
<div *ngIf="changed && permissionsService.isAdmin()" class="changes-box">
<iqser-icon-button
(action)="save()"
[disabled]="form.invalid"
[label]="'watermark-screen.action.save' | translate"
[type]="iconButtonTypes.primary"
icon="iqser:check"
></iqser-icon-button>
<div (click)="revert()" class="all-caps-label cancel" translate="watermark-screen.action.revert"></div>
</div>
</div>
<div class="right-container" iqserHasScrollbar>
<div class="heading-xl" translate="watermark-screen.title"></div>
<form (keyup)="configChanged()" [formGroup]="form">
<div class="iqser-input-group w-300">
<textarea
(mousemove)="triggerChanges()"
[placeholder]="'watermark-screen.form.text-placeholder' | translate"
class="w-full"
formControlName="text"
iqserHasScrollbar
name="text"
rows="4"
type="text"
></textarea>
</div>
<div class="iqser-input-group">
<label class="all-caps-label mb-8" translate="watermark-screen.form.orientation"></label>
<div class="square-options">
<div
(click)="setValue('orientation', option)"
*ngFor="let option of ['VERTICAL', 'HORIZONTAL', 'DIAGONAL']"
[class.active]="form.get('orientation').value === option"
[class.disabled]="form.get('orientation').disabled"
[ngClass]="option"
>
<span>ABC</span>
</div>
</div>
</div>
<div class="iqser-input-group">
<label class="all-caps-label" translate="watermark-screen.form.font-size"></label>
<mat-slider (change)="configChanged()" color="primary" formControlName="fontSize" max="50" min="5"></mat-slider>
</div>
<div class="iqser-input-group">
<label class="all-caps-label" translate="watermark-screen.form.opacity"></label>
<mat-slider (change)="configChanged()" color="primary" formControlName="opacity" min="1"></mat-slider>
</div>
<div class="iqser-input-group w-150">
<label class="all-caps-label mb-5" translate="watermark-screen.form.color"></label>
<input
[placeholder]="'add-edit-dictionary.form.color-placeholder' | translate"
class="hex-color-input"
formControlName="hexColor"
name="hexColor"
type="text"
/>
<div
(colorPickerChange)="setValue('hexColor', $event)"
[class.disabled]="form.get('hexColor').disabled"
[colorPicker]="form.get('hexColor').value"
[cpDisabled]="form.get('hexColor').disabled"
[cpOutputFormat]="'hex'"
[cpPosition]="'top-right'"
[cpUseRootViewContainer]="true"
[style.background]="form.get('hexColor').value"
class="input-icon"
>
<mat-icon
*ngIf="!form.get('hexColor')?.value || form.get('hexColor').value?.length === 0"
svgIcon="red:color-picker"
></mat-icon>
</div>
</div>
<div class="iqser-input-group">
<label class="all-caps-label mb-8" translate="watermark-screen.form.font-type"></label>
<div class="square-options">
<div
(click)="setValue('fontType', option.value)"
*ngFor="
let option of [
{ value: 'times-new-roman', display: 'Times' },
{ value: 'helvetica', display: 'Helvetica' },
{ value: 'courier', display: 'Courier' }
]
"
[class.active]="form.get('fontType').value === option.value"
[class.disabled]="form.get('fontType').disabled"
[ngClass]="option.value"
>
{{ option.display }}
</div>
</div>
</div>
</form>
</div>
</div>
</section>

View File

@ -0,0 +1,104 @@
<div class="content-container">
<div #viewer class="viewer"></div>
<div *ngIf="changed && permissionsService.isAdmin()" class="changes-box">
<iqser-icon-button
(action)="save()"
[disabled]="form.invalid"
[label]="'watermark-screen.action.save' | translate"
[type]="iconButtonTypes.primary"
icon="iqser:check"
></iqser-icon-button>
<div (click)="revert()" class="all-caps-label cancel" translate="watermark-screen.action.revert"></div>
</div>
</div>
<div class="right-container" iqserHasScrollbar>
<div class="heading-xl" translate="watermark-screen.title"></div>
<form (keyup)="configChanged()" [formGroup]="form">
<div class="iqser-input-group w-300">
<textarea
(mousemove)="triggerChanges()"
[placeholder]="'watermark-screen.form.text-placeholder' | translate"
class="w-full"
formControlName="text"
iqserHasScrollbar
name="text"
rows="4"
type="text"
></textarea>
</div>
<div class="iqser-input-group">
<label class="all-caps-label mb-8" translate="watermark-screen.form.orientation"></label>
<div class="square-options">
<div
(click)="setValue('orientation', option)"
*ngFor="let option of ['VERTICAL', 'HORIZONTAL', 'DIAGONAL']"
[class.active]="form.get('orientation').value === option"
[class.disabled]="form.get('orientation').disabled"
[ngClass]="option"
>
<span>ABC</span>
</div>
</div>
</div>
<div class="iqser-input-group">
<label class="all-caps-label" translate="watermark-screen.form.font-size"></label>
<mat-slider (change)="configChanged()" color="primary" formControlName="fontSize" max="50" min="5"></mat-slider>
</div>
<div class="iqser-input-group">
<label class="all-caps-label" translate="watermark-screen.form.opacity"></label>
<mat-slider (change)="configChanged()" color="primary" formControlName="opacity" min="1"></mat-slider>
</div>
<div class="iqser-input-group w-150">
<label class="all-caps-label mb-5" translate="watermark-screen.form.color"></label>
<input
[placeholder]="'add-edit-dictionary.form.color-placeholder' | translate"
class="hex-color-input"
formControlName="hexColor"
name="hexColor"
type="text"
/>
<div
(colorPickerChange)="setValue('hexColor', $event)"
[class.disabled]="form.get('hexColor').disabled"
[colorPicker]="form.get('hexColor').value"
[cpDisabled]="form.get('hexColor').disabled"
[cpOutputFormat]="'hex'"
[cpPosition]="'top-right'"
[cpUseRootViewContainer]="true"
[style.background]="form.get('hexColor').value"
class="input-icon"
>
<mat-icon
*ngIf="!form.get('hexColor')?.value || form.get('hexColor').value?.length === 0"
svgIcon="red:color-picker"
></mat-icon>
</div>
</div>
<div class="iqser-input-group">
<label class="all-caps-label mb-8" translate="watermark-screen.form.font-type"></label>
<div class="square-options">
<div
(click)="setValue('fontType', option.value)"
*ngFor="
let option of [
{ value: 'times-new-roman', display: 'Times' },
{ value: 'helvetica', display: 'Helvetica' },
{ value: 'courier', display: 'Courier' }
]
"
[class.active]="form.get('fontType').value === option.value"
[class.disabled]="form.get('fontType').disabled"
[ngClass]="option.value"
>
{{ option.display }}
</div>
</div>
</div>
</form>
</div>

View File

@ -1,5 +1,11 @@
@use 'variables';
:host {
display: flex;
flex-grow: 1;
overflow: hidden;
}
.content-container {
order: 1;

View File

@ -6,7 +6,7 @@ import { HttpClient } from '@angular/common/http';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { Debounce, IconButtonTypes, LoadingService, Toaster } from '@iqser/common-ui';
import { IWatermark, WatermarkOrientation, WatermarkOrientations } from '@red/domain';
import { BASE_HREF } from '../../../../tokens';
import { BASE_HREF } from '../../../../../tokens';
import { stampPDFPage } from '@utils/page-stamper';
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
import { DossierTemplatesService } from '@services/entity-services/dossier-templates.service';

View File

@ -0,0 +1,14 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { RouterModule } from '@angular/router';
import { SharedModule } from '@shared/shared.module';
import { WatermarkScreenComponent } from './watermark-screen/watermark-screen.component';
import { ColorPickerModule } from 'ngx-color-picker';
const routes = [{ path: '', component: WatermarkScreenComponent }];
@NgModule({
declarations: [WatermarkScreenComponent],
imports: [RouterModule.forChild(routes), CommonModule, SharedModule, ColorPickerModule],
})
export class WatermarkModule {}

View File

@ -57,7 +57,7 @@ export class EditDossierDictionaryComponent implements EditDossierSectionInterfa
async ngOnInit() {
this._loadingService.start();
this.canEdit = this._permissionsService.isDossierMember(this.dossier);
this.canEdit = this._permissionsService.canEditDossier(this.dossier);
await this._updateDossierDictionary();
this.form = this._getForm();
this._loadingService.stop();

View File

@ -15,7 +15,7 @@
<redaction-team-members
(remove)="toggleSelected($event)"
[canAdd]="false"
[canRemove]="!disabled"
[canRemove]="hasOwner"
[dossierId]="dossier.dossierId"
[largeSpacing]="true"
[memberIds]="selectedApproversList"
@ -29,7 +29,7 @@
<redaction-team-members
(remove)="toggleSelected($event)"
[canAdd]="false"
[canRemove]="!disabled"
[canRemove]="hasOwner"
[dossierId]="dossier.dossierId"
[largeSpacing]="true"
[memberIds]="selectedReviewers$ | async"
@ -42,7 +42,7 @@
<div class="info mt-4">{{ 'assign-dossier-owner.dialog.no-reviewers' | translate }}</div>
</ng-container>
<ng-container *ngIf="!disabled">
<ng-container *ngIf="hasOwner">
<iqser-input-with-action
(valueChange)="setMembersSelectOptions($event)"
[(value)]="searchQuery"

View File

@ -1,5 +1,5 @@
@use 'libs/common-ui/src/assets/styles/common-mixins';
@use 'apps/red-ui/src/assets/styles/variables';
@use 'common-mixins';
@use 'variables';
.search-container {
margin-top: 16px;

View File

@ -54,6 +54,10 @@ export class EditDossierTeamComponent extends AutoUnsubscribe implements EditDos
return !this.userService.currentUser.isManager || !this.form.get('owner').value;
}
get hasOwner() {
return !!this.dossier.ownerId;
}
get changed() {
if (this.dossier.ownerId !== this.selectedOwnerId) {
return true;
@ -72,6 +76,14 @@ export class EditDossierTeamComponent extends AutoUnsubscribe implements EditDos
}
async save(): EditDossierSaveResult {
if (!this.hasOwner) {
const owner = this.form.get('owner').value;
if (!this.isApprover(owner)) {
this.toggleApprover(owner);
}
this._updateLists();
}
const dossier = {
...this.dossier,
memberIds: this.selectedMembersList,
@ -160,11 +172,13 @@ export class EditDossierTeamComponent extends AutoUnsubscribe implements EditDos
members: [[...this.dossier?.memberIds]],
});
this.addSubscription = this.form.get('owner').valueChanges.subscribe(owner => {
if (!this.isApprover(owner)) {
this.toggleApprover(owner);
if (this.hasOwner) {
if (!this.isApprover(owner)) {
this.toggleApprover(owner);
}
// If it is an approver, it is already a member, no need to check
this._updateLists();
}
// If it is an approver, it is already a member, no need to check
this._updateLists();
});
this._updateLists();
}

View File

@ -40,7 +40,7 @@ export class FileWorkloadComponent {
}
get redactionColor() {
return this._getDictionaryColor('redaction');
return this._getDictionaryColor('dossier_redaction');
}
private _getDictionaryColor(type: string) {

View File

@ -1,4 +1,4 @@
@use 'apps/red-ui/src/assets/styles/variables';
@use 'variables';
.view-mode-selection {
border-right: 1px solid variables.$separator;

View File

@ -23,6 +23,6 @@ export class DossierWorkloadColumnComponent {
}
get redactionColor() {
return this._appStateService.getDictionaryColor('redaction', this.dossier.dossierTemplateId);
return this._appStateService.getDictionaryColor('dossier_redaction', this.dossier.dossierTemplateId);
}
}

View File

@ -1,4 +1,4 @@
@use 'apps/red-ui/src/assets/styles/variables';
@use 'variables';
.annotation-actions {
display: none;

View File

@ -8,7 +8,6 @@ import { UserService } from '@services/user.service';
import { AnnotationReferencesService } from '../../services/annotation-references.service';
import { MultiSelectService } from '../../services/multi-select.service';
import { FilePreviewStateService } from '../../services/file-preview-state.service';
import { firstValueFrom } from 'rxjs';
export const AnnotationButtonTypes = {
dark: 'dark',
@ -34,9 +33,9 @@ export class AnnotationActionsComponent implements OnChanges {
constructor(
private readonly _userService: UserService,
readonly multiSelectService: MultiSelectService,
private readonly _state: FilePreviewStateService,
private readonly _permissionsService: PermissionsService,
readonly annotationActionsService: AnnotationActionsService,
private readonly _screenStateService: FilePreviewStateService,
readonly annotationReferencesService: AnnotationReferencesService,
) {}
@ -115,7 +114,7 @@ export class AnnotationActionsComponent implements OnChanges {
}
private async _setPermissions() {
const dossier = await firstValueFrom(this._screenStateService.dossier$);
const dossier = await this._state.dossier;
this.annotationPermissions = AnnotationPermissions.forUser(
this._permissionsService.isApprover(dossier),
this._userService.currentUser,

View File

@ -1,8 +1,14 @@
<div *ngIf="hasChangesToShow" [matTooltip]="changesTooltip" class="chip" matTooltipClass="multiline" matTooltipPosition="above">
<div
*ngIf="hasChangesToShow && (!isSelected || (multiSelectService.inactive$ | async))"
[matTooltip]="changesTooltip"
class="chip"
matTooltipClass="multiline"
matTooltipPosition="above"
>
<mat-icon [svgIcon]="'red:redaction-changes'"></mat-icon>
</div>
<ng-container *ngIf="hasEnginesToShow && !isSelected">
<ng-container *ngIf="hasEnginesToShow && (!isSelected || (multiSelectService.inactive$ | async))">
<div #trigger="cdkOverlayOrigin" (mouseout)="isPopoverOpen = false" (mouseover)="isPopoverOpen = true" cdkOverlayOrigin class="chip">
<ng-container *ngFor="let engine of engines">
<mat-icon *ngIf="engine.show" [svgIcon]="engine.icon"></mat-icon>

View File

@ -3,6 +3,7 @@ import { AnnotationWrapper } from '@models/file/annotation.wrapper';
import { TranslateService } from '@ngx-translate/core';
import { annotationChangesTranslations } from '../../../../../../translations/annotation-changes-translations';
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
import { MultiSelectService } from '../../services/multi-select.service';
interface Engine {
readonly icon: string;
@ -35,7 +36,7 @@ export class AnnotationDetailsComponent implements OnChanges {
hasChangesToShow: boolean;
changesTooltip: string;
constructor(private readonly _translateService: TranslateService) {}
constructor(private readonly _translateService: TranslateService, readonly multiSelectService: MultiSelectService) {}
ngOnChanges(changes: SimpleChanges) {
if (changes.annotation) {

View File

@ -4,6 +4,8 @@ import { FilterService, HelpModeService, IqserEventTarget } from '@iqser/common-
import { MultiSelectService } from '../../services/multi-select.service';
import { AnnotationReferencesService } from '../../services/annotation-references.service';
import { ViewModeService } from '../../services/view-mode.service';
import { FilePreviewStateService } from '../../services/file-preview-state.service';
import { firstValueFrom } from 'rxjs';
@Component({
selector: 'redaction-annotations-list',
@ -16,7 +18,6 @@ export class AnnotationsListComponent implements OnChanges {
@Input() selectedAnnotations: AnnotationWrapper[];
@Input() annotationActionsTemplate: TemplateRef<unknown>;
@Input() activeViewerPage: number;
@Input() canMultiSelect = true;
@Output() readonly pagesPanelActive = new EventEmitter<boolean>();
@Output() readonly selectAnnotations = new EventEmitter<AnnotationWrapper[]>();
@ -28,6 +29,7 @@ export class AnnotationsListComponent implements OnChanges {
readonly helpModeService: HelpModeService,
readonly annotationReferencesService: AnnotationReferencesService,
private readonly _filterService: FilterService,
private readonly _state: FilePreviewStateService,
) {}
ngOnChanges(changes: SimpleChanges): void {
@ -36,7 +38,7 @@ export class AnnotationsListComponent implements OnChanges {
}
}
annotationClicked(annotation: AnnotationWrapper, $event: MouseEvent): void {
async annotationClicked(annotation: AnnotationWrapper, $event: MouseEvent): Promise<void> {
if (($event?.target as IqserEventTarget)?.localName === 'input') {
return;
}
@ -50,15 +52,16 @@ export class AnnotationsListComponent implements OnChanges {
if (this.isSelected(annotation.annotationId)) {
this.deselectAnnotations.emit([annotation]);
} else {
if (this.canMultiSelect && ($event?.ctrlKey || $event?.metaKey) && this.selectedAnnotations.length > 0) {
const canMultiSelect = await firstValueFrom(this._state.isWritable$);
if (canMultiSelect && ($event?.ctrlKey || $event?.metaKey) && this.selectedAnnotations.length > 0) {
this.multiSelectService.activate();
}
this.selectAnnotations.emit([annotation]);
}
}
referenceClicked(annotation: AnnotationWrapper): void {
this.annotationClicked(annotation, null);
async referenceClicked(annotation: AnnotationWrapper): Promise<void> {
await this.annotationClicked(annotation, null);
if (this._filterService.filtersEnabled('primaryFilters')) {
this._filterService.toggleFilter('primaryFilters', annotation.superType, true);
}

View File

@ -1,5 +1,5 @@
@use 'apps/red-ui/src/assets/styles/variables';
@use 'libs/common-ui/src/assets/styles/common-mixins';
@use 'variables';
@use 'common-mixins';
:host {
display: block;

View File

@ -14,7 +14,7 @@
<div>
<div
(click)="multiSelectService.activate()"
*ngIf="!isReadOnly && (multiSelectInactive$ | async)"
*ngIf="(multiSelectInactive$ | async) && state.isWritable$ | async"
class="all-caps-label primary pointer"
iqserHelpMode="bulk-select-annotations"
translate="file-preview.tabs.annotations.select"
@ -31,7 +31,7 @@
</ng-template>
<div class="right-content">
<div *ngIf="isReadOnly" class="justify-center banner read-only d-flex">
<div *ngIf="state.isReadonly$ | async" class="justify-center banner read-only d-flex">
<div class="flex-center">
<mat-icon class="primary-white" svgIcon="red:read-only"></mat-icon>
<span class="read-only-text" translate="readonly"></span>
@ -59,7 +59,7 @@
*ngIf="selectedAnnotations?.length > 0"
[alwaysVisible]="true"
[annotations]="selectedAnnotations"
[canPerformAnnotationActions]="!isReadOnly"
[canPerformAnnotationActions]="state.isWritable$ | async"
[viewer]="viewer"
buttonType="primary"
tooltipPosition="above"
@ -73,7 +73,7 @@
></iqser-circle-button>
</div>
<div [class.lower-height]="(multiSelectActive$ | async) || isReadOnly" class="annotations-wrapper">
<div [class.lower-height]="(multiSelectActive$ | async) || (state.isReadonly$ | async)" class="annotations-wrapper">
<div
#quickNavigation
(keydown)="preventKeyDefault($event)"
@ -206,7 +206,6 @@
[activeViewerPage]="activeViewerPage"
[annotationActionsTemplate]="annotationActionsTemplate"
[annotations]="(displayedAnnotations$ | async)?.get(activeViewerPage)"
[canMultiSelect]="!isReadOnly"
[selectedAnnotations]="selectedAnnotations"
iqserHelpMode="workload-annotations-list"
></redaction-annotations-list>

View File

@ -1,5 +1,5 @@
@use 'apps/red-ui/src/assets/styles/variables';
@use 'libs/common-ui/src/assets/styles/common-mixins';
@use 'variables';
@use 'common-mixins';
.banner {
padding: 13px 16px;

View File

@ -33,6 +33,7 @@ import { ExcludedPagesService } from '../../services/excluded-pages.service';
import { MultiSelectService } from '../../services/multi-select.service';
import { DocumentInfoService } from '../../services/document-info.service';
import { SkippedService } from '../../services/skipped.service';
import { FilePreviewStateService } from '../../services/file-preview-state.service';
const COMMAND_KEY_ARRAY = ['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown', 'Escape'];
const ALL_HOTKEY_ARRAY = ['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown'];
@ -75,6 +76,7 @@ export class FileWorkloadComponent {
readonly multiSelectService: MultiSelectService,
readonly documentInfoService: DocumentInfoService,
readonly skippedService: SkippedService,
readonly state: FilePreviewStateService,
private readonly _permissionsService: PermissionsService,
private readonly _changeDetectorRef: ChangeDetectorRef,
private readonly _filterService: FilterService,
@ -95,10 +97,6 @@ export class FileWorkloadComponent {
return this.displayedAnnotations.get(this.activeViewerPage);
}
get isReadOnly(): boolean {
return !this._permissionsService.canPerformAnnotationActions(this.file);
}
get currentPageIsExcluded(): boolean {
return this.file?.excludedPages?.includes(this.activeViewerPage);
}
@ -252,8 +250,8 @@ export class FileWorkloadComponent {
this.selectPage.emit(1);
}
scrollQuickNavLast(): void {
this.selectPage.emit(this.file.numberOfPages);
scrollQuickNavLast(): Promise<void> {
return this.state.file.then(file => this.selectPage.emit(file.numberOfPages));
}
pageSelectedByClick($event: number): void {

View File

@ -1,5 +1,5 @@
@use 'apps/red-ui/src/assets/styles/variables';
@use 'libs/common-ui/src/assets/styles/common-mixins';
@use 'variables';
@use 'common-mixins';
:host {
height: 100%;

View File

@ -1,4 +1,4 @@
@use 'apps/red-ui/src/assets/styles/variables';
@use 'variables';
.page-wrapper {
color: variables.$accent;

View File

@ -1,4 +1,4 @@
@use 'apps/red-ui/src/assets/styles/variables';
@use 'variables';
.page {
display: flex;

View File

@ -27,7 +27,7 @@ import { AnnotationActionsService } from '../../services/annotation-actions.serv
import { UserPreferenceService } from '@services/user-preference.service';
import { BASE_HREF } from '../../../../../../tokens';
import { ConfigService } from '@services/config.service';
import { AutoUnsubscribe, ConfirmationDialogInput, LoadingService, shareDistinctLast } from '@iqser/common-ui';
import { AutoUnsubscribe, ConfirmationDialogInput, LoadingService } from '@iqser/common-ui';
import { DossiersDialogService } from '../../../../services/dossiers-dialog.service';
import { loadCompareDocumentWrapper } from '../../../../utils/compare-mode.utils';
import { PdfViewerUtils } from '../../../../utils/pdf-viewer.utils';
@ -37,7 +37,7 @@ import { toPosition } from '../../../../utils/pdf-calculation.utils';
import { ViewModeService } from '../../services/view-mode.service';
import { MultiSelectService } from '../../services/multi-select.service';
import { FilePreviewStateService } from '../../services/file-preview-state.service';
import { filter, switchMap, tap, withLatestFrom } from 'rxjs/operators';
import { tap, withLatestFrom } from 'rxjs/operators';
import Tools = Core.Tools;
import TextTool = Tools.TextTool;
import Annotation = Core.Annotations.Annotation;
@ -120,12 +120,8 @@ export class PdfViewerComponent extends AutoUnsubscribe implements OnInit, OnCha
this._setReadyAndInitialState = this._setReadyAndInitialState.bind(this);
await this._loadViewer();
this.addActiveScreenSubscription = this.stateService.fileData$
this.addActiveScreenSubscription = this.stateService.blob$
.pipe(
filter(fileData => !!fileData),
switchMap(fileData => fileData.blob$),
// Skip document reload if file content hasn't changed
shareDistinctLast(),
withLatestFrom(this.stateService.file$),
tap(([blob, file]) => this._loadDocument(blob, file)),
)
@ -158,7 +154,8 @@ export class PdfViewerComponent extends AutoUnsubscribe implements OnInit, OnCha
await pdfNet.initialize(environment.licenseKey ? atob(environment.licenseKey) : null);
const compareDocument = await pdfNet.PDFDoc.createFromBuffer(fileReader.result as ArrayBuffer);
const currentDocument = await pdfNet.PDFDoc.createFromBuffer(await this.stateService.fileData.blob$.value.arrayBuffer());
const blob = await this.stateService.blob;
const currentDocument = await pdfNet.PDFDoc.createFromBuffer(await blob.arrayBuffer());
const loadCompareDocument = async () => {
this._loadingService.start();
@ -212,7 +209,8 @@ export class PdfViewerComponent extends AutoUnsubscribe implements OnInit, OnCha
this.viewModeService.compareMode = false;
const pdfNet = this.instance.Core.PDFNet;
await pdfNet.initialize(environment.licenseKey ? atob(environment.licenseKey) : null);
const currentDocument = await pdfNet.PDFDoc.createFromBuffer(await this.stateService.fileData.blob$.value.arrayBuffer());
const blob = await this.stateService.blob;
const currentDocument = await pdfNet.PDFDoc.createFromBuffer(await blob.arrayBuffer());
const filename = (await this.stateService.file).filename ?? 'document.pdf';
this.instance.UI.loadDocument(currentDocument, { filename });

View File

@ -1,135 +1,137 @@
<ng-container *ngIf="stateService.dossier$ | async as dossier">
<ng-container *ngIf="stateService.file$ | async as file">
<section [class.fullscreen]="fullScreen">
<div class="page-header">
<div class="flex flex-1">
<redaction-view-switch (switchView)="switchView($event)"></redaction-view-switch>
<section *ngIf="stateService.file$ | async as file" [class.fullscreen]="fullScreen">
<div class="page-header">
<div class="flex flex-1">
<redaction-view-switch (switchView)="switchView($event)"></redaction-view-switch>
</div>
<div class="flex-1 actions-container">
<div
*ngIf="file.isProcessing"
class="spinning-icon mr-16"
matTooltip="{{ 'file-status.processing' | translate }}"
matTooltipPosition="above"
>
<mat-icon svgIcon="red:reanalyse"></mat-icon>
</div>
<div class="flex-1 actions-container">
<div
*ngIf="file.isProcessing"
class="spinning-icon mr-16"
matTooltip="{{ 'file-status.processing' | translate }}"
matTooltipPosition="above"
>
<mat-icon svgIcon="red:reanalyse"></mat-icon>
</div>
<redaction-user-management></redaction-user-management>
<ng-container *ngIf="permissionsService.isApprover(dossier) && !!file.lastReviewer">
<div class="vertical-line"></div>
<div class="all-caps-label mr-16 ml-8" translate="file-preview.last-reviewer"></div>
<redaction-initials-avatar [user]="file.lastReviewer" [withName]="true"></redaction-initials-avatar>
</ng-container>
<redaction-user-management></redaction-user-management>
<ng-container *ngIf="permissionsService.isApprover(dossier) && !!file.lastReviewer">
<div class="vertical-line"></div>
<div class="all-caps-label mr-16 ml-8" translate="file-preview.last-reviewer"></div>
<redaction-initials-avatar [user]="file.lastReviewer" [withName]="true"></redaction-initials-avatar>
</ng-container>
<redaction-file-actions [file]="file" type="file-preview"></redaction-file-actions>
<div class="vertical-line"></div>
<iqser-circle-button
(action)="toggleFullScreen()"
[icon]="fullScreen ? 'red:exit-fullscreen' : 'red:fullscreen'"
[tooltip]="'file-preview.fullscreen' | translate"
class="ml-2"
tooltipPosition="below"
></iqser-circle-button>
<redaction-file-actions [file]="file" type="file-preview"></redaction-file-actions>
<!-- Dev Mode Features-->
<iqser-circle-button
(action)="downloadOriginalFile(file)"
*ngIf="userPreferenceService.areDevFeaturesEnabled"
[tooltip]="'file-preview.download-original-file' | translate"
[type]="circleButtonTypes.primary"
class="ml-8"
icon="iqser:download"
tooltipPosition="below"
></iqser-circle-button>
<!-- End Dev Mode Features-->
<iqser-circle-button
(action)="toggleFullScreen()"
[icon]="fullScreen ? 'red:exit-fullscreen' : 'red:fullscreen'"
[tooltip]="'file-preview.fullscreen' | translate"
class="ml-2"
tooltipPosition="below"
></iqser-circle-button>
<iqser-circle-button
(action)="closeFullScreen()"
*ngIf="!fullScreen"
[routerLink]="dossier.routerLink"
[tooltip]="'common.close' | translate"
class="ml-8"
icon="iqser:close"
tooltipPosition="below"
></iqser-circle-button>
</div>
<!-- Dev Mode Features-->
<iqser-circle-button
(action)="downloadOriginalFile(file)"
*ngIf="userPreferenceService.areDevFeaturesEnabled"
[tooltip]="'file-preview.download-original-file' | translate"
[type]="circleButtonTypes.primary"
class="ml-8"
icon="iqser:download"
tooltipPosition="below"
></iqser-circle-button>
<!-- End Dev Mode Features-->
<iqser-circle-button
(action)="closeFullScreen()"
*ngIf="!fullScreen"
[routerLink]="dossier.routerLink"
[tooltip]="'common.close' | translate"
class="ml-8"
icon="iqser:close"
tooltipPosition="below"
></iqser-circle-button>
</div>
</div>
<div class="overlay-shadow"></div>
<div class="content-inner">
<div class="content-container">
<redaction-pdf-viewer
(annotationSelected)="handleAnnotationSelected($event)"
(annotationsChanged)="annotationsChangedByReviewAction($event)"
(keyUp)="handleKeyEvent($event); handleArrowEvent($event)"
(manualAnnotationRequested)="openManualAnnotationDialog($event)"
(pageChanged)="viewerPageChanged($event)"
(viewerReady)="viewerReady($event)"
*ngIf="displayPdfViewer"
[annotations]="visibleAnnotations"
[canPerformActions]="canPerformAnnotationActions$ | async"
[class.hidden]="!ready"
[dossier]="dossier"
[shouldDeselectAnnotationsOnPageChange]="shouldDeselectAnnotationsOnPageChange"
></redaction-pdf-viewer>
</div>
<div class="overlay-shadow"></div>
<div class="right-container">
<iqser-empty-state
*ngIf="file.excluded && (documentInfoService.hidden$ | async) && excludedPagesService.hidden$ | async"
[horizontalPadding]="40"
[text]="'file-preview.tabs.is-excluded' | translate"
icon="red:needs-work"
></iqser-empty-state>
<div class="content-inner">
<div class="content-container">
<redaction-pdf-viewer
(annotationSelected)="handleAnnotationSelected($event)"
(annotationsChanged)="annotationsChangedByReviewAction($event)"
(keyUp)="handleKeyEvent($event); handleArrowEvent($event)"
(manualAnnotationRequested)="openManualAnnotationDialog($event)"
(pageChanged)="viewerPageChanged($event)"
(viewerReady)="viewerReady($event)"
*ngIf="displayPdfViewer"
[annotations]="visibleAnnotations"
[canPerformActions]="canPerformAnnotationActions$ | async"
[class.hidden]="!ready"
[dossier]="dossier"
[shouldDeselectAnnotationsOnPageChange]="shouldDeselectAnnotationsOnPageChange"
></redaction-pdf-viewer>
</div>
<redaction-document-info *ngIf="documentInfoService.shown$ | async"></redaction-document-info>
<div class="right-container">
<iqser-empty-state
*ngIf="file.excluded && (documentInfoService.hidden$ | async) && excludedPagesService.hidden$ | async"
[horizontalPadding]="40"
[text]="'file-preview.tabs.is-excluded' | translate"
icon="red:needs-work"
></iqser-empty-state>
<redaction-document-info *ngIf="documentInfoService.shown$ | async"></redaction-document-info>
<redaction-file-workload
#fileWorkloadComponent
(annotationsChanged)="annotationsChangedByReviewAction($event)"
(deselectAnnotations)="deselectAnnotations($event)"
(selectAnnotations)="selectAnnotations($event)"
(selectPage)="selectPage($event)"
*ngIf="!file.excluded"
[(shouldDeselectAnnotationsOnPageChange)]="shouldDeselectAnnotationsOnPageChange"
[activeViewerPage]="activeViewerPage"
[annotationActionsTemplate]="annotationActionsTemplate"
[annotations]="visibleAnnotations"
[dialogRef]="dialogRef"
[file]="file"
[selectedAnnotations]="selectedAnnotations"
[viewer]="activeViewer"
></redaction-file-workload>
</div>
<redaction-file-workload
#fileWorkloadComponent
(annotationsChanged)="annotationsChangedByReviewAction($event)"
(deselectAnnotations)="deselectAnnotations($event)"
(selectAnnotations)="selectAnnotations($event)"
(selectPage)="selectPage($event)"
*ngIf="!file.excluded"
[(shouldDeselectAnnotationsOnPageChange)]="shouldDeselectAnnotationsOnPageChange"
[activeViewerPage]="activeViewerPage"
[annotationActionsTemplate]="annotationActionsTemplate"
[annotations]="visibleAnnotations"
[dialogRef]="dialogRef"
[file]="file"
[selectedAnnotations]="selectedAnnotations"
[viewer]="activeViewer"
></redaction-file-workload>
</div>
</section>
<ng-template #annotationActionsTemplate let-annotation="annotation">
<redaction-annotation-actions
(annotationsChanged)="annotationsChangedByReviewAction($event)"
[annotations]="[annotation]"
[canPerformAnnotationActions]="canPerformAnnotationActions$ | async"
[viewer]="activeViewer"
></redaction-annotation-actions>
</ng-template>
<ng-template #annotationFilterTemplate let-filter="filter">
<redaction-type-filter
*ngIf="filter.topLevelFilter"
[dossierTemplateId]="dossier.dossierTemplateId"
[filter]="filter"
></redaction-type-filter>
<ng-container *ngIf="!filter.topLevelFilter">
<redaction-dictionary-annotation-icon [dictionaryKey]="filter.id" [dossierTemplateId]="dossier.dossierTemplateId">
</redaction-dictionary-annotation-icon>
{{ filter.label | humanize: false }}
</ng-container>
</ng-template>
</ng-container>
</div>
</section>
</ng-container>
<ng-template #annotationActionsTemplate let-annotation="annotation">
<redaction-annotation-actions
(annotationsChanged)="annotationsChangedByReviewAction($event)"
[annotations]="[annotation]"
[canPerformAnnotationActions]="canPerformAnnotationActions$ | async"
[viewer]="activeViewer"
></redaction-annotation-actions>
</ng-template>
<ng-template #annotationFilterTemplate let-filter="filter">
<redaction-type-filter
*ngIf="filter.topLevelFilter"
[dossierTemplateId]="stateService.dossierTemplateId"
[filter]="filter"
></redaction-type-filter>
<ng-container *ngIf="!filter.topLevelFilter">
<redaction-dictionary-annotation-icon
[dictionaryKey]="filter.id"
[dossierTemplateId]="stateService.dossierTemplateId"
></redaction-dictionary-annotation-icon>
{{ filter.label | humanize: false }}
</ng-container>
</ng-template>

View File

@ -277,6 +277,7 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
selectPage(pageNumber: number) {
this.viewerComponent.utils.navigateToPage(pageNumber);
this._workloadComponent?.scrollAnnotationsToPage(pageNumber, 'always');
this._lastPage = pageNumber.toString();
}
openManualAnnotationDialog(manualRedactionEntryWrapper: ManualRedactionEntryWrapper) {
@ -421,7 +422,7 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
async downloadOriginalFile(file: File) {
const data = await this._fileManagementService
.downloadOriginalFile(this.dossierId, this.fileId, 'response', true, file.cacheIdentifier)
.downloadOriginalFile(this.dossierId, this.fileId, 'response', file.cacheIdentifier)
.toPromise();
download(data, file.filename);
}
@ -693,7 +694,7 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
this._instance.Core.annotationManager.showAnnotations(annotations);
}
private async _setAnnotationsColor(annotations: Annotation[], customData: string) {
private _setAnnotationsColor(annotations: Annotation[], customData: string) {
annotations.forEach(annotation => {
annotation['StrokeColor'] = this._annotationDrawService.convertColor(this._instance, annotation.getCustomData(customData));
});

View File

@ -45,7 +45,7 @@ export class AnnotationDrawService {
}
getColor(activeViewer: WebViewerInstance, dossierTemplateId: string, superType: string, dictionary?: string) {
let color;
let color: string;
switch (superType) {
case 'hint':
case 'redaction':
@ -62,11 +62,11 @@ export class AnnotationDrawService {
return color;
}
public getAndConvertColor(activeViewer: WebViewerInstance, dossierTemplateId: string, superType: string, dictionary?: string) {
getAndConvertColor(activeViewer: WebViewerInstance, dossierTemplateId: string, superType: string, dictionary?: string) {
return this.convertColor(activeViewer, this.getColor(activeViewer, dossierTemplateId, superType, dictionary));
}
public convertColor(activeViewer: WebViewerInstance, hexColor: string) {
convertColor(activeViewer: WebViewerInstance, hexColor: string) {
const rgbColor = hexToRgb(hexColor);
return new activeViewer.Core.Annotations.Color(rgbColor.r, rgbColor.g, rgbColor.b);
}
@ -108,7 +108,7 @@ export class AnnotationDrawService {
}
private async _drawSections(activeViewer: WebViewerInstance, sectionGrid: ISectionGrid, dossierId: string) {
const sections = [];
const sections: Core.Annotations.RectangleAnnotation[] = [];
for (const page of Object.keys(sectionGrid.rectanglesPerPage)) {
const sectionRectangles = sectionGrid.rectanglesPerPage[page];
sectionRectangles.forEach(sectionRectangle => {
@ -154,7 +154,7 @@ export class AnnotationDrawService {
const pageNumber = compareMode ? annotationWrapper.pageNumber * 2 - 1 : annotationWrapper.pageNumber;
const dossierTemplateId = this._dossiersService.find(dossierId).dossierTemplateId;
let annotation;
let annotation: Core.Annotations.RectangleAnnotation | Core.Annotations.TextHighlightAnnotation;
if (annotationWrapper.rectangle || annotationWrapper.isImage) {
annotation = new activeViewer.Core.Annotations.RectangleAnnotation();
const pageHeight = activeViewer.Core.documentViewer.getPageHeight(pageNumber);

View File

@ -1,44 +1,80 @@
import { Injectable } from '@angular/core';
import { BehaviorSubject, firstValueFrom, Observable } from 'rxjs';
import { BehaviorSubject, firstValueFrom, Observable, pairwise, switchMap } from 'rxjs';
import { FileDataModel } from '@models/file/file-data.model';
import { Dossier, File } from '@red/domain';
import { DossiersService } from '../../../../../services/entity-services/dossiers.service';
import { ActivatedRoute } from '@angular/router';
import { FilesMapService } from '../../../../../services/entity-services/files-map.service';
import { PermissionsService } from '../../../../../services/permissions.service';
import { boolFactory, shareLast } from '@iqser/common-ui';
import { filter, startWith } from 'rxjs/operators';
import { FileManagementService } from '../../../../../services/entity-services/file-management.service';
@Injectable()
export class FilePreviewStateService {
readonly fileData$: Observable<FileDataModel>;
readonly file$: Observable<File>;
readonly blob$: Observable<Blob>;
readonly dossier$: Observable<Dossier>;
readonly isReadonly$: Observable<boolean>;
readonly isWritable$: Observable<boolean>;
readonly fileData$: Observable<FileDataModel>;
readonly dossierId: string;
readonly dossierTemplateId: string;
readonly fileId: string;
private readonly _fileData$ = new BehaviorSubject<FileDataModel>(undefined);
readonly #fileData$ = new BehaviorSubject<FileDataModel | undefined>(undefined);
constructor(
private readonly _dossiersService: DossiersService,
private readonly _filesMapService: FilesMapService,
dossiersService: DossiersService,
filesMapService: FilesMapService,
permissionsService: PermissionsService,
activatedRoute: ActivatedRoute,
private readonly _fileManagementService: FileManagementService,
) {
this.dossierId = activatedRoute.snapshot.paramMap.get('dossierId');
this.dossierTemplateId = this._dossiersService.find(this.dossierId).dossierTemplateId;
this.dossier$ = _dossiersService.getEntityChanged$(this.dossierId);
this.fileId = activatedRoute.snapshot.paramMap.get('fileId');
this.dossierId = activatedRoute.snapshot.paramMap.get('dossierId');
this.dossierTemplateId = dossiersService.find(this.dossierId).dossierTemplateId;
this.fileData$ = this._fileData$.asObservable();
this.file$ = _filesMapService.watch$(this.dossierId, this.fileId);
this.dossier$ = dossiersService.getEntityChanged$(this.dossierId);
this.file$ = filesMapService.watch$(this.dossierId, this.fileId);
[this.isReadonly$, this.isWritable$] = boolFactory(this.file$, file => !permissionsService.canPerformAnnotationActions(file));
this.fileData$ = this.#fileData$.asObservable().pipe(filter(value => !!value));
this.blob$ = this.#blob$;
}
get fileData(): FileDataModel {
return this._fileData$.value;
return this.#fileData$.value;
}
set fileData(fileDataModel: FileDataModel) {
this._fileData$.next(fileDataModel);
this.#fileData$.next(fileDataModel);
}
get file(): Promise<File> {
return firstValueFrom(this.file$);
}
get dossier(): Promise<Dossier> {
return firstValueFrom(this.dossier$);
}
get blob(): Promise<Blob> {
return firstValueFrom(this.blob$);
}
get #blob$() {
return this.file$.pipe(
startWith(undefined),
pairwise(),
filter(([oldFile, newFile]) => oldFile?.cacheIdentifier !== newFile.cacheIdentifier),
switchMap(([, newFile]) => this.#downloadOriginalFile(newFile.cacheIdentifier)),
shareLast(),
);
}
#downloadOriginalFile(cacheIdentifier: string): Observable<Blob> {
return this._fileManagementService.downloadOriginalFile(this.dossierId, this.fileId, 'body', cacheIdentifier);
}
}

View File

@ -1,32 +1,30 @@
import { Injectable } from '@angular/core';
import { BehaviorSubject, Observable } from 'rxjs';
import { shareDistinctLast } from '@iqser/common-ui';
import { map } from 'rxjs/operators';
import { boolFactory } from '@iqser/common-ui';
@Injectable()
export class MultiSelectService {
readonly active$: Observable<boolean>;
readonly inactive$: Observable<boolean>;
private readonly _active$ = new BehaviorSubject(false);
readonly #active$ = new BehaviorSubject(false);
constructor() {
this.active$ = this._active$.asObservable().pipe(shareDistinctLast());
this.inactive$ = this.active$.pipe(map(value => !value));
[this.active$, this.inactive$] = boolFactory(this.#active$.asObservable());
}
get isActive() {
return this._active$.value;
return this.#active$.value;
}
activate() {
this._active$.next(true);
this.#active$.next(true);
}
deactivate() {
this._active$.next(false);
this.#active$.next(false);
}
toggle() {
this._active$.next(!this._active$.value);
this.#active$.next(!this.#active$.value);
}
}

View File

@ -1,23 +1,19 @@
import { Injectable } from '@angular/core';
import { forkJoin, Observable, of, switchMap } from 'rxjs';
import { catchError, map, take, tap } from 'rxjs/operators';
import { forkJoin, Observable, of } from 'rxjs';
import { catchError, map, tap } from 'rxjs/operators';
import { FileDataModel } from '@models/file/file-data.model';
import { PermissionsService } from '@services/permissions.service';
import { File, IRedactionLog, IViewedPage } from '@red/domain';
import { FileManagementService } from '@services/entity-services/file-management.service';
import { RedactionLogService } from './redaction-log.service';
import { ViewedPagesService } from '@services/entity-services/viewed-pages.service';
import { AppStateService } from '../../../state/app-state.service';
import { DossiersService } from '../../../services/entity-services/dossiers.service';
import { UserPreferenceService } from '../../../services/user-preference.service';
import { FilePreviewStateService } from '../screens/file-preview-screen/services/file-preview-state.service';
@Injectable()
export class PdfViewerDataService {
constructor(
private readonly _dossiersService: DossiersService,
private readonly _permissionsService: PermissionsService,
private readonly _fileManagementService: FileManagementService,
private readonly _redactionLogService: RedactionLogService,
private readonly _viewedPagesService: ViewedPagesService,
private readonly _appStateService: AppStateService,
@ -33,24 +29,16 @@ export class PdfViewerDataService {
}
loadDataFor(newFile: File): Observable<FileDataModel> {
const oldBlob$ = this._stateService.fileData?.blob$;
const blob$ = this._stateService.file$.pipe(
map(file => file.cacheIdentifier === newFile.cacheIdentifier && oldBlob$),
switchMap(isSame => (isSame ? oldBlob$ : this.downloadOriginalFile(newFile))),
take(1),
);
const redactionLog$ = this.loadRedactionLogFor(newFile.dossierId, newFile.fileId);
const viewedPages$ = this.getViewedPagesFor(newFile);
const dossier = this._dossiersService.find(newFile.dossierId);
return forkJoin([blob$, redactionLog$, viewedPages$]).pipe(
return forkJoin([redactionLog$, viewedPages$]).pipe(
map(
(data: [blob: Blob, redactionLog: IRedactionLog, viewedPages: IViewedPage[]]) =>
(data: [redactionLog: IRedactionLog, viewedPages: IViewedPage[]]) =>
new FileDataModel(
newFile,
...data,
this._appStateService.dictionaryData[dossier.dossierTemplateId],
this._appStateService.dictionaryData[this._stateService.dossierTemplateId],
this._userPreferenceService.areDevFeaturesEnabled,
),
),
@ -63,8 +51,4 @@ export class PdfViewerDataService {
}
return of([]);
}
downloadOriginalFile(file: File): Observable<Blob> {
return this._fileManagementService.downloadOriginalFile(file.dossierId, file.fileId, 'body', true, file.cacheIdentifier);
}
}

View File

@ -36,26 +36,16 @@ export class FileManagementService extends GenericService<unknown> {
return this._post(body, `delete/restore/${dossierId}`).pipe(switchMap(() => this._filesService.loadAll(dossierId)));
}
downloadOriginalFile(dossierId: string, fileId: string, observe?: 'body', inline?: boolean, indicator?: string): Observable<Blob>;
downloadOriginalFile(
dossierId: string,
fileId: string,
observe?: 'response',
inline?: boolean,
indicator?: string,
): Observable<HttpResponse<Blob>>;
downloadOriginalFile(dossierId: string, fileId: string, observe?: 'body', indicator?: string): Observable<Blob>;
downloadOriginalFile(dossierId: string, fileId: string, observe?: 'response', indicator?: string): Observable<HttpResponse<Blob>>;
@Validate()
downloadOriginalFile(
@RequiredParam() dossierId: string,
@RequiredParam() fileId: string,
observe: 'body' | 'response' = 'body',
inline?: boolean,
indicator?: string,
) {
const queryParams: QueryParam[] = [];
if (inline) {
queryParams.push({ key: 'inline', value: inline });
}
const queryParams: QueryParam[] = [{ key: 'inline', value: true }];
if (indicator) {
queryParams.push({ key: 'indicator', value: indicator });

View File

@ -113,8 +113,8 @@ export class UserService extends EntitiesService<User, IUser> {
find(id: string): User | undefined {
if (id?.toLowerCase() === 'system') {
return new User({ email: 'System' }, [], 'system');
return new User({ username: 'System' }, [], 'system');
}
return super.find(id);
return super.find(id) || new User({ username: 'Deleted User' }, [], 'deleted');
}
}

View File

@ -19,5 +19,6 @@
"OAUTH_IDP_HINT": null,
"OAUTH_URL": "https://dev-05.iqser.cloud/auth/realms/redaction",
"RECENT_PERIOD_IN_HOURS": 24,
"SELECTION_MODE": "structural"
"SELECTION_MODE": "structural",
"MANUAL_BASE_URL": "https://docs.redactmanager.com/3.0"
}

View File

@ -1,42 +1,42 @@
{
"open-usermenu": {
"en": "https://docs.redactmanager.com/2.2/en/user-menu-and-profile.html",
"en": "/en/23200-user-menu-and-account.html",
"de": "",
"it": "",
"fr": ""
},
"filter-dossier-list": {
"en": "https://docs.redactmanager.com/2.2/en/creating-and-managing-dossiers/dossier-overview/dossier-list.html#UUID-e5177955-3e92-8627-0732-af8d4a2036e2",
"en": "/en/26024-features-and-actions-in-the-dossier-overview.html",
"de": "",
"it": "",
"fr": ""
},
"filter-document-list": {
"en": "https://docs.redactmanager.com/preview/en/create-and-manage-dossier/dossier-overview/dossier-list.html",
"en": "/en/create-and-manage-dossier/dossier-overview/dossier-list.html",
"de": "",
"it": "",
"fr": ""
},
"dossiers-quickfilter-my-dossiers": {
"en": "https://docs.redactmanager.com/preview/en/create-and-manage-dossier/dossier-overview/dossier-list.html",
"en": "/en/create-and-manage-dossier/dossier-overview/dossier-list.html",
"de": "",
"it": "",
"fr": ""
},
"search-in-entire-application": {
"en": "https://docs.redactmanager.com/2.2/en/searching-the-application.html",
"en": "/en/15632-searching-the-application.html",
"de": "",
"it": "",
"fr": ""
},
"open-notifications": {
"en": "https://docs.redactmanager.com/2.2/en/notifications.html",
"en": "/en/15471-notifications.html",
"de": "",
"it": "",
"fr": ""
},
"dossier-list": {
"en": "https://docs.redactmanager.com/2.2/en/20941-dossier-liste.html",
"en": "/en/20941-dossier-liste.html",
"de": "",
"it": "",
"fr": ""
@ -48,7 +48,7 @@
"fr": ""
},
"new-dossier-button": {
"en": "https://docs.redactmanager.com/2.2/en/creating-and-managing-dossiers/dossier-overview/creating-a-new-dossier.html",
"en": "/en/creating-and-managing-dossiers/dossier-overview/creating-a-new-dossier.html",
"de": "",
"it": "",
"fr": ""
@ -90,19 +90,19 @@
"fr": ""
},
"standard-view": {
"en": "https://docs.redactmanager.com/2.2/en/editing-documents-in-the-editor/views-in-the-editor/standard-view.html",
"en": "/en/editing-documents-in-the-editor/views-in-the-editor/standard-view.html",
"de": "",
"it": "",
"fr": ""
},
"delta-view": {
"en": "https://docs.redactmanager.com/2.2/en/editing-documents-in-the-editor/views-in-the-editor/delta-view.html",
"en": "/en/editing-documents-in-the-editor/views-in-the-editor/delta-view.html",
"de": "",
"it": "",
"fr": ""
},
"preview-view": {
"en": "https://docs.redactmanager.com/2.2/en/editing-documents-in-the-editor/views-in-the-editor/preview.html",
"en": "/en/editing-documents-in-the-editor/views-in-the-editor/preview.html",
"de": "",
"it": "",
"fr": ""

View File

@ -498,7 +498,7 @@
"defaultColor": "Default Color",
"dictionaryRequestColor": "Dictionary Request",
"ignoredHintColor": "Ignored Hint",
"manualRedactionColor": "Manual Redaction",
"manualRedactionColor": "Redaction Color",
"notRedacted": "Skipped",
"previewColor": "Preview",
"requestAdd": "Request Add",

View File

@ -23,6 +23,7 @@ OAUTH_IDP_HINT="${OAUTH_IDP_HINT:-}"
OAUTH_URL="${OAUTH_URL:-/auth}"
RECENT_PERIOD_IN_HOURS="${RECENT_PERIOD_IN_HOURS:-24}"
SELECTION_MODE="${SELECTION_MODE:-structural}"
MANUAL_BASE_URL="${MANUAL_BASE_URL:-https://docs.redactmanager.com/3.0}"
@ -47,6 +48,7 @@ echo '{
"OAUTH_URL":"'"$OAUTH_URL"'",
"RECENT_PERIOD_IN_HOURS":'"$RECENT_PERIOD_IN_HOURS"',
"SELECTION_MODE":"'"$SELECTION_MODE"'"
"MANUAL_BASE_URL":"'"$MANUAL_BASE_URL"'"
}' > /usr/share/nginx/html/ui/assets/config/config.json
echo 'Env variables: '

@ -1 +1 @@
Subproject commit b808e4661e56aa93baca96c57f910443437112d4
Subproject commit 6b4fe281cb6e23fb541ce75ead740a5678c968b8

View File

@ -18,6 +18,11 @@ export class DossierTemplateStats implements IDossierTemplateStats {
}
dictionarySummary(type: string): DictionarySummary | undefined {
return this._dictionarySummaryMap.get(type);
const defaultValue: DictionarySummary = { entriesCount: 0, id: type, name: type, type };
if (!this._dictionarySummaryMap.get(type)) {
// TODO
console.error(`Missing type ${type}`);
}
return this._dictionarySummaryMap.get(type) || defaultValue;
}
}

View File

@ -1,6 +1,6 @@
{
"name": "redaction",
"version": "3.205.0",
"version": "3.213.0",
"private": true,
"license": "MIT",
"scripts": {

Binary file not shown.