Merge master into RED-7497

This commit is contained in:
Adina Țeudan 2023-09-12 13:15:30 +03:00
commit c0ff902f69
71 changed files with 1248 additions and 250 deletions

View File

@ -1,4 +1,4 @@
<iqser-help-mode *ngIf="!config.IS_DOCUMINE"></iqser-help-mode>
<iqser-help-mode></iqser-help-mode>
<div class="top-bar">
<ng-template #menuPlaceholder>
@ -21,11 +21,11 @@
<redaction-spotlight-search
*allow="roles.search; if: (isSearchScreen$ | async) === false && (currentUser.isUser || currentUser.isManager)"
[actions]="searchActions"
[attr.help-mode-key]="'search_in_entire_application'"
[attr.help-mode-key]="'search_entire_application'"
[placeholder]="'search.placeholder' | translate"
></redaction-spotlight-search>
<iqser-help-button *ngIf="!config.IS_DOCUMINE" [attr.help-mode-key]="'help_mode'" id="help-mode-button"></iqser-help-button>
<iqser-help-button [attr.help-mode-key]="'help_mode'" id="help-mode-button"></iqser-help-button>
<redaction-notifications
*ngIf="currentUser.isUser || currentUser.isManager"

View File

@ -1,4 +1,4 @@
<div *ngIf="breadcrumbsService.breadcrumbs$ | async as breadcrumbs" [attr.help-mode-key]="'navigate_in_breadcrumbs'" class="breadcrumbs">
<div *ngIf="breadcrumbsService.breadcrumbs$ | async as breadcrumbs" [attr.help-mode-key]="'navigate_breadcrumbs'" class="breadcrumbs">
<a *ngIf="breadcrumbs.length === 0; else items" class="breadcrumb back" redactionNavigateLastDossiersScreen>
<mat-icon svgIcon="iqser:expand"></mat-icon>
{{ 'top-bar.navigation-items.back' | translate }}
@ -29,15 +29,17 @@
></iqser-chevron-button>
<mat-menu #dropdownMenu="matMenu" class="padding-bottom-8">
<a
*ngFor="let option of breadcrumb.options.options"
[routerLink]="option.options.routerLink | tenant"
mat-menu-item
routerLinkActive="active"
>
{{ option.name$ | async }}
<mat-icon class="checkmark" svgIcon="iqser:check"></mat-icon>
</a>
<div id="breadcrumbs-menu-items">
<a
*ngFor="let option of breadcrumb.options.options"
[routerLink]="option.options.routerLink | tenant"
mat-menu-item
routerLinkActive="active"
>
{{ option.name$ | async }}
<mat-icon class="checkmark" svgIcon="iqser:check"></mat-icon>
</a>
</div>
</mat-menu>
</ng-container>
</ng-container>

View File

@ -25,9 +25,8 @@ export const canRemoveFromDictionary = (annotation: AnnotationWrapper) =>
!annotation.pending &&
!annotation.hasBeenResized;
export const canRemoveRedaction = (annotations: AnnotationWrapper[], permissions: AnnotationPermissions) =>
annotations.length === 1 &&
(permissions.canRemoveOnlyHere || permissions.canRemoveFromDictionary || permissions.canMarkAsFalsePositive);
export const canRemoveRedaction = (permissions: AnnotationPermissions) =>
permissions.canRemoveOnlyHere || permissions.canRemoveFromDictionary || permissions.canMarkAsFalsePositive;
export const canChangeLegalBasis = (annotation: AnnotationWrapper, canAddRedaction: boolean) =>
canAddRedaction && annotation.isRedacted && !annotation.pending;

View File

@ -29,6 +29,7 @@ export class AnnotationPermissions {
canResizeAnnotation = true;
canRecategorizeAnnotation = true;
canForceHint = true;
canEditAnnotations = true;
static forUser(
isApprover: boolean,
@ -46,7 +47,6 @@ export class AnnotationPermissions {
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.canForceHint = canForceHint(annotation, canAddRedaction);
permissions.canForceRedaction = canForceRedaction(annotation, canAddRedaction);
@ -54,10 +54,11 @@ export class AnnotationPermissions {
permissions.canMarkAsFalsePositive = canMarkAsFalsePositive(annotation, annotationEntity);
permissions.canRemoveOnlyHere = canRemoveOnlyHere(annotation, canAddRedaction);
permissions.canRemoveFromDictionary = canRemoveFromDictionary(annotation);
permissions.canRemoveRedaction = canRemoveRedaction(annotations, permissions);
permissions.canRemoveRedaction = canRemoveRedaction(permissions);
permissions.canChangeLegalBasis = canChangeLegalBasis(annotation, canAddRedaction);
permissions.canRecategorizeAnnotation = canRecategorizeAnnotation(annotation, canAddRedaction);
permissions.canResizeAnnotation = canResizeAnnotation(annotation, canAddRedaction);
permissions.canEditAnnotations = annotation.isSkipped || annotation.isRedacted;
summedPermissions._merge(permissions);
}
@ -77,6 +78,7 @@ export class AnnotationPermissions {
result.canRemoveOnlyHere = permissions.reduce((acc, next) => acc && next.canRemoveOnlyHere, true);
result.canRemoveRedaction = permissions.reduce((acc, next) => acc && next.canRemoveRedaction, true);
result.canUndo = permissions.reduce((acc, next) => acc && next.canUndo, true);
result.canEditAnnotations = permissions.reduce((acc, next) => acc && next.canEditAnnotations, true);
return result;
}

View File

@ -302,6 +302,7 @@ export class AnnotationWrapper implements IListable {
changeLogType: ChangeType,
legalBasisList: ILegalBasis[],
hintDictionary: boolean,
isDocumine: boolean,
) {
const annotationWrapper = new AnnotationWrapper();
@ -351,7 +352,7 @@ export class AnnotationWrapper implements IListable {
c => c.manualRedactionType === ManualRedactionTypes.LEGAL_BASIS_CHANGE && c.annotationStatus === LogEntryStatuses.REQUESTED,
)?.propertyChanges.legalBasis;
this.#createContent(annotationWrapper, redactionLogEntry);
this.#createContent(annotationWrapper, redactionLogEntry, isDocumine);
this.#setSuperType(annotationWrapper, redactionLogEntry);
this.#handleRecommendations(annotationWrapper, redactionLogEntry);
annotationWrapper.typeLabel = this.#getTypeLabel(redactionLogEntry, annotationWrapper);
@ -421,20 +422,24 @@ export class AnnotationWrapper implements IListable {
}
}
static #createContent(annotationWrapper: AnnotationWrapper, entry: IRedactionLogEntry) {
static #createContent(annotationWrapper: AnnotationWrapper, entry: IRedactionLogEntry, isDocumine: boolean) {
let content = '';
if (entry.matchedRule) {
content += `Rule ${entry.matchedRule} matched \n\n`;
content += `Rule ${entry.matchedRule} matched${isDocumine ? ':' : ''} \n\n`;
}
if (entry.reason) {
if (isDocumine && entry.reason.slice(-1) === '.') {
entry.reason = entry.reason.slice(0, -1);
}
content += entry.reason + '\n\n';
//remove leading and trailing commas and whitespaces
content = content.replace(/(^[, ]*)|([, ]*$)/g, '');
content = content.substring(0, 1).toUpperCase() + content.substring(1);
}
if (annotationWrapper.legalBasis) {
if (annotationWrapper.legalBasis && !isDocumine) {
content += 'Legal basis: ' + annotationWrapper.legalBasis + '\n\n';
}
@ -443,7 +448,7 @@ export class AnnotationWrapper implements IListable {
}
if (entry.section) {
let prefix = 'In section: ';
let prefix = `In section${isDocumine ? '' : ':'} `;
if (content.length) {
prefix = ` ${prefix.toLowerCase()}`;
}

View File

@ -4,7 +4,7 @@
*ngIf="item.show"
[routerLinkActiveOptions]="{ exact: false }"
[routerLink]="'../' + item.screen"
[attr.help-mode-key]="'user_account'"
[attr.help-mode-key]="item.helpModeKey"
class="item"
routerLinkActive="active"
>

View File

@ -9,6 +9,7 @@ interface NavItem {
readonly label: string;
readonly screen: string;
readonly show?: boolean;
readonly helpModeKey?: string;
}
@Component({
@ -24,21 +25,25 @@ export class AccountSideNavComponent {
screen: 'user-profile',
label: _('user-profile'),
show: true,
helpModeKey: 'my_profile',
},
{
screen: 'notifications',
show: this.currentUser.isUser && this._permissionsService.has(Roles.notifications.write),
label: _('notifications.label'),
helpModeKey: 'notification_preferences',
},
{
screen: 'preferences',
label: _('preferences-screen.label'),
show: this.currentUser.isUser,
helpModeKey: 'user_preferences',
},
{
screen: 'warnings-preferences',
label: _('preferences-screen.warnings-label'),
show: this.currentUser.isUser,
helpModeKey: 'prompts_and_dialogs',
},
];

View File

@ -4,6 +4,7 @@ import { NotificationPreferencesService } from '../../../services/notification-p
import { BaseFormComponent, getConfig, LoadingService, Toaster } from '@iqser/common-ui';
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
import {
DocumentNotificationsTypes,
NotificationCategoriesValues,
NotificationGroupsKeys,
NotificationGroupsValues,
@ -77,7 +78,8 @@ export class NotificationsScreenComponent extends BaseFormComponent implements O
async save() {
this.#loadingService.start();
try {
await firstValueFrom(this.#notificationPreferencesService.update(this.form.value));
const preferences = this.#filterNotificationPreferences();
await firstValueFrom(this.#notificationPreferencesService.update(preferences));
} catch (e) {
this.#toaster.error(_('notifications-screen.error.generic'));
}
@ -106,4 +108,27 @@ export class NotificationsScreenComponent extends BaseFormComponent implements O
this.#loadingService.stop();
}
#filterNotificationPreferences() {
if (!this.#config.IS_DOCUMINE) {
return this.form.value;
}
if (!this.isPreferenceChecked('inAppNotifications', DocumentNotificationsTypes.assignApprover)) {
return {
...this.form.value,
inAppNotifications: this.form
.get('inAppNotifications')
.value.filter((preference: string) => !RSS_EXCLUDED_SETTINGS.includes(preference)),
};
}
if (!this.form.get('inAppNotifications').value.includes(DocumentNotificationsTypes.assignReviewer)) {
return {
...this.form.value,
inAppNotifications: [...this.form.get('inAppNotifications').value, DocumentNotificationsTypes.assignReviewer],
};
}
return this.form.value;
}
}

View File

@ -61,6 +61,19 @@ const dossierTemplateIdRoutes: IqserRoutes = [
},
loadChildren: () => import('./screens/rules/rules.module').then(m => m.RulesModule),
},
{
path: 'component-rules',
component: BaseDossierTemplateScreenComponent,
canActivate: [CompositeRouteGuard, IqserPermissionsGuard],
data: {
routeGuards: [IqserAuthGuard, RedRoleGuard],
permissions: {
allow: [Roles.rules.read],
redirectTo: 'info',
},
},
loadChildren: () => import('./screens/component-rules/component-rules.module').then(m => m.ComponentRulesModule),
},
{
path: 'file-attributes',
component: BaseDossierTemplateScreenComponent,

View File

@ -57,6 +57,7 @@ import { DossierTemplateActionsComponent } from './shared/components/dossier-tem
import { IqserUsersModule } from '@iqser/common-ui/lib/users';
import { TenantPipe } from '@iqser/common-ui/lib/tenants';
import { SelectComponent } from '@shared/components/select/select.component';
import { ComponentRulesService } from './services/component-rules.service';
const dialogs = [
AddEditCloneDossierTemplateDialogComponent,
@ -97,7 +98,7 @@ const components = [
@NgModule({
declarations: [...components],
providers: [AdminDialogService, AuditService, DigitalSignatureService, RulesService, SmtpConfigService],
providers: [AdminDialogService, AuditService, DigitalSignatureService, RulesService, ComponentRulesService, SmtpConfigService],
imports: [
CommonModule,
SharedModule,

View File

@ -0,0 +1,26 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { RouterModule } from '@angular/router';
import { ComponentRulesScreenComponent } from './rules-screen/component-rules-screen.component';
import { MonacoEditorModule } from '@materia-ui/ngx-monaco-editor';
import { PendingChangesGuard } from '@guards/can-deactivate.guard';
import { TranslateModule } from '@ngx-translate/core';
import { IconButtonComponent } from '@iqser/common-ui';
import { FormsModule } from '@angular/forms';
import { MatIconModule } from '@angular/material/icon';
const routes = [{ path: '', component: ComponentRulesScreenComponent, canDeactivate: [PendingChangesGuard] }];
@NgModule({
declarations: [ComponentRulesScreenComponent],
imports: [
RouterModule.forChild(routes),
CommonModule,
MonacoEditorModule,
TranslateModule,
IconButtonComponent,
FormsModule,
MatIconModule,
],
})
export class ComponentRulesModule {}

View File

@ -0,0 +1,26 @@
<div class="header-container">
<div [translate]="'component-rules-screen.title'" class="heading-l"></div>
<div [translate]="'component-rules-screen.warning-text'" class="error"></div>
</div>
<ngx-monaco-editor (init)="onCodeEditorInit($event)" [(ngModel)]="codeEditorText" [options]="editorOptions"></ngx-monaco-editor>
<div *ngIf="changed && permissionsService.canEditRules() && !isLeaving" class="changes-box">
<div (click)="goToErrors()" *ngIf="numberOfErrors()" class="errors">
<span>
<mat-icon [svgIcon]="'iqser:alert-circle'" class="icon"></mat-icon>
<span [translateParams]="{ errors: numberOfErrors() }" [translate]="'component-rules-screen.errors-found'"></span>
</span>
<mat-icon [svgIcon]="'iqser:expand'" class="icon face-up"></mat-icon>
</div>
<div class="actions">
<iqser-icon-button
(action)="save()"
[label]="'component-rules-screen.save-changes' | translate"
[type]="iconButtonTypes.primary"
icon="iqser:check"
></iqser-icon-button>
<div (click)="revert()" [translate]="'component-rules-screen.revert-changes'" class="all-caps-label cancel"></div>
</div>
</div>

View File

@ -0,0 +1,67 @@
:host {
flex-grow: 1;
overflow: hidden;
padding: 15px 0 0 15px;
}
ngx-monaco-editor {
height: 100%;
width: 100%;
}
.header-container {
display: flex;
align-items: center;
padding-bottom: 15px;
margin-left: 30px;
.error {
color: red;
font-size: 8px;
font-weight: 500;
margin-left: 20px;
font-style: italic;
}
}
.changes-box {
flex-direction: column;
justify-content: center;
align-items: start;
gap: 10px;
> div {
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
width: 100%;
}
.errors {
cursor: pointer;
.icon {
scale: 0.7;
}
:first-child {
display: flex;
align-items: center;
gap: 0.5em;
font-weight: 500;
/* TODO: Update this to --iqser-error when added */
color: #dd4d50;
}
.face-up {
transform: rotate(90deg);
}
margin-right: 0;
}
.actions {
gap: 24px;
}
}

View File

@ -0,0 +1,216 @@
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, computed, ElementRef, OnInit, signal, ViewChild } from '@angular/core';
import { PermissionsService } from '@services/permissions.service';
import { IconButtonTypes, LoadingService, Toaster } from '@iqser/common-ui';
import { saveAs } from 'file-saver';
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
import { firstValueFrom } from 'rxjs';
import { DOSSIER_TEMPLATE_ID } from '@red/domain';
import { EditorThemeService } from '@services/editor-theme.service';
import { ComponentCanDeactivate } from '@guards/can-deactivate.guard';
import { Debounce, getParam } from '@iqser/common-ui/lib/utils';
import { ComponentRulesService } from '../../../services/component-rules.service';
import ICodeEditor = monaco.editor.ICodeEditor;
import IModelDeltaDecoration = monaco.editor.IModelDeltaDecoration;
import IStandaloneEditorConstructionOptions = monaco.editor.IStandaloneEditorConstructionOptions;
interface SyntaxError {
line: number;
column: number;
message: string;
}
@Component({
templateUrl: './component-rules-screen.component.html',
styleUrls: ['./component-rules-screen.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ComponentRulesScreenComponent implements OnInit, ComponentCanDeactivate {
readonly iconButtonTypes = IconButtonTypes;
readonly editorOptions: IStandaloneEditorConstructionOptions = {
theme: 'vs',
language: 'java',
automaticLayout: true,
readOnly: !this.permissionsService.canEditRules(),
glyphMargin: true,
};
initialLines: string[] = [];
currentLines: string[] = [];
isLeaving = false;
@ViewChild('fileInput')
private _fileInput: ElementRef;
private _codeEditor: ICodeEditor;
private _decorations: string[] = [];
readonly #errorGlyphs = signal<string[]>([]);
readonly numberOfErrors = computed(() => this.#errorGlyphs().length);
readonly #dossierTemplateId = getParam(DOSSIER_TEMPLATE_ID);
constructor(
readonly permissionsService: PermissionsService,
private readonly _componentRulesService: ComponentRulesService,
private readonly _changeDetectorRef: ChangeDetectorRef,
private readonly _toaster: Toaster,
private readonly _loadingService: LoadingService,
private readonly _editorThemeService: EditorThemeService,
) {}
set isLeavingPage(isLeaving: boolean) {
this.isLeaving = isLeaving;
this._changeDetectorRef.detectChanges();
}
get changed(): boolean {
return this.currentLines.toString() !== this.initialLines.toString();
}
get codeEditorText() {
return this.currentLines.join('\n');
}
set codeEditorText($event: any) {
this.currentLines = $event.split('\n');
this.codeEditorTextChanged();
this._closeProblemsView();
}
async ngOnInit() {
await this._initialize();
}
onCodeEditorInit(editor: ICodeEditor) {
this._codeEditor = editor;
for (const theme of this._editorThemeService.themes) {
(window as any).monaco.editor.defineTheme(theme, this._editorThemeService.configurations[theme]);
}
(window as any).monaco.editor.setTheme(this._editorThemeService.getTheme(true));
this._changeDetectorRef.detectChanges();
}
@Debounce()
codeEditorTextChanged() {
const newDecorations = this.currentLines.filter(entry => this._isNew(entry)).map(entry => this._makeDecorationFor(entry));
this._decorations = this._codeEditor.deltaDecorations(this._decorations, newDecorations);
}
goToErrors() {
this._codeEditor.trigger(null, 'editor.action.marker.next', null);
}
async save(): Promise<void> {
this._loadingService.start();
this._removeErrorMarkers();
await firstValueFrom(
this._componentRulesService.uploadRules({
rules: this._codeEditor.getModel().getValue(),
dossierTemplateId: this.#dossierTemplateId,
}),
).then(
async () => {
await this._initialize();
this._toaster.success(_('component-rules-screen.success.generic'));
},
error => {
const errors = error.error as SyntaxError[] | undefined;
this._drawErrorMarkers(errors);
this._loadingService.stop();
this._toaster.error(_('component-rules-screen.error.generic'));
},
);
}
revert(): void {
this.currentLines = this.initialLines;
this._decorations = this._codeEditor?.deltaDecorations(this._decorations, []) || [];
this._removeErrorMarkers();
this._changeDetectorRef.detectChanges();
this._loadingService.stop();
}
download(): void {
const content = this._codeEditor.getModel().getValue();
const blob = new Blob([content], {
type: 'text/plain;charset=utf-8',
});
saveAs(blob, 'rules.txt');
}
upload($event): void {
const file: File = $event.target.files[0];
const fileReader = new FileReader();
if (file) {
fileReader.onload = () => {
this._codeEditor.getModel().setValue(fileReader.result as string);
this._fileInput.nativeElement.value = null;
};
fileReader.readAsText(file);
}
}
private _isNew(entry: string): boolean {
return this.initialLines.indexOf(entry) < 0 && entry?.trim().length > 0;
}
private _makeDecorationFor(entry: string): IModelDeltaDecoration {
const line = this.currentLines.indexOf(entry) + 1;
return {
range: new monaco.Range(line, 1, line, 1),
options: { isWholeLine: true, className: 'changed-row-marker' },
} as IModelDeltaDecoration;
}
private _drawErrorMarkers(errors: SyntaxError[] | undefined) {
const model = this._codeEditor?.getModel();
if (!model || !errors?.length) {
return;
}
const markers = [];
const glyphs = [];
errors
.filter(e => e.line > 0)
.forEach(e => {
const endColumn = model.getLineLength(e.line) + 1;
markers.push({
message: e.message,
severity: monaco.MarkerSeverity.Error,
startLineNumber: e.line,
startColumn: e.column,
endLineNumber: e.line,
endColumn,
});
glyphs.push({
range: new monaco.Range(e.line, e.column, e.line, endColumn),
options: {
glyphMarginClassName: 'error-glyph-margin',
},
});
});
this.#errorGlyphs.set(this._codeEditor.deltaDecorations(this.#errorGlyphs(), glyphs));
(window as any).monaco.editor.setModelMarkers(model, model.id, markers);
}
private _closeProblemsView() {
this._codeEditor.trigger(null, 'closeMarkersNavigation', null);
}
private _removeErrorMarkers() {
const model = this._codeEditor?.getModel();
if (!model) {
return;
}
(window as any).monaco.editor.setModelMarkers(model, model.id, []);
this.#errorGlyphs.set(this._codeEditor.deltaDecorations(this.#errorGlyphs(), []) || []);
this._closeProblemsView();
}
private async _initialize() {
this._loadingService.start();
await firstValueFrom(this._componentRulesService.download(this.#dossierTemplateId)).then(
rules => {
this.currentLines = this.initialLines = rules.rules.split('\n');
this.revert();
},
() => this._loadingService.stop(),
);
}
}

View File

@ -6,12 +6,21 @@
<ngx-monaco-editor (init)="onCodeEditorInit($event)" [(ngModel)]="codeEditorText" [options]="editorOptions"></ngx-monaco-editor>
<div *ngIf="changed && permissionsService.canEditRules() && !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)="goToErrors()" *ngIf="numberOfErrors()" class="errors">
<span>
<mat-icon [svgIcon]="'iqser:alert-circle'" class="icon"></mat-icon>
<span [translateParams]="{ errors: numberOfErrors() }" [translate]="'rules-screen.errors-found'"></span>
</span>
<mat-icon [svgIcon]="'iqser:expand'" class="icon face-up"></mat-icon>
</div>
<div class="actions">
<iqser-icon-button
(action)="save()"
[label]="'rules-screen.save-changes' | translate"
[type]="iconButtonTypes.primary"
icon="iqser:check"
></iqser-icon-button>
<div (click)="revert()" [translate]="'rules-screen.revert-changes'" class="all-caps-label cancel"></div>
<div (click)="revert()" [translate]="'rules-screen.revert-changes'" class="all-caps-label cancel"></div>
</div>
</div>

View File

@ -23,3 +23,45 @@ ngx-monaco-editor {
font-style: italic;
}
}
.changes-box {
flex-direction: column;
justify-content: center;
align-items: start;
gap: 10px;
> div {
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
width: 100%;
}
.errors {
cursor: pointer;
.icon {
scale: 0.7;
}
:first-child {
display: flex;
align-items: center;
gap: 0.5em;
font-weight: 500;
/* TODO: Update this to --iqser-error when added */
color: #dd4d50;
}
.face-up {
transform: rotate(90deg);
}
margin-right: 0;
}
.actions {
gap: 24px;
}
}

View File

@ -1,4 +1,4 @@
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, OnInit, ViewChild } from '@angular/core';
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, computed, ElementRef, OnInit, signal, ViewChild } from '@angular/core';
import { PermissionsService } from '@services/permissions.service';
import { IconButtonTypes, LoadingService, Toaster } from '@iqser/common-ui';
import { saveAs } from 'file-saver';
@ -40,7 +40,8 @@ export class RulesScreenComponent implements OnInit, ComponentCanDeactivate {
private _fileInput: ElementRef;
private _codeEditor: ICodeEditor;
private _decorations: string[] = [];
private _errorGlyphs: string[] = [];
readonly #errorGlyphs = signal<string[]>([]);
readonly numberOfErrors = computed(() => this.#errorGlyphs().length);
readonly #dossierTemplateId = getParam(DOSSIER_TEMPLATE_ID);
constructor(
@ -68,6 +69,7 @@ export class RulesScreenComponent implements OnInit, ComponentCanDeactivate {
set codeEditorText($event: any) {
this.currentLines = $event.split('\n');
this.codeEditorTextChanged();
this._closeProblemsView();
}
async ngOnInit() {
@ -86,12 +88,16 @@ export class RulesScreenComponent implements OnInit, ComponentCanDeactivate {
@Debounce()
codeEditorTextChanged() {
const newDecorations = this.currentLines.filter(entry => this._isNew(entry)).map(entry => this._makeDecorationFor(entry));
this._decorations = this._codeEditor.deltaDecorations(this._decorations, newDecorations);
}
goToErrors() {
this._codeEditor.trigger(null, 'editor.action.marker.next', null);
}
async save(): Promise<void> {
this._loadingService.start();
this._removeErrorMarkers();
await firstValueFrom(
this._rulesService.uploadRules({
rules: this._codeEditor.getModel().getValue(),
@ -100,7 +106,6 @@ export class RulesScreenComponent implements OnInit, ComponentCanDeactivate {
).then(
async () => {
await this._initialize();
this._removeErrorMarkers();
this._toaster.success(_('rules-screen.success.generic'));
},
error => {
@ -180,17 +185,22 @@ export class RulesScreenComponent implements OnInit, ComponentCanDeactivate {
},
});
});
this._errorGlyphs = this._codeEditor.deltaDecorations(this._errorGlyphs, glyphs);
this.#errorGlyphs.set(this._codeEditor.deltaDecorations(this.#errorGlyphs(), glyphs));
(window as any).monaco.editor.setModelMarkers(model, model.id, markers);
}
private _closeProblemsView() {
this._codeEditor.trigger(null, 'closeMarkersNavigation', null);
}
private _removeErrorMarkers() {
const model = this._codeEditor?.getModel();
if (!model) {
return;
}
(window as any).monaco.editor.setModelMarkers(model, model.id, []);
this._errorGlyphs = this._codeEditor?.deltaDecorations(this._errorGlyphs, []) || [];
this.#errorGlyphs.set(this._codeEditor.deltaDecorations(this.#errorGlyphs(), []) || []);
this._closeProblemsView();
}
private async _initialize() {

View File

@ -7,11 +7,20 @@ import { PendingChangesGuard } from '@guards/can-deactivate.guard';
import { TranslateModule } from '@ngx-translate/core';
import { IconButtonComponent } from '@iqser/common-ui';
import { FormsModule } from '@angular/forms';
import { MatIconModule } from '@angular/material/icon';
const routes = [{ path: '', component: RulesScreenComponent, canDeactivate: [PendingChangesGuard] }];
@NgModule({
declarations: [RulesScreenComponent],
imports: [RouterModule.forChild(routes), CommonModule, MonacoEditorModule, TranslateModule, IconButtonComponent, FormsModule],
imports: [
RouterModule.forChild(routes),
CommonModule,
MonacoEditorModule,
TranslateModule,
IconButtonComponent,
FormsModule,
MatIconModule,
],
})
export class RulesModule {}

View File

@ -0,0 +1,16 @@
import { Injectable } from '@angular/core';
import { GenericService } from '@iqser/common-ui';
import { IComponentRules } from '@red/domain';
@Injectable()
export class ComponentRulesService extends GenericService<IComponentRules> {
protected readonly _defaultModelPath = 'rules';
download(dossierTemplateId: string) {
return this._getOne([dossierTemplateId]);
}
uploadRules(body: IComponentRules) {
return this._post<unknown>(body);
}
}

View File

@ -33,10 +33,9 @@ export class AdminSideNavComponent implements OnInit {
readonly translations = adminSideNavTranslations;
readonly currentUser = getCurrentUser<User>();
readonly roles = Roles;
prefix: string;
readonly isDocumine = this.#config.IS_DOCUMINE;
readonly canAccessRulesInDocumine = this.isDocumine && !this.#config.RULE_EDITOR_DEV_ONLY;
prefix: string;
readonly items: { readonly [key in AdminSideNavType]: NavItem[] } = {
settings: [
{
@ -86,13 +85,13 @@ export class AdminSideNavComponent implements OnInit {
{
screen: 'info',
label: _('admin-side-nav.dossier-template-info'),
helpModeKey: 'dossier_templates_info',
helpModeKey: this.currentUser.isAdmin ? 'dossier_templates_info' : 'user_dossier_template_info',
show: true,
},
{
screen: 'entities',
label: _('admin-side-nav.entities'),
helpModeKey: 'entities',
helpModeKey: this.currentUser.isAdmin ? 'entities' : 'user_dossier_template_entities',
show: true,
},
{
@ -100,6 +99,14 @@ export class AdminSideNavComponent implements OnInit {
label: _('admin-side-nav.rule-editor'),
show: (this.isIqserDevMode || this.canAccessRulesInDocumine) && this._permissionsService.has(Roles.rules.read),
},
{
screen: 'component-rules',
label: _('admin-side-nav.component-rule-editor'),
show:
this.isDocumine &&
(this.isIqserDevMode || this.canAccessRulesInDocumine) &&
this._permissionsService.has(Roles.rules.read),
},
{
screen: 'default-colors',
label: _('admin-side-nav.default-colors'),
@ -127,14 +134,14 @@ export class AdminSideNavComponent implements OnInit {
{
screen: 'dossier-states',
label: _('admin-side-nav.dossier-states'),
helpModeKey: 'dossier_states',
helpModeKey: this.currentUser.isAdmin ? 'dossier_states' : 'user_dossier_template_dossier_states',
show: true,
},
{
screen: 'reports',
label: _('admin-side-nav.reports'),
show: this._permissionsService.has([Roles.reportTemplates.read]),
helpModeKey: 'reports',
helpModeKey: this.currentUser.isAdmin ? 'admin_reports' : 'user_dossier_template_reports',
},
{
screen: 'justifications',
@ -170,7 +177,10 @@ export class AdminSideNavComponent implements OnInit {
],
};
constructor(private readonly _permissionsService: IqserPermissionsService, private readonly _route: ActivatedRoute) {}
constructor(
private readonly _permissionsService: IqserPermissionsService,
private readonly _route: ActivatedRoute,
) {}
@HostBinding('class.smaller') get isSmaller(): boolean {
return [AdminSideNavTypes.dossierTemplates, AdminSideNavTypes.entities].includes(this.type);

View File

@ -106,14 +106,14 @@ export class ConfigService {
label: this._translateService.instant('dossier-listing.quick-filters.owner'),
checker: dossierOwnerQuickChecker(userId),
disabled: entities.filter(dossierOwnerQuickChecker(userId)).length === 0,
helpModeKey: 'filter_dossier_list',
helpModeKey: 'quick_filter_dossiers',
},
{
id: 'member',
label: this._translateService.instant('dossier-listing.quick-filters.member'),
checker: dossierMemberQuickChecker(userId),
disabled: entities.filter(dossierMemberQuickChecker(userId)).length === 0,
helpModeKey: 'filter_dossier_list',
helpModeKey: 'quick_filter_dossiers',
},
].map(filter => new NestedFilter(filter));
}

View File

@ -2,7 +2,7 @@
*ngIf="stats as dossierTemplate"
[class.empty]="dossierTemplate.isEmpty"
[routerLink]="dossierTemplate.isEmpty ? null : ['..', dossierTemplate.dossierTemplateId]"
[attr.help-mode-key]="!dossierTemplate.isEmpty ? 'dossier_overview' : null"
[attr.help-mode-key]="!dossierTemplate.isEmpty ? 'open_dossier_template' : null"
class="dialog"
>
<ng-container *ngIf="!dossierTemplate.isEmpty; else empty">
@ -13,7 +13,7 @@
<mat-icon svgIcon="red:archive"></mat-icon>
<span
[innerHTML]="
'dossier-template-stats.archived-dossiers' | translate : { count: dossierTemplate.numberOfArchivedDossiers }
'dossier-template-stats.archived-dossiers' | translate: { count: dossierTemplate.numberOfArchivedDossiers }
"
></span>
</div>
@ -21,20 +21,18 @@
<mat-icon svgIcon="iqser:trash"></mat-icon>
<span
[innerHTML]="
'dossier-template-stats.deleted-dossiers' | translate : { count: dossierTemplate.numberOfDeletedDossiers }
'dossier-template-stats.deleted-dossiers' | translate: { count: dossierTemplate.numberOfDeletedDossiers }
"
></span>
</div>
<div>
<mat-icon svgIcon="red:user"></mat-icon>
<span
[innerHTML]="'dossier-template-stats.total-people' | translate : { count: dossierTemplate.numberOfPeople }"
></span>
<span [innerHTML]="'dossier-template-stats.total-people' | translate: { count: dossierTemplate.numberOfPeople }"></span>
</div>
<div>
<mat-icon svgIcon="iqser:pages"></mat-icon>
<span
[innerHTML]="'dossier-template-stats.analyzed-pages' | translate : { count: dossierTemplate.numberOfPages }"
[innerHTML]="'dossier-template-stats.analyzed-pages' | translate: { count: dossierTemplate.numberOfPages }"
></span>
</div>
</div>
@ -44,7 +42,7 @@
[config]="translateChartService.translateDossierStates(dossierTemplate.dossiersChartConfig, dossierTemplate.id)"
[radius]="63"
[strokeWidth]="15"
[subtitles]="['dossier-template-stats.active-dossiers' | translate : { count: dossierTemplate.numberOfActiveDossiers }]"
[subtitles]="['dossier-template-stats.active-dossiers' | translate: { count: dossierTemplate.numberOfActiveDossiers }]"
direction="row"
totalType="sum"
></redaction-donut-chart>
@ -76,7 +74,7 @@
*ngIf="permissionsService.canCreateDossier(dossierTemplate)"
[label]="'dashboard.empty-template.new-dossier' | translate"
[type]="iconButtonTypes.primary"
[attr.help-mode-key]="'new_dossier_button'"
[attr.help-mode-key]="'new_dossier'"
icon="iqser:plus"
></iqser-icon-button>
</ng-template>

View File

@ -7,7 +7,7 @@
>
<ng-container slot="right">
<redaction-file-download-btn
[attr.help-mode-key]="'edit_dossier_in_dossier'"
[attr.help-mode-key]="'download_dossier_in_dossier'"
[buttonId]="'download-files-btn'"
[disabled]="downloadBtnDisabled$ | async"
[dossier]="dossier"
@ -17,7 +17,7 @@
<iqser-circle-button
(action)="downloadDossierAsCSV()"
*ngIf="permissionsService.canDownloadCsvReport(dossier)"
[attr.help-mode-key]="'edit_dossier_in_dossier'"
[attr.help-mode-key]="'download_csv'"
[disabled]="listingService.areSomeSelected$ | async"
[icon]="'iqser:csv'"
[tooltip]="'dossier-overview.header-actions.download-csv' | translate"
@ -36,7 +36,7 @@
<iqser-circle-button
(action)="upload.emit()"
*ngIf="permissionsService.canUploadFiles(dossier)"
[attr.help-mode-key]="'edit_dossier_in_dossier'"
[attr.help-mode-key]="'upload_document'"
[buttonId]="'upload-document-btn'"
[icon]="'iqser:upload'"
[tooltip]="'dossier-overview.header-actions.upload-document' | translate"

View File

@ -35,7 +35,6 @@
<div #actionsWrapper class="actions-wrapper">
<redaction-file-actions
*ngIf="!file.isProcessing"
[attr.help-mode-key]="'workflow_view'"
[dossier]="dossier"
[file]="file"
[maxWidth]="width"

View File

@ -116,7 +116,7 @@ export class ConfigService {
action: () => this.#openEditDossierDialog(dossierId),
icon: 'iqser:edit',
hide: !this.#currentUser.isManager && !this._iqserPermissionsService.has(Roles.dossiers.edit),
helpModeKey: 'edit_dossier_in_dossier',
helpModeKey: 'edit_dossier',
disabled$,
},
];
@ -446,28 +446,28 @@ export class ConfigService {
required: true,
checker: this._recentlyModifiedChecker,
disabled: entities.filter(this._recentlyModifiedChecker).length === 0,
helpModeKey: 'filter_document_list',
helpModeKey: 'quick_filters_documents',
},
{
id: 'assigned-to-me',
label: this._translateService.instant('dossier-overview.quick-filters.assigned-to-me'),
checker: this._assignedToMeChecker,
disabled: entities.filter(this._assignedToMeChecker).length === 0,
helpModeKey: 'filter_document_list',
helpModeKey: 'quick_filters_documents',
},
{
id: 'unassigned',
label: this._translateService.instant('dossier-overview.quick-filters.unassigned'),
checker: this._unassignedChecker,
disabled: entities.filter(this._unassignedChecker).length === 0,
helpModeKey: 'filter_document_list',
helpModeKey: 'quick_filters_documents',
},
{
id: 'assigned-to-others',
label: this._translateService.instant('dossier-overview.quick-filters.assigned-to-others'),
checker: this._assignedToOthersChecker,
disabled: entities.filter(this._assignedToOthersChecker).length === 0,
helpModeKey: 'filter_document_list',
helpModeKey: 'quick_filters_documents',
},
].map(filter => new NestedFilter(filter));
}

View File

@ -41,6 +41,7 @@
[noDataIcon]="'iqser:document'"
[noDataText]="'dossier-overview.no-data.title' | translate"
[showNoDataButton]="true"
[id]="'workflow-view'"
addElementColumn="NEW"
>
<ng-template #workflowItemTemplate let-entity="entity">

View File

@ -69,7 +69,7 @@ export class ConfigService {
hide: !this._permissionsService.canCreateDossier(dossierTemplate),
icon: 'iqser:plus',
type: 'primary',
helpModeKey: 'new_dossier_button',
helpModeKey: 'new_dossier',
},
];
}
@ -226,14 +226,14 @@ export class ConfigService {
label: this._translateService.instant('dossier-listing.quick-filters.owner'),
checker: dossierOwnerQuickChecker(userId),
disabled: entities.filter(dossierOwnerQuickChecker(userId)).length === 0,
helpModeKey: 'filter_dossier_list',
helpModeKey: 'quick_filter_dossiers',
},
{
id: 'member',
label: this._translateService.instant('dossier-listing.quick-filters.member'),
checker: dossierMemberQuickChecker(userId),
disabled: entities.filter(dossierMemberQuickChecker(userId)).length === 0,
helpModeKey: 'filter_dossier_list',
helpModeKey: 'quick_filter_dossiers',
},
].map(filter => new NestedFilter(filter));
}

View File

@ -30,20 +30,17 @@
[tooltipPosition]="tooltipPosition"
[tooltip]="'annotation-actions.resize.label' | translate"
[type]="buttonType"
[attr.help-mode-key]="helpModeKey('resize')"
icon="red:resize"
></iqser-circle-button>
<iqser-circle-button
(action)="annotationActionsService.editRedaction(annotations)"
*ngIf="
annotationPermissions.canChangeLegalBasis ||
annotationPermissions.canRecategorizeAnnotation ||
annotationPermissions.canForceRedaction ||
annotationPermissions.canForceHint
"
*ngIf="canEdit"
[tooltipPosition]="tooltipPosition"
[tooltip]="'annotation-actions.edit-redaction.label' | translate"
[type]="buttonType"
[attr.help-mode-key]="helpModeKey('edit')"
icon="iqser:edit"
></iqser-circle-button>
@ -139,10 +136,11 @@
<iqser-circle-button
(action)="removeOrSuggestRemoveRedaction()"
*ngIf="annotationPermissions.canRemoveRedaction"
*ngIf="canRemoveRedaction"
[tooltipPosition]="tooltipPosition"
[tooltip]="'annotation-actions.remove-annotation.remove-redaction' | translate"
[type]="buttonType"
[attr.help-mode-key]="helpModeKey('remove')"
icon="iqser:trash"
></iqser-circle-button>
</ng-container>

View File

@ -1,5 +1,5 @@
import { Component, computed, Input, OnChanges } from '@angular/core';
import { HelpModeService, IqserPermissionsService } from '@iqser/common-ui';
import { getConfig, HelpModeService, IqserPermissionsService } from '@iqser/common-ui';
import { AnnotationPermissions } from '@models/file/annotation.permissions';
import { AnnotationWrapper } from '@models/file/annotation.wrapper';
import { PermissionsService } from '@services/permissions.service';
@ -24,8 +24,6 @@ export type AnnotationButtonType = keyof typeof AnnotationButtonTypes;
styleUrls: ['./annotation-actions.component.scss'],
})
export class AnnotationActionsComponent implements OnChanges {
#annotations: AnnotationWrapper[] = [];
protected _annotationId = '';
@Input() buttonType: AnnotationButtonType = AnnotationButtonTypes.default;
@Input() tooltipPosition: 'before' | 'above' = 'before';
@Input() canPerformAnnotationActions: boolean;
@ -33,10 +31,45 @@ export class AnnotationActionsComponent implements OnChanges {
readonly roles = Roles;
annotationPermissions: AnnotationPermissions;
isImage = true;
protected _annotationId = '';
#annotations: AnnotationWrapper[] = [];
readonly isVisible = computed(() => {
const hidden = this._annotationManager.hidden();
return this.#annotations.reduce((acc, annotation) => !hidden.has(annotation.id) && acc, true);
});
readonly #isDocumine = getConfig().IS_DOCUMINE;
get annotations(): AnnotationWrapper[] {
return this.#annotations;
}
@Input()
set annotations(annotations: AnnotationWrapper[]) {
this.#annotations = annotations.filter(a => a !== undefined);
this.isImage = this.#annotations?.reduce((accumulator, annotation) => annotation.isImage && accumulator, true);
this._annotationId = this.#annotations[0]?.id;
}
get canEdit(): boolean {
const documineCanEditRedactions =
this.annotationPermissions.canChangeLegalBasis ||
this.annotationPermissions.canRecategorizeAnnotation ||
this.annotationPermissions.canForceHint ||
this.annotationPermissions.canForceRedaction;
return this.#isDocumine && this.annotations.length > 1 ? this.annotationPermissions.canEditAnnotations : documineCanEditRedactions;
}
get canRemoveRedaction(): boolean {
return (this.#isDocumine || this.annotations.length === 1) && this.annotationPermissions.canRemoveRedaction;
}
get viewerAnnotations() {
return this._annotationManager.get(this.#annotations);
}
get resizing() {
return this.#annotations?.length === 1 && this.#annotations?.[0].id === this._annotationManager.resizingAnnotationId;
}
constructor(
readonly viewModeService: ViewModeService,
@ -50,31 +83,12 @@ export class AnnotationActionsComponent implements OnChanges {
readonly annotationReferencesService: AnnotationReferencesService,
) {}
get annotations(): AnnotationWrapper[] {
return this.#annotations;
}
@Input()
set annotations(annotations: AnnotationWrapper[]) {
this.#annotations = annotations.filter(a => a !== undefined);
this.isImage = this.#annotations?.reduce((accumulator, annotation) => annotation.isImage && accumulator, true);
this._annotationId = this.#annotations[0]?.id;
}
get viewerAnnotations() {
return this._annotationManager.get(this.#annotations);
}
get resizing() {
return this.#annotations?.length === 1 && this.#annotations?.[0].id === this._annotationManager.resizingAnnotationId;
}
ngOnChanges(): void {
this.#setPermissions();
}
removeOrSuggestRemoveRedaction() {
this.annotationActionsService.removeRedaction(this.annotations[0], this.annotationPermissions);
this.annotationActionsService.removeRedaction(this.annotations, this.annotationPermissions);
}
acceptRecommendation() {
@ -113,4 +127,8 @@ export class AnnotationActionsComponent implements OnChanges {
this._iqserPermissionsService,
);
}
helpModeKey(action: string) {
return this.#isDocumine ? `${action}_annotation` : '';
}
}

View File

@ -4,6 +4,7 @@ import { PdfProxyService } from '../../services/pdf-proxy.service';
import { ActionsHelpModeKeys } from '../../utils/constants';
import { ListItem } from '@models/file/list-item';
import { MultiSelectService } from '../../services/multi-select.service';
import { getConfig } from '@iqser/common-ui';
@Component({
selector: 'redaction-annotation-wrapper',
@ -16,6 +17,7 @@ export class AnnotationWrapperComponent implements OnChanges {
@HostBinding('attr.annotation-id') annotationId: string;
@HostBinding('class.active') active = false;
actionsHelpModeKey?: string;
readonly #isDocumine = getConfig().IS_DOCUMINE;
protected readonly _pdfProxyService = inject(PdfProxyService);
protected readonly _multiSelectService = inject(MultiSelectService);
@ -26,11 +28,14 @@ export class AnnotationWrapperComponent implements OnChanges {
}
#getActionsHelpModeKey(): string {
const type = this.annotation.item.typeLabel?.split('.')[1];
const typeValue = this.annotation.item.typeValue;
if (type === 'hint' && (typeValue === 'ocr' || typeValue === 'formula' || typeValue === 'image')) {
return ActionsHelpModeKeys[`${type}-${typeValue}`];
if (!this.#isDocumine) {
const type = this.annotation.item.typeLabel?.split('.')[1];
const typeValue = this.annotation.item.typeValue;
if (type === 'hint' && (typeValue === 'ocr' || typeValue === 'formula' || typeValue === 'image')) {
return ActionsHelpModeKeys[`${type}-${typeValue}`];
}
return ActionsHelpModeKeys[type];
}
return ActionsHelpModeKeys[type];
return '';
}
}

View File

@ -20,7 +20,7 @@
<div
(click)="multiSelectService.activate()"
*ngIf="multiSelectService.enabled() && multiSelectService.inactive()"
[attr.help-mode-key]="'workload_in_editor'"
[attr.help-mode-key]="'workload_bulk_selection'"
class="all-caps-label primary pointer"
translate="file-preview.tabs.annotations.select"
></div>
@ -28,7 +28,7 @@
<iqser-popup-filter
*ngIf="documentInfoService.hidden()"
[actionsTemplate]="annotationFilterActionTemplate"
[attr.help-mode-key]="'workload_in_editor'"
[attr.help-mode-key]="'workload_filter'"
[fileId]="state.file()?.id"
[primaryFiltersSlug]="'primaryFilters'"
[secondaryFiltersSlug]="'secondaryFilters'"

View File

@ -1,4 +1,4 @@
<div *ngIf="viewedPages$ | async as viewedPages" class="pages" id="pages">
<div *ngIf="viewedPages$ | async as viewedPages" class="pages" id="pages" [attr.help-mode-key]="'workload_page_list'">
<redaction-page-indicator
(pageSelected)="pageSelectedByClick($event)"
*ngFor="let pageNumber of pages; trackBy: trackBy"

View File

@ -32,7 +32,7 @@
<iqser-circle-button
(action)="editingReviewer = true"
*ngIf="_canAssignOrUnassign() && !!file.assignee"
[attr.help-mode-key]="'document_features_in_editor'"
[attr.help-mode-key]="'editor_assign_user'"
[buttonId]="'change-assignee'"
[icon]="'iqser:edit'"
[tooltip]="assignTooltip() | translate"

View File

@ -3,16 +3,26 @@
<div [translate]="'edit-redaction.dialog.title'" class="dialog-header heading-l"></div>
<div class="dialog-content redaction">
<div *ngIf="redactedText" class="iqser-input-group w-450">
<label [translate]="'edit-redaction.dialog.content.redacted-text'" class="selected-text"></label>
{{ redactedText }}
<div *ngIf="showList">
<label
[translate]="'edit-redaction.dialog.content.redacted-text'"
[translateParams]="{ length: redactedTexts.length }"
class="selected-text"
></label>
<cdk-virtual-scroll-viewport
[itemSize]="16"
[ngStyle]="{ height: redactedTexts.length <= 5 ? 16 * redactedTexts.length + 'px' : 80 + 'px' }"
>
<ul *cdkVirtualFor="let text of redactedTexts">
<li>{{ text }}</li>
</ul>
</cdk-virtual-scroll-viewport>
</div>
<div class="iqser-input-group required w-450">
<label [translate]="'edit-redaction.dialog.content.type'"></label>
<mat-form-field>
<mat-select formControlName="type">
<mat-select formControlName="type" [placeholder]="'edit-redaction.dialog.content.unchanged' | translate">
<mat-select-trigger>{{ displayedDictionaryLabel }}</mat-select-trigger>
<mat-option
*ngFor="let dictionary of dictionaries"
@ -29,7 +39,11 @@
<div class="iqser-input-group w-450">
<label [translate]="'edit-redaction.dialog.content.comment'"></label>
<textarea
[placeholder]="'edit-redaction.dialog.content.comment-placeholder' | translate"
[placeholder]="
redactedTexts.length === 1
? ('edit-redaction.dialog.content.comment-placeholder' | translate)
: ('edit-redaction.dialog.content.unchanged' | translate)
"
formControlName="comment"
iqserHasScrollbar
name="comment"
@ -40,7 +54,12 @@
</div>
<div class="dialog-actions">
<iqser-icon-button [label]="'edit-redaction.dialog.actions.save' | translate" [submit]="true" [type]="iconButtonTypes.primary">
<iqser-icon-button
[label]="'edit-redaction.dialog.actions.save' | translate"
[submit]="true"
[type]="iconButtonTypes.primary"
[disabled]="!changed"
>
</iqser-icon-button>
<div [translate]="'edit-redaction.dialog.actions.cancel'" class="all-caps-label cancel" mat-dialog-close></div>

View File

@ -0,0 +1,18 @@
@use 'common-mixins';
cdk-virtual-scroll-viewport {
margin-top: 8px;
@include common-mixins.scroll-bar;
}
:host ::ng-deep .cdk-virtual-scroll-orientation-vertical .cdk-virtual-scroll-content-wrapper {
max-width: 100% !important;
}
li {
white-space: nowrap;
text-overflow: ellipsis;
list-style-position: inside;
overflow: hidden;
padding-right: 4px;
}

View File

@ -10,6 +10,7 @@ import { EditRedactionData, EditRedactResult } from '../../../utils/dialog-types
@Component({
templateUrl: 'edit-annotation-dialog.component.html',
styleUrls: ['edit-annotation-dialog.component.scss'],
})
export class EditAnnotationDialogComponent
extends IqserDialogComponent<EditAnnotationDialogComponent, EditRedactionData, EditRedactResult>
@ -17,7 +18,7 @@ export class EditAnnotationDialogComponent
{
readonly roles = Roles;
readonly iconButtonTypes = IconButtonTypes;
readonly redactedText: string;
readonly redactedTexts: string[];
dictionaries: Dictionary[] = [];
form: UntypedFormGroup;
readonly #dossier: Dossier;
@ -32,9 +33,9 @@ export class EditAnnotationDialogComponent
super();
this.#dossier = _activeDossiersService.find(this.data.dossierId);
const annotations = this.data.annotations;
const firstEntry = annotations[0];
this.redactedText = annotations.length === 1 ? firstEntry.value : null;
this.redactedTexts = annotations.map(annotation => annotation.value);
this.form = this.#getForm();
this.initialFormValue = JSON.parse(JSON.stringify(this.form.getRawValue()));
}
get displayedDictionaryLabel() {
@ -45,6 +46,10 @@ export class EditAnnotationDialogComponent
return null;
}
get showList() {
return this.data.annotations.every(annotation => annotation.isSkipped || annotation.isRedacted);
}
async ngOnInit(): Promise<void> {
this.#setTypes();
}
@ -66,9 +71,10 @@ export class EditAnnotationDialogComponent
}
#getForm(): UntypedFormGroup {
const sameType = new Set(this.data.annotations.map(annotation => annotation.type)).size === 1;
return this._formBuilder.group({
comment: [null],
type: [this.data.annotations[0].type],
type: [sameType ? this.data.annotations[0].type : null],
});
}

View File

@ -1,8 +1,41 @@
<section class="dialog">
<form (submit)="save()" [formGroup]="form">
<div [innerHTML]="'remove-annotation.dialog.title' | translate" class="dialog-header heading-l"></div>
<div
[innerHTML]="'remove-annotation.dialog.title' | translate: { count: data.redactions.length }"
class="dialog-header heading-l"
></div>
<div class="dialog-content redaction">
<div *ngIf="data.redactions.length > 1">
<label
[translateParams]="{ length: redactedTexts.length }"
[translate]="'remove-annotation.dialog.content.redacted-text'"
class="selected-text"
></label>
<cdk-virtual-scroll-viewport
[itemSize]="16"
[ngStyle]="{ height: redactedTexts.length <= 5 ? 16 * redactedTexts.length + 'px' : 80 + 'px' }"
>
<ul *cdkVirtualFor="let text of redactedTexts; let idx = index">
<li>
{{
(isFalsePositive
? 'remove-annotation.dialog.content.list-item-false-positive'
: 'remove-annotation.dialog.content.list-item'
)
| translate
: {
text: text,
context: data.falsePositiveContext[idx]
}
| replaceNbsp
}}
</li>
</ul>
</cdk-virtual-scroll-viewport>
</div>
<div class="dialog-content">
<iqser-details-radio [options]="options" formControlName="option"></iqser-details-radio>
<div class="iqser-input-group w-450">

View File

@ -0,0 +1,14 @@
cdk-virtual-scroll-viewport {
margin-top: 8px;
}
:host ::ng-deep .cdk-virtual-scroll-orientation-vertical .cdk-virtual-scroll-content-wrapper {
max-width: 100% !important;
}
li {
white-space: nowrap;
text-overflow: ellipsis;
list-style-position: inside;
overflow: hidden;
}

View File

@ -1,13 +1,12 @@
import { Component } from '@angular/core';
import { DetailsRadioOption, IconButtonTypes } from '@iqser/common-ui';
import { DetailsRadioOption, IconButtonTypes, IqserDialogComponent } from '@iqser/common-ui';
import { FormBuilder, UntypedFormGroup } from '@angular/forms';
import { IqserDialogComponent } from '@iqser/common-ui';
import { PermissionsService } from '@services/permissions.service';
import { RemoveAnnotationData, RemoveAnnotationResult } from '../../../utils/dialog-types';
import { getRemoveRedactionOptions, RemoveAnnotationOption, RemoveAnnotationOptions } from '../../../utils/dialog-options';
@Component({
templateUrl: './remove-annotation-dialog.component.html',
templateUrl: 'remove-annotation-dialog.component.html',
styleUrls: ['remove-annotation-dialog.component.scss'],
})
export class RemoveAnnotationDialogComponent extends IqserDialogComponent<
RemoveAnnotationDialogComponent,
@ -16,13 +15,12 @@ export class RemoveAnnotationDialogComponent extends IqserDialogComponent<
> {
readonly iconButtonTypes = IconButtonTypes;
readonly options: DetailsRadioOption<RemoveAnnotationOption>[];
readonly redactedTexts: string[];
form!: UntypedFormGroup;
constructor(private readonly _formBuilder: FormBuilder, private readonly _permissionsService: PermissionsService) {
super();
this.options = getRemoveRedactionOptions(this.data, true);
this.form = this.#getForm();
get isFalsePositive(): boolean {
return this.form.get('option').value.value === RemoveAnnotationOptions.FALSE_POSITIVE;
}
get #applyToAllDossiers(): boolean {
@ -30,6 +28,13 @@ export class RemoveAnnotationDialogComponent extends IqserDialogComponent<
return selectedOption === RemoveAnnotationOptions.IN_DOSSIER || selectedOption === RemoveAnnotationOptions.FALSE_POSITIVE;
}
constructor(private readonly _formBuilder: FormBuilder) {
super();
this.options = getRemoveRedactionOptions(this.data, true);
this.redactedTexts = this.data.redactions.map(annotation => annotation.value);
this.form = this.#getForm();
}
save(): void {
this.dialogRef.close({ ...this.form.getRawValue(), applyToAllDossiers: this.#applyToAllDossiers });
}

View File

@ -4,7 +4,7 @@
<div *ngIf="isHintDialog" class="dialog-header heading-l" [translate]="'manual-annotation.dialog.header.force-hint'"></div>
<div class="dialog-content">
<div *ngIf="!isHintDialog" class="iqser-input-group required w-400">
<div *ngIf="!isHintDialog && !isDocumine" class="iqser-input-group required w-400">
<label [translate]="'manual-annotation.dialog.content.reason'"></label>
<mat-form-field>
<mat-select
@ -19,7 +19,7 @@
</mat-form-field>
</div>
<div *ngIf="!isHintDialog" class="iqser-input-group w-400">
<div *ngIf="!isHintDialog && !isDocumine" class="iqser-input-group w-400">
<label [translate]="'manual-annotation.dialog.content.legalBasis'"></label>
<input [value]="form.get('reason').value?.legalBasis" disabled type="text" />
</div>

View File

@ -1,7 +1,7 @@
import { Component, Inject, OnInit } from '@angular/core';
import { UntypedFormGroup, Validators } from '@angular/forms';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { BaseDialogComponent } from '@iqser/common-ui';
import { BaseDialogComponent, getConfig } from '@iqser/common-ui';
import { JustificationsService } from '@services/entity-services/justifications.service';
import { Dossier, ILegalBasisChangeRequest } from '@red/domain';
import { firstValueFrom } from 'rxjs';
@ -19,6 +19,7 @@ export interface LegalBasisOption {
styleUrls: ['./force-annotation-dialog.component.scss'],
})
export class ForceAnnotationDialogComponent extends BaseDialogComponent implements OnInit {
readonly isDocumine = getConfig().IS_DOCUMINE;
legalOptions: LegalBasisOption[] = [];
constructor(

View File

@ -15,16 +15,17 @@ export class RemoveRedactionDialogComponent extends IqserDialogComponent<
RemoveRedactionData,
RemoveRedactionResult
> {
#applyToAllDossiers: boolean;
readonly iconButtonTypes = IconButtonTypes;
readonly options: DetailsRadioOption<RemoveRedactionOption>[];
readonly recommendation = this.data.redaction.isRecommendation;
readonly recommendation;
readonly hint: boolean;
form!: UntypedFormGroup;
hint: boolean;
#applyToAllDossiers: boolean;
constructor(private readonly _formBuilder: FormBuilder) {
super();
this.hint = this.data.redaction.hint;
this.recommendation = this.data.redactions.every(r => r.isRecommendation);
this.hint = this.data.redactions.every(r => r.hint);
this.options = getRemoveRedactionOptions(this.data);
this.form = this.#getForm();
this.#applyToAllDossiers = this.data.applyToAllDossiers ?? true;

View File

@ -2,14 +2,14 @@
<div [translate]="'rss-dialog.title'" class="dialog-header heading-l"></div>
<hr />
<div class="dialog-content">
<div class="dialog-content" id="scm-edit">
<div *ngIf="scmData() as scmEntry" class="table output-data">
<div class="table-header">{{ 'rss-dialog.table-header.component' | translate }}</div>
<div class="table-header">{{ 'rss-dialog.table-header.value' | translate }}</div>
<div class="table-header">{{ 'rss-dialog.table-header.transformation-rule' | translate }}</div>
<div class="table-header">{{ 'rss-dialog.table-header.annotation-references' | translate }}</div>
<ng-container *ngFor="let entry of scmEntry.result | keyvalue : originalOrder; let index = index">
<ng-container *ngFor="let entry of scmEntry.result | keyvalue: originalOrder; let index = index">
<div class="bold">{{ entry.key }}</div>
<div [id]="getValueCellId(index)">
<iqser-editable-input
@ -21,13 +21,15 @@
[parentId]="getValueCellId(index)"
[saveTooltip]="'rss-dialog.actions.save' | translate"
[value]="entry.value.value ?? entry.value.originalValue"
[helpModeKey]="'scm_edit_DIALOG'"
>
<ng-container slot="editing">
<iqser-circle-button
(action)="undo(entry.value.originalKey)"
*ngIf="entry.value.value && canEdit"
[showDot]="true"
[tooltip]="'rss-dialog.actions.undo' | translate : { value: entry.value.originalValue } | replaceNbsp"
[tooltip]="'rss-dialog.actions.undo' | translate: { value: entry.value.originalValue } | replaceNbsp"
[attr.help-mode-key]="'scm_undo_DIALOG'"
class="ml-2"
icon="red:undo"
></iqser-circle-button>
@ -65,12 +67,14 @@
[label]="'rss-dialog.actions.export-json' | translate"
[submit]="true"
[type]="iconButtonTypes.primary"
[attr.help-mode-key]="'scm_export_DIALOG'"
></iqser-icon-button>
<iqser-icon-button
(action)="exportXML()"
[label]="'rss-dialog.actions.export-xml' | translate"
[type]="iconButtonTypes.primary"
[attr.help-mode-key]="'scm_export_DIALOG'"
></iqser-icon-button>
<iqser-icon-button

View File

@ -30,11 +30,12 @@
class="ml-8"
icon="red:extract"
tooltipPosition="below"
[attr.help-mode-key]="'editor_scm'"
></iqser-circle-button>
<redaction-file-actions
[dossier]="state.dossier()"
[fileActionsHelpModeKey]="'editor_document_features'"
[helpModeKeyPrefix]="'editor'"
[file]="file"
[minWidth]="width"
type="file-preview"
@ -53,6 +54,7 @@
[icon]="fullScreen ? 'red:exit-fullscreen' : 'red:fullscreen'"
[tooltip]="'file-preview.fullscreen' | translate"
class="ml-2"
[attr.help-mode-key]="'editor_full_screen'"
></iqser-circle-button>
<!-- Dev Mode Features-->
@ -73,6 +75,7 @@
[tooltip]="'common.close' | translate"
class="ml-8"
icon="iqser:close"
[attr.help-mode-key]="'editor_close'"
></iqser-circle-button>
</div>
</div>

View File

@ -338,6 +338,7 @@ export class FilePreviewScreenComponent
document.documentElement.addEventListener('fullscreenchange', this.fullscreenListener);
this.#openRssDialogIfDefault();
this._viewerHeaderService.resetLayers();
}
ngAfterViewInit() {

View File

@ -71,6 +71,7 @@ import { DocumentUnloadedGuard } from './services/document-unloaded.guard';
import { FilePreviewDialogService } from './services/file-preview-dialog.service';
import { ManualRedactionService } from './services/manual-redaction.service';
import { TablesService } from './services/tables.service';
import { ReplaceNbspPipe } from '@common-ui/pipes/replace-nbsp.pipe';
const routes: IqserRoutes = [
{
@ -154,6 +155,7 @@ const components = [
IqserDenyDirective,
TenantPipe,
LogPipe,
ReplaceNbspPipe,
],
providers: [FilePreviewDialogService, ManualRedactionService, DocumentUnloadedGuard, TablesService],
})

View File

@ -113,10 +113,10 @@ export class AnnotationActionsService {
}));
requests.push(this._manualRedactionService.changeLegalBasis(changeLegalBasisBody, dossierId, fileId));
}
if (result.type !== annotations[0].type || this.#isDocumine) {
const recategorizeBody: List<IRecategorizationRequest> = annotations.map(({ id }) => ({
annotationId: id,
type: result.type,
if (this.#isDocumine) {
const recategorizeBody: List<IRecategorizationRequest> = annotations.map(annotation => ({
annotationId: annotation.id,
type: result.type ?? annotation.type,
}));
requests.push(this._manualRedactionService.recategorizeRedactions(recategorizeBody, dossierId, fileId));
}
@ -134,7 +134,7 @@ export class AnnotationActionsService {
await this.#processObsAndEmit(zip(requests).pipe(log()));
}
async removeRedaction(redaction: AnnotationWrapper, permissions: AnnotationPermissions) {
async removeRedaction(redactions: AnnotationWrapper[], permissions: AnnotationPermissions) {
const removePermissions: RemoveRedactionPermissions = {
canRemoveOnlyHere: permissions.canRemoveOnlyHere,
canRemoveFromDictionary: permissions.canRemoveFromDictionary,
@ -144,9 +144,9 @@ export class AnnotationActionsService {
const isApprover = this._permissionsService.isApprover(this._state.dossier());
const data = {
redaction,
redactions,
dossier: this._state.dossier(),
falsePositiveContext: this.#getFalsePositiveText(redaction),
falsePositiveContext: redactions.map(r => this.#getFalsePositiveText(r)),
permissions: removePermissions,
applyToAllDossiers: isApprover ? dossierTemplate.applyDictionaryUpdatesToAllDossiersByDefault : false,
isApprover,
@ -162,9 +162,9 @@ export class AnnotationActionsService {
result.option.value === RemoveRedactionOptions.FALSE_POSITIVE ||
result.option.value === RemoveRedactionOptions.DO_NOT_RECOMMEND
) {
this.#setAsFalsePositive(redaction, result);
this.#setAsFalsePositive(redactions, result);
} else {
this.#removeRedaction(redaction, result);
this.#removeRedaction(redactions, result);
}
}
@ -393,8 +393,8 @@ export class AnnotationActionsService {
return words;
}
#setAsFalsePositive(redaction: AnnotationWrapper, dialogResult: RemoveRedactionResult) {
const request = {
#setAsFalsePositive(redactions: AnnotationWrapper[], dialogResult: RemoveRedactionResult) {
const requests = redactions.map(redaction => ({
sourceId: redaction.id,
value: this.#getFalsePositiveText(redaction),
type: redaction.type,
@ -406,24 +406,24 @@ export class AnnotationActionsService {
? DictionaryEntryTypes.FALSE_RECOMMENDATION
: DictionaryEntryTypes.FALSE_POSITIVE,
comment: dialogResult.comment ? { text: dialogResult.comment } : null,
};
}));
const { dossierId, fileId } = this._state;
this.#processObsAndEmit(this._manualRedactionService.addAnnotation([request], dossierId, fileId)).then();
this.#processObsAndEmit(this._manualRedactionService.addAnnotation(requests, dossierId, fileId)).then();
}
#removeRedaction(redaction: AnnotationWrapper, dialogResult: RemoveRedactionResult) {
#removeRedaction(redactions: AnnotationWrapper[], dialogResult: RemoveRedactionResult) {
const removeFromDictionary = dialogResult.option.value === RemoveRedactionOptions.IN_DOSSIER;
const body = {
const body = redactions.map(redaction => ({
annotationId: redaction.id,
comment: dialogResult.comment,
removeFromDictionary,
removeFromAllDossiers: !!dialogResult.option.extraOption?.checked || !!dialogResult.applyToAllDossiers,
};
}));
// todo: might not be correct, probably shouldn't get to this point if they are not all the same
const isHint = redactions.every(r => r.isHint);
const { dossierId, fileId } = this._state;
this.#processObsAndEmit(
this._manualRedactionService.removeRedaction([body], dossierId, fileId, removeFromDictionary, redaction.isHint),
).then();
this.#processObsAndEmit(this._manualRedactionService.removeRedaction(body, dossierId, fileId, removeFromDictionary, isHint)).then();
}
#getRemoveRedactionDialog(data: RemoveRedactionData) {

View File

@ -1,7 +1,7 @@
import { effect, Injectable, Signal, signal } from '@angular/core';
import { toObservable } from '@angular/core/rxjs-interop';
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
import { EntitiesService, Toaster } from '@iqser/common-ui';
import { EntitiesService, getConfig, Toaster } from '@iqser/common-ui';
import { AnnotationWrapper } from '@models/file/annotation.wrapper';
import {
ChangeType,
@ -50,6 +50,7 @@ export class FileDataService extends EntitiesService<AnnotationWrapper, Annotati
readonly earmarks: Signal<Map<number, AnnotationWrapper[]>>;
readonly annotations: Signal<AnnotationWrapper[]>;
readonly annotations$: Observable<AnnotationWrapper[]>;
readonly #isDocumine = getConfig().IS_DOCUMINE;
constructor(
private readonly _state: FilePreviewStateService,
@ -220,6 +221,7 @@ export class FileDataService extends EntitiesService<AnnotationWrapper, Annotati
changeLogValues.changeLogType,
redactionLog.legalBasis ?? [],
!!dictionary?.hint,
this.#isDocumine,
);
if (entry.sourceId) {

View File

@ -1,6 +1,6 @@
import { inject, Injectable, NgZone } from '@angular/core';
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
import { IqserPermissionsService } from '@iqser/common-ui';
import { getConfig, IqserPermissionsService } from '@iqser/common-ui';
import { BASE_HREF_FN } from '@iqser/common-ui/lib/utils';
import { AnnotationPermissions } from '@models/file/annotation.permissions';
import { AnnotationWrapper } from '@models/file/annotation.wrapper';
@ -21,6 +21,7 @@ export class PdfAnnotationActionsService {
readonly #annotationActionsService = inject(AnnotationActionsService);
readonly #iqserPermissionsService = inject(IqserPermissionsService);
readonly #annotationManager = inject(REDAnnotationManager);
readonly #isDocumine = getConfig().IS_DOCUMINE;
get(annotations: AnnotationWrapper[]): IHeaderElement[] {
const availableActions: IHeaderElement[] = [];
@ -47,13 +48,13 @@ export class PdfAnnotationActionsService {
availableActions.push(resizeButton);
}
if (
const canEditRedactions =
permissions.canChangeLegalBasis ||
permissions.canRecategorizeAnnotation ||
permissions.canForceHint ||
permissions.canForceRedaction
) {
permissions.canForceRedaction;
const canEdit = this.#isDocumine && annotations.length > 1 ? permissions.canEditAnnotations : canEditRedactions;
if (canEdit) {
const editButton = this.#getButton('edit', _('annotation-actions.edit-redaction.label'), () =>
this.#annotationActionsService.editRedaction(annotations),
);
@ -83,7 +84,7 @@ export class PdfAnnotationActionsService {
if (permissions.canRemoveRedaction) {
const removeRedactionButton = this.#getButton('trash', _('annotation-actions.remove-annotation.remove-redaction'), () =>
this.#annotationActionsService.removeRedaction(annotations[0], permissions),
this.#annotationActionsService.removeRedaction(annotations, permissions),
);
availableActions.push(removeRedactionButton);
}

View File

@ -149,15 +149,16 @@ export const getRemoveRedactionOptions = (
isDocumine: boolean = false,
): DetailsRadioOption<RemoveRedactionOption>[] => {
const translations = isDocumine ? removeAnnotationTranslations : removeRedactionTranslations;
const { permissions, redaction, applyToAllDossiers, isApprover, falsePositiveContext } = data;
const { permissions, redactions, applyToAllDossiers, isApprover, falsePositiveContext } = data;
const isBulk = redactions.length > 1;
const options: DetailsRadioOption<RemoveRedactionOption>[] = [];
if (permissions.canRemoveOnlyHere) {
options.push({
label: translations.ONLY_HERE.label,
description: translations.ONLY_HERE.description,
description: isBulk ? translations.ONLY_HERE.descriptionBulk : translations.ONLY_HERE.description,
descriptionParams: {
value: redaction.value,
value: redactions[0].value,
},
icon: PIN_ICON,
value: RemoveRedactionOptions.ONLY_HERE,
@ -165,9 +166,9 @@ export const getRemoveRedactionOptions = (
}
if (permissions.canRemoveFromDictionary) {
options.push({
label: translations.IN_DOSSIER.label,
description: translations.IN_DOSSIER.description,
descriptionParams: { value: redaction.value, type: redaction.type },
label: isBulk ? translations.IN_DOSSIER.labelBulk : translations.IN_DOSSIER.label,
description: isBulk ? translations.IN_DOSSIER.descriptionBulk : translations.IN_DOSSIER.description,
descriptionParams: { value: redactions[0].value, type: redactions[0].type },
icon: FOLDER_ICON,
value: RemoveRedactionOptions.IN_DOSSIER,
extraOption: !isDocumine
@ -180,11 +181,15 @@ export const getRemoveRedactionOptions = (
});
}
if (permissions.canMarkAsFalsePositive) {
if (data.redaction.isRecommendation) {
if (data.redactions[0].isRecommendation) {
options.push({
label: translations.DO_NOT_RECOMMEND.label,
description: translations.DO_NOT_RECOMMEND.description,
descriptionParams: { value: redaction.value, type: redaction.type, context: falsePositiveContext },
description: isBulk ? translations.DO_NOT_RECOMMEND.descriptionBulk : translations.DO_NOT_RECOMMEND.description,
descriptionParams: {
value: redactions[0].value,
type: redactions[0].type,
context: falsePositiveContext[0],
},
icon: FOLDER_ICON,
value: RemoveRedactionOptions.DO_NOT_RECOMMEND,
extraOption: !isDocumine
@ -198,8 +203,12 @@ export const getRemoveRedactionOptions = (
} else {
options.push({
label: translations.FALSE_POSITIVE.label,
description: translations.FALSE_POSITIVE.description,
descriptionParams: { value: redaction.value, type: redaction.type, context: falsePositiveContext },
description: isBulk ? translations.FALSE_POSITIVE.descriptionBulk : translations.FALSE_POSITIVE.description,
descriptionParams: {
value: redactions[0].value,
type: redactions[0].type,
context: falsePositiveContext[0],
},
icon: REMOVE_FROM_DICT_ICON,
value: RemoveRedactionOptions.FALSE_POSITIVE,
extraOption: !isDocumine

View File

@ -71,9 +71,9 @@ export interface RemoveRedactionPermissions {
}
export interface RemoveRedactionData {
redaction: AnnotationWrapper;
redactions: AnnotationWrapper[];
dossier: Dossier;
falsePositiveContext: string;
falsePositiveContext: string[];
permissions: RemoveRedactionPermissions;
applyToAllDossiers: boolean;
isApprover: boolean;

View File

@ -33,6 +33,11 @@ export class LayersService {
return tooltipsDisabled ? this.#enableIcon : this.#disableIcon;
}
resetLayers() {
this._active = false;
this.#updateButton();
}
async toggleLayers(): Promise<void> {
const layers = await this._documentViewer.document.getLayersArray();
layers.forEach(layer => {
@ -45,6 +50,10 @@ export class LayersService {
this._documentViewer.refreshAndUpdateView();
this._active = !this._active;
this.#updateButton();
}
#updateButton() {
this._pdf.instance.UI.updateElement(HeaderElements.TOGGLE_LAYERS, {
title: this.toggleLayersBtnTitle,
img: this.toggleLayersBtnIcon,

View File

@ -1,11 +1,11 @@
import { inject, Injectable, NgZone } from '@angular/core';
import { getConfig, IqserPermissionsService, isIqserDevMode } from '@iqser/common-ui';
import { getConfig, HelpModeService, IqserPermissionsService, isIqserDevMode } from '@iqser/common-ui';
import { BASE_HREF_FN } from '@iqser/common-ui/lib/utils';
import { TranslateService } from '@ngx-translate/core';
import { IHeaderElement, RotationTypes } from '@red/domain';
import { FilesMapService } from '@services/files/files-map.service';
import { Roles } from '@users/roles';
import { fromEvent, Observable, Subject } from 'rxjs';
import { fromEvent, merge, Observable, Subject } from 'rxjs';
import { filter, map, tap } from 'rxjs/operators';
import { HeaderElements, HeaderElementType } from '../../file-preview/utils/constants';
import { ROTATION_ACTION_BUTTONS, ROTATION_BUTTONS, ViewerEvents } from '../utils/constants';
@ -56,6 +56,7 @@ export class ViewerHeaderService {
private readonly _layersService: LayersService,
private readonly _readableRedactionsService: ReadableRedactionsService,
private readonly _ngZone: NgZone,
private readonly _helpModeService: HelpModeService,
) {
this.events$ = this.#events$.asObservable();
}
@ -224,9 +225,9 @@ export class ViewerHeaderService {
}
get #toggleLoadAnnotations$() {
return this.expandedPanelEvent$.pipe(
tap(isVisible =>
isVisible ? this.enable([HeaderElements.LOAD_ALL_ANNOTATIONS]) : this.disable([HeaderElements.LOAD_ALL_ANNOTATIONS]),
return merge(this.expandedPanelEvent$, this._helpModeService.isHelpModeActive$).pipe(
tap(enable =>
enable ? this.enable([HeaderElements.LOAD_ALL_ANNOTATIONS]) : this.disable([HeaderElements.LOAD_ALL_ANNOTATIONS]),
),
);
}
@ -376,4 +377,8 @@ export class ViewerHeaderService {
this.disable(ROTATION_ACTION_BUTTONS);
}
}
resetLayers() {
this._layersService.resetLayers();
}
}

View File

@ -2,7 +2,7 @@
<iqser-circle-button
(action)="openEditDossierDialog(dossier.id)"
*allow="roles.dossiers.read; if: currentUser.isUser"
[attr.help-mode-key]="'edit_dossier_dossier_info'"
[attr.help-mode-key]="'edit_dossier'"
[icon]="
((iqserPermissionsService.has$(roles.dossiers.edit) | async) && currentUser.isManager) || canEditDossierDictionary
? 'iqser:edit'

View File

@ -11,13 +11,15 @@
</ng-container>
<ng-template #actions (longPress)="forceReanalysisAction($event)" redactionLongPress>
<div [attr.help-mode-key]="fileActionsHelpModeKey" class="file-actions">
<div class="file-actions">
<redaction-expandable-file-actions
[actions]="buttons"
[id]="'actions-for-' + file.fileId"
[maxWidth]="maxWidth"
[minWidth]="minWidth"
[tooltipPosition]="tooltipPosition"
[helpModeKeyPrefix]="helpModeKeyPrefix"
[isDossierOverviewWorkflow]="isDossierOverviewWorkflow"
></redaction-expandable-file-actions>
</div>
</ng-template>

View File

@ -41,7 +41,7 @@ export class FileActionsComponent implements OnChanges {
@Input({ required: true }) type: 'file-preview' | 'dossier-overview-list' | 'dossier-overview-workflow';
@Input() maxWidth: number;
@Input() minWidth: number;
@Input() fileActionsHelpModeKey: 'document_features_in_dossier' | 'editor_document_features' = 'document_features_in_dossier';
@Input() helpModeKeyPrefix: 'dossier' | 'editor' = 'dossier';
readonly currentUser = getCurrentUser<User>();
toggleTooltip?: string;
assignTooltip?: string;
@ -112,6 +112,7 @@ export class FileActionsComponent implements OnChanges {
tooltip: _('dossier-overview.delete.action'),
icon: 'iqser:trash',
show: this.showDelete,
helpModeKey: 'delete_file',
},
{
id: 'assign-btn',
@ -120,6 +121,7 @@ export class FileActionsComponent implements OnChanges {
tooltip: this.assignTooltip,
icon: 'red:assign',
show: this.showAssign,
helpModeKey: 'assign_user',
},
{
id: 'assign-to-me-btn',
@ -128,6 +130,7 @@ export class FileActionsComponent implements OnChanges {
tooltip: _('dossier-overview.assign-me'),
icon: 'red:assign-me',
show: this.showAssignToSelf,
helpModeKey: 'assign_user',
},
{
id: 'open-import-redactions-dialog-btn',
@ -145,6 +148,7 @@ export class FileActionsComponent implements OnChanges {
tooltipClass: 'small',
show: this._permissionsService.canDownloadRedactedFile() && !!this.file.lastProcessed,
disabled: this.file.processingStatus === ProcessingFileStatuses.ERROR,
helpModeKey: 'download',
},
{
id: 'toggle-document-info-btn',
@ -154,6 +158,7 @@ export class FileActionsComponent implements OnChanges {
ariaExpanded: toObservable(this._documentInfoService?.shown, { injector: this._injector }),
icon: 'red:status-info',
show: !!this._documentInfoService,
helpModeKey: 'document_info',
},
{
id: 'toggle-exclude-pages-btn',
@ -172,6 +177,7 @@ export class FileActionsComponent implements OnChanges {
tooltip: _('dossier-overview.back-to-new'),
icon: 'red:undo',
show: this.showSetToNew,
helpModeKey: 'change_status',
},
{
id: 'set-file-under-approval-btn',
@ -180,6 +186,7 @@ export class FileActionsComponent implements OnChanges {
tooltip: _('dossier-overview.under-approval'),
icon: 'red:ready-for-approval',
show: this.showUnderApproval,
helpModeKey: 'change_status',
},
{
id: 'set-file-under-review-btn',
@ -188,6 +195,7 @@ export class FileActionsComponent implements OnChanges {
tooltip: _('dossier-overview.under-review'),
icon: 'red:undo',
show: this.showUnderReview,
helpModeKey: 'change_status',
},
{
id: 'set-file-approved-btn',
@ -197,6 +205,7 @@ export class FileActionsComponent implements OnChanges {
icon: 'red:approved',
disabled: !this.file.canBeApproved,
show: this.showApprove,
helpModeKey: 'change_status',
},
{
id: 'toggle-automatic-analysis-btn',
@ -205,6 +214,7 @@ export class FileActionsComponent implements OnChanges {
tooltip: _('dossier-overview.stop-auto-analysis'),
icon: 'red:disable-analysis',
show: this.canDisableAutoAnalysis,
helpModeKey: 'stop_analysis',
},
{
id: 'reanalyse-file-preview-btn',
@ -215,6 +225,7 @@ export class FileActionsComponent implements OnChanges {
icon: 'iqser:refresh',
show: this.showReanalyseFilePreview,
disabled: this.file.isProcessing,
helpModeKey: 'stop_analysis',
},
{
id: 'toggle-automatic-analysis-btn',
@ -224,6 +235,7 @@ export class FileActionsComponent implements OnChanges {
buttonType: this.isFilePreview ? CircleButtonTypes.warn : CircleButtonTypes.default,
icon: 'red:enable-analysis',
show: this.canEnableAutoAnalysis,
helpModeKey: 'stop_analysis',
},
{
id: 'set-under-approval-btn',
@ -232,6 +244,7 @@ export class FileActionsComponent implements OnChanges {
tooltip: _('dossier-overview.under-approval'),
icon: 'red:undo',
show: this.showUndoApproval,
helpModeKey: 'change_status',
},
{
id: 'ocr-file-btn',
@ -248,6 +261,7 @@ export class FileActionsComponent implements OnChanges {
tooltip: _('dossier-overview.reanalyse.action'),
icon: 'iqser:refresh',
show: this.showReanalyseDossierOverview,
helpModeKey: 'stop_analysis',
},
{
id: 'toggle-analysis-btn',
@ -258,6 +272,7 @@ export class FileActionsComponent implements OnChanges {
class: { 'mr-24': this.isDossierOverviewList },
checked: !this.file.excluded,
show: this.showToggleAnalysis,
helpModeKey: 'disable_extraction',
},
];

View File

@ -2,7 +2,7 @@
<button
[routerLinkActive]="'active'"
[routerLink]="['..', DOSSIERS_ROUTE]"
[attr.help-mode-key]="'dossier_list'"
[attr.help-mode-key]="'active_archived_dossiers'"
class="red-tab"
id="active-button"
>
@ -13,7 +13,7 @@
[disabled]="dossierTemplate.numberOfArchivedDossiers === 0"
[routerLinkActive]="'active'"
[routerLink]="['..', ARCHIVE_ROUTE]"
[attr.help-mode-key]="'dossier_list'"
[attr.help-mode-key]="'active_archived_dossiers'"
class="red-tab"
id="archived-button"
>

View File

@ -11,6 +11,7 @@
[tooltipPosition]="tooltipPosition"
[tooltip]="btn.tooltip | translate"
[type]="btn.buttonType || buttonType"
[attr.help-mode-key]="helpModeKey(btn)"
></iqser-circle-button>
<!-- download redacted file-->
@ -23,6 +24,7 @@
[tooltipClass]="btn.tooltipClass"
[tooltipPosition]="tooltipPosition"
[type]="buttonType"
[attr.help-mode-key]="helpModeKey(btn)"
></redaction-file-download-btn>
<!-- exclude from redaction -->
@ -35,6 +37,7 @@
[matTooltipPosition]="tooltipPosition"
[matTooltip]="btn.tooltip | translate"
[ngClass]="btn.class"
[attr.help-mode-key]="helpModeKey(btn)"
color="primary"
iqserStopPropagation
></mat-slide-toggle>

View File

@ -20,6 +20,8 @@ export class ExpandableFileActionsComponent implements OnChanges {
@Input() minWidth: number;
@Input() buttonType: CircleButtonType;
@Input() tooltipPosition: IqserTooltipPosition;
@Input() helpModeKeyPrefix: 'dossier' | 'editor';
@Input() isDossierOverviewWorkflow = false;
displayedButtons: Action[];
hiddenButtons: Action[];
@ -73,6 +75,12 @@ export class ExpandableFileActionsComponent implements OnChanges {
}
}
helpModeKey(action: Action) {
return action.helpModeKey
? `${this.helpModeKeyPrefix}${this.isDossierOverviewWorkflow ? '_workflow' : ''}_${action.helpModeKey}`
: '';
}
onButtonClick(button: Action, $event: MouseEvent) {
button.action($event);
this.matMenu.closeMenu();

View File

@ -25,7 +25,6 @@ const NOTIFICATIONS_THRESHOLD = 1000;
export class NotificationsService extends EntitiesService<INotification, Notification> implements OnDestroy {
readonly #config = getConfig<AppConfig>();
readonly #subscription = new Subscription();
readonly #filterDocumineNotifications: string[] = [NotificationTypes.ASSIGN_REVIEWER];
protected readonly _defaultModelPath = 'notification';
protected readonly _entityClass = Notification;
@ -119,10 +118,6 @@ export class NotificationsService extends EntitiesService<INotification, Notific
const todayDate = dayjs(new Date());
notifications = notifications.filter(n => {
if (this.#config.IS_DOCUMINE && this.#filterDocumineNotifications.includes(n.notificationType)) {
return false;
}
if (!(n.notificationType in NotificationTypes)) {
return false;
}

View File

@ -2,7 +2,9 @@ import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
export interface DialogOption {
label: string;
labelBulk?: string;
description: string;
descriptionBulk?: string;
extraOptionLabel?: string;
}

View File

@ -6,17 +6,22 @@ export const removeAnnotationTranslations: { [key in RemoveAnnotationOption]: Di
ONLY_HERE: {
label: _('remove-annotation.dialog.content.options.only-here.label'),
description: _('remove-annotation.dialog.content.options.only-here.description'),
descriptionBulk: _('remove-annotation.dialog.content.options.only-here.description-bulk'),
},
IN_DOSSIER: {
label: _('remove-annotation.dialog.content.options.in-dossier.label'),
labelBulk: _('remove-annotation.dialog.content.options.in-dossier.label-bulk'),
description: _('remove-annotation.dialog.content.options.in-dossier.description'),
descriptionBulk: _('remove-annotation.dialog.content.options.in-dossier.description-bulk'),
},
FALSE_POSITIVE: {
label: _('remove-annotation.dialog.content.options.false-positive.label'),
description: _('remove-annotation.dialog.content.options.false-positive.description'),
descriptionBulk: _('remove-annotation.dialog.content.options.false-positive.description-bulk'),
},
DO_NOT_RECOMMEND: {
label: _('remove-redaction.dialog.content.options.do-not-recommend.label'),
description: _('remove-redaction.dialog.content.options.do-not-recommend.description'),
descriptionBulk: _('remove-redaction.dialog.content.options.do-not-recommend.description-bulk'),
},
};

View File

@ -3,7 +3,7 @@
"ADMIN_CONTACT_URL": null,
"API_URL": "https://dan.iqser.cloud",
"APP_NAME": "RedactManager",
"IS_DOCUMINE": false,
"IS_DOCUMINE": true,
"RULE_EDITOR_DEV_ONLY": false,
"AUTO_READ_TIME": 3,
"BACKEND_APP_VERSION": "4.4.40",
@ -18,8 +18,8 @@
"SELECTION_MODE": "structural",
"MANUAL_BASE_URL": "https://docs.redactmanager.com/preview",
"ANNOTATIONS_THRESHOLD": 1000,
"THEME": "redact",
"BASE_TRANSLATIONS_DIRECTORY": "/assets/i18n/redact/",
"THEME": "scm",
"BASE_TRANSLATIONS_DIRECTORY": "/assets/i18n/scm/",
"AVAILABLE_NOTIFICATIONS_DAYS": 30,
"AVAILABLE_OLD_NOTIFICATIONS_MINUTES": 60,
"NOTIFICATIONS_THRESHOLD": 1000,

View File

@ -27,14 +27,19 @@
"documentKey": "filter_dossier_list"
},
{
"elementKey": "navigate_in_breadcrumbs",
"documentKey": "navigate_in_breadcrumbs"
"elementKey": "navigate_breadcrumbs",
"documentKey": "navigate_breadcrumbs"
},
{
"elementKey": "new_dossier_button",
"documentKey": "new_dossier_button",
"overlappingElements": ["USER_MENU"]
},
{
"elementKey": "new_dossier",
"documentKey": "new_dossier",
"overlappingElements": ["USER_MENU"]
},
{
"elementKey": "open_notifications",
"documentKey": "open_notifications"
@ -48,13 +53,8 @@
"documentKey": "views"
},
{
"elementKey": "search_in_entire_application",
"documentKey": "search_in_entire_application"
},
{
"elementKey": "edit_dossier_in_dossier",
"documentKey": "edit_dossier_in_dossier",
"overlappingElements": ["USER_MENU"]
"elementKey": "search_entire_application",
"documentKey": "search_entire_application"
},
{
"elementKey": "document_features_in_editor",
@ -155,8 +155,20 @@
"overlappingElements": ["USER_MENU", "DOCUMENT_INFO"]
},
{
"elementKey": "user_account",
"documentKey": "user_account"
"elementKey": "my_profile",
"documentKey": "my_profile"
},
{
"elementKey": "notification_preferences",
"documentKey": "notification_preferences"
},
{
"elementKey": "user_preferences",
"documentKey": "user_preferences"
},
{
"elementKey": "prompts_and_dialogs",
"documentKey": "prompts_and_dialogs"
},
{
"elementKey": "my_downloads",
@ -167,8 +179,8 @@
"documentKey": "trash"
},
{
"elementKey": "dossier_overview",
"documentKey": "dossier_overview"
"elementKey": "open_dossier_template",
"documentKey": "open_dossier_template"
},
{
"elementKey": "home",
@ -189,7 +201,8 @@
{
"elementKey": "dossier",
"documentKey": "dossier",
"scrollableParentView": "VIRTUAL_SCROLL"
"scrollableParentView": "VIRTUAL_SCROLL",
"overlappingElements": ["BREADCRUMBS_MENU"]
},
{
"elementKey": "document_in_editor",
@ -198,7 +211,8 @@
},
{
"elementKey": "dossier_list",
"documentKey": "dossier_list"
"documentKey": "dossier_list",
"overlappingElements": ["BREADCRUMBS_MENU"]
},
{
"elementKey": "dossier_templates",
@ -209,6 +223,10 @@
"documentKey": "edit_clone_delete_dossier_templates",
"overlappingElements": ["USER_MENU"]
},
{
"elementKey": "user_dossier_template_info",
"documentKey": "user_dossier_template_info"
},
{
"elementKey": "dossier_templates_info",
"documentKey": "dossier_templates_info"
@ -217,6 +235,10 @@
"elementKey": "entities",
"documentKey": "entities"
},
{
"elementKey": "user_dossier_template_entities",
"documentKey": "user_dossier_template_entities"
},
{
"elementKey": "edit_delete_entities",
"documentKey": "edit_delete_entities",
@ -279,6 +301,10 @@
"documentKey": "edit_delete_dossier_attributes",
"overlappingElements": ["USER_MENU"]
},
{
"elementKey": "user_dossier_template_dossier_states",
"documentKey": "user_dossier_template_dossier_states"
},
{
"elementKey": "dossier_states",
"documentKey": "dossier_states"
@ -296,6 +322,14 @@
"documentKey": "reports",
"overlappingElements": ["USER_MENU"]
},
{
"elementKey": "user_dossier_template_reports",
"documentKey": "user_dossier_template_reports"
},
{
"elementKey": "admin_reports",
"documentKey": "reports"
},
{
"elementKey": "justifications",
"documentKey": "justifications"
@ -375,5 +409,192 @@
{
"elementKey": "edit-file-attributes",
"documentKey": "document_list"
},
{
"elementKey": "active_archived_dossiers",
"documentKey": "active_archived_dossiers"
},
{
"elementKey": "quick_filter_dossiers",
"documentKey": "quick_filter_dossiers"
},
{
"elementKey": "edit_dossier",
"documentKey": "edit_dossier"
},
{
"elementKey": "download_dossier_in_dossier",
"documentKey": "download_dossier",
"overlappingElements": ["USER_MENU"]
},
{
"elementKey": "download_csv",
"documentKey": "download_csv",
"overlappingElements": ["USER_MENU"]
},
{
"elementKey": "upload_document",
"documentKey": "upload_document",
"overlappingElements": ["USER_MENU"]
},
{
"elementKey": "close_dossier",
"documentKey": "close_dossier",
"overlappingElements": ["USER_MENU"]
},
{
"elementKey": "quick_filters_documents",
"documentKey": "quick_filters_documents"
},
{
"elementKey": "filter_documents",
"documentKey": "filter_documents"
},
{
"elementKey": "dossier_delete_file",
"documentKey": "dossier_delete_file",
"scrollableParentView": "VIRTUAL_SCROLL"
},
{
"elementKey": "dossier_assign_user",
"documentKey": "dossier_assign_user",
"scrollableParentView": "VIRTUAL_SCROLL"
},
{
"elementKey": "dossier_change_status",
"documentKey": "dossier_change_status",
"scrollableParentView": "VIRTUAL_SCROLL"
},
{
"elementKey": "dossier_stop_analysis",
"documentKey": "dossier_stop_analysis",
"scrollableParentView": "VIRTUAL_SCROLL"
},
{
"elementKey": "dossier_download",
"documentKey": "dossier_download",
"scrollableParentView": "VIRTUAL_SCROLL"
},
{
"elementKey": "dossier_disable_extraction",
"documentKey": "dossier_disable_extraction",
"scrollableParentView": "VIRTUAL_SCROLL"
},
{
"elementKey": "dossier_workflow_delete_file",
"documentKey": "dossier_delete_file",
"scrollableParentView": "WORKFLOW_VIEW"
},
{
"elementKey": "dossier_workflow_assign_user",
"documentKey": "dossier_assign_user",
"scrollableParentView": "WORKFLOW_VIEW"
},
{
"elementKey": "dossier_workflow_change_status",
"documentKey": "dossier_change_status",
"scrollableParentView": "WORKFLOW_VIEW"
},
{
"elementKey": "dossier_workflow_stop_analysis",
"documentKey": "dossier_stop_analysis",
"scrollableParentView": "WORKFLOW_VIEW"
},
{
"elementKey": "dossier_workflow_download",
"documentKey": "dossier_download",
"scrollableParentView": "WORKFLOW_VIEW"
},
{
"elementKey": "dossier_workflow_disable_extraction",
"documentKey": "dossier_disable_extraction",
"scrollableParentView": "WORKFLOW_VIEW"
},
{
"elementKey": "workload_page_list",
"documentKey": "workload_page_list"
},
{
"elementKey": "editor_delete_file",
"documentKey": "editor_delete_file"
},
{
"elementKey": "editor_assign_user",
"documentKey": "editor_assign_user"
},
{
"elementKey": "editor_change_status",
"documentKey": "editor_change_status"
},
{
"elementKey": "editor_stop_analysis",
"documentKey": "editor_stop_analysis"
},
{
"elementKey": "editor_download",
"documentKey": "editor_download"
},
{
"elementKey": "editor_disable_extraction",
"documentKey": "editor_disable_extraction"
},
{
"elementKey": "editor_scm",
"documentKey": "editor_scm"
},
{
"elementKey": "editor_document_info",
"documentKey": "editor_document_info"
},
{
"elementKey": "editor_full_screen",
"documentKey": "editor_full_screen"
},
{
"elementKey": "editor_close",
"documentKey": "editor_close"
},
{
"elementKey": "scm_edit_DIALOG",
"documentKey": "scm_edit",
"scrollableParentView": "SCM_EDIT_DIALOG"
},
{
"elementKey": "scm_undo_DIALOG",
"documentKey": "scm_undo",
"scrollableParentView": "SCM_EDIT_DIALOG"
},
{
"elementKey": "scm_export_DIALOG",
"documentKey": "scm_export"
},
{
"elementKey": "resize_annotation",
"documentKey": "resize_annotation",
"scrollableParentView": "ANNOTATIONS_LIST",
"overlappingElements": ["USER_MENU", "WORKLOAD_FILTER", "DOCUMENT_INFO"]
},
{
"elementKey": "edit_annotation",
"documentKey": "edit_annotation",
"scrollableParentView": "ANNOTATIONS_LIST",
"overlappingElements": ["USER_MENU", "WORKLOAD_FILTER", "DOCUMENT_INFO"]
},
{
"elementKey": "remove_annotation",
"documentKey": "remove_annotation",
"scrollableParentView": "ANNOTATIONS_LIST",
"overlappingElements": ["USER_MENU", "WORKLOAD_FILTER", "DOCUMENT_INFO"]
},
{
"elementKey": "workload_filter",
"documentKey": "workload_filter",
"overlappingElements": ["USER_MENU", "DOCUMENT_INFO"]
},
{
"elementKey": "workload_bulk_selection",
"documentKey": "workload_bulk_selection",
"overlappingElements": ["USER_MENU", "DOCUMENT_INFO"]
}
]

View File

@ -234,6 +234,7 @@
},
"admin-side-nav": {
"audit": "Audit",
"component-rule-editor": "",
"configurations": "Configurations",
"default-colors": "Default Colors",
"dictionary": "Dictionary",
@ -557,6 +558,19 @@
"title": "Aktion bestätigen"
}
},
"component-rules-screen": {
"error": {
"generic": ""
},
"errors-found": "",
"revert-changes": "",
"save-changes": "",
"success": {
"generic": ""
},
"title": "",
"warning-text": ""
},
"configurations": "Einstellungen",
"confirm-archive-dossier": {
"archive": "Archive Dossier",
@ -1237,7 +1251,8 @@
"reason": "Reason",
"redacted-text": "Redacted text",
"section": "Paragraph / Location",
"type": "Type"
"type": "Type",
"unchanged": ""
},
"title": "Edit {type, select, image{Image} hint{Hint} other{Redaction}}"
}
@ -2019,22 +2034,29 @@
"save": "Save"
},
"content": {
"comment": "Comment",
"comment-placeholder": "Add remarks or mentions ...",
"comment": "",
"comment-placeholder": "",
"list-item": "",
"list-item-false-positive": "",
"options": {
"false-positive": {
"description": "\"{value}\" is not a \"{type}\" in this context: \"{context}\".",
"label": "False positive"
"description": "",
"description-bulk": "",
"label": ""
},
"in-dossier": {
"description": "Do not annotate \"{value}\" as \"{type}\" in any dossier.",
"label": "No longer annotate as \"{type}\""
"description": "",
"description-bulk": "",
"label": "",
"label-bulk": ""
},
"only-here": {
"description": "Do not annotate \"{value}\" at this position in the current document.",
"label": "Remove here"
"description": "",
"description-bulk": "",
"label": ""
}
}
},
"redacted-text": ""
},
"title": "Remove annotation"
}
@ -2050,9 +2072,10 @@
"comment-placeholder": "Add remarks or mentions ...",
"options": {
"do-not-recommend": {
"description": "Do not recommend \"{value}\" as {type} in any document of the current dossier.",
"extraOptionLabel": "Apply to all dossiers",
"label": "Remove from dossier"
"description": "",
"description-bulk": "",
"extraOptionLabel": "",
"label": ""
},
"false-positive": {
"description": "\"{value}\" is not a {type} in this context: \"{context}\".",
@ -2212,6 +2235,7 @@
"error": {
"generic": "Es ist ein Fehler aufgetreten ... Die Regeln konnten nicht aktualisiert werden!"
},
"errors-found": "",
"revert-changes": "Anmeldedaten speichern",
"save-changes": "Änderungen speichern",
"success": {

View File

@ -234,6 +234,7 @@
},
"admin-side-nav": {
"audit": "Audit",
"component-rule-editor": "",
"configurations": "Configurations",
"default-colors": "Default Colors",
"dictionary": "Dictionary",
@ -557,6 +558,19 @@
"title": "Confirm Action"
}
},
"component-rules-screen": {
"error": {
"generic": ""
},
"errors-found": "",
"revert-changes": "",
"save-changes": "",
"success": {
"generic": ""
},
"title": "",
"warning-text": ""
},
"configurations": "Configurations",
"confirm-archive-dossier": {
"archive": "Archive Dossier",
@ -1237,7 +1251,8 @@
"reason": "Reason",
"redacted-text": "Redacted text",
"section": "Paragraph / Location",
"type": "Type"
"type": "Type",
"unchanged": "Unchanged"
},
"title": "Edit {type, select, image{Image} hint{Hint} other{Redaction}}"
}
@ -2021,22 +2036,29 @@
"content": {
"comment": "Comment",
"comment-placeholder": "Add remarks or mentions ...",
"list-item": "",
"list-item-false-positive": "",
"options": {
"false-positive": {
"description": "\"{value}\" is not a \"{type}\" in this context: \"{context}\".",
"description-bulk": "",
"label": "False positive"
},
"in-dossier": {
"description": "Do not annotate \"{value}\" as \"{type}\" in any dossier.",
"label": "No longer annotate as \"{type}\""
"description-bulk": "",
"label": "No longer annotate as \"{type}\"",
"label-bulk": ""
},
"only-here": {
"description": "Do not annotate \"{value}\" at this position in the current document.",
"description-bulk": "",
"label": "Remove here"
}
}
},
"redacted-text": ""
},
"title": "Remove annotation"
"title": "Remove {count, plural, one{annotation} other {annotations}}"
}
},
"remove-redaction": {
@ -2051,6 +2073,7 @@
"options": {
"do-not-recommend": {
"description": "Do not recommend \"{value}\" as {type} in any document of the current dossier.",
"description-bulk": "",
"extraOptionLabel": "Apply to all dossiers",
"label": "Remove from dossier"
},
@ -2209,16 +2232,17 @@
"title": "Structured Component Management"
},
"rules-screen": {
"title": "Rule Editor",
"warning-text": "Warning: experimental feature!",
"error": {
"generic": "Something went wrong... Rules update failed!"
},
"errors-found": "{errors, plural, one{An error} other{{errors} errors}} found in rules",
"revert-changes": "Revert",
"save-changes": "Save Changes",
"success": {
"generic": "Rules updated!"
}
},
"title": "Rule Editor",
"warning-text": "Warning: experimental feature!"
},
"search-screen": {
"cols": {

View File

@ -234,6 +234,7 @@
},
"admin-side-nav": {
"audit": "",
"component-rule-editor": "",
"configurations": "",
"default-colors": "",
"dictionary": "",
@ -557,6 +558,19 @@
"title": "Aktion bestätigen"
}
},
"component-rules-screen": {
"error": {
"generic": ""
},
"errors-found": "",
"revert-changes": "",
"save-changes": "",
"success": {
"generic": ""
},
"title": "",
"warning-text": ""
},
"configurations": "Einstellungen",
"confirm-archive-dossier": {
"archive": "",
@ -1237,7 +1251,8 @@
"reason": "",
"redacted-text": "",
"section": "",
"type": ""
"type": "",
"unchanged": ""
},
"title": ""
}
@ -2021,20 +2036,27 @@
"content": {
"comment": "",
"comment-placeholder": "",
"list-item": "",
"list-item-false-positive": "",
"options": {
"false-positive": {
"description": "",
"description-bulk": "",
"label": ""
},
"in-dossier": {
"description": "",
"label": ""
"description-bulk": "",
"label": "",
"label-bulk": ""
},
"only-here": {
"description": "",
"description-bulk": "",
"label": ""
}
}
},
"redacted-text": ""
},
"title": ""
}
@ -2051,6 +2073,7 @@
"options": {
"do-not-recommend": {
"description": "",
"description-bulk": "",
"extraOptionLabel": "",
"label": ""
},
@ -2209,16 +2232,17 @@
"title": ""
},
"rules-screen": {
"title": "",
"warning-text": "",
"error": {
"generic": "Es ist ein Fehler aufgetreten ... Die Regeln konnten nicht aktualisiert werden!"
},
"errors-found": "",
"revert-changes": "Anmeldedaten speichern",
"save-changes": "Änderungen speichern",
"success": {
"generic": "Die Regeln wurden aktualisiert!"
}
},
"title": "",
"warning-text": ""
},
"search-screen": {
"cols": {

View File

@ -234,6 +234,7 @@
},
"admin-side-nav": {
"audit": "Audit",
"component-rule-editor": "Component Rule Editor",
"configurations": "Configurations",
"default-colors": "Default Colors",
"dictionary": "Dictionary",
@ -557,6 +558,19 @@
"title": "Confirm Action"
}
},
"component-rules-screen": {
"error": {
"generic": "Something went wrong... Component rules update failed!"
},
"errors-found": "{errors, plural, one{An error} other{{errors} errors}} found in rules",
"revert-changes": "Revert",
"save-changes": "Save Changes",
"success": {
"generic": "Component rules updated!"
},
"title": "Component Rule Editor",
"warning-text": "Warning: experimental feature!"
},
"configurations": "Configurations",
"confirm-archive-dossier": {
"archive": "Archive Dossier",
@ -1235,9 +1249,10 @@
}
},
"reason": "",
"redacted-text": "Annotated text",
"redacted-text": "Selected {length, plural, one{annotation} other {annotations}}",
"section": "",
"type": "Type"
"type": "Type",
"unchanged": "Unchanged"
},
"title": "Edit annotation"
}
@ -2021,22 +2036,29 @@
"content": {
"comment": "Comment",
"comment-placeholder": "Add remarks or mentions ...",
"list-item": "{text}",
"list-item-false-positive": "\"{text}\" in the context: \"{context}\"",
"options": {
"false-positive": {
"description": "\"{value}\" is not a \"{type}\" in this context: \"{context}\".",
"description-bulk": "The selected items should not be annotated in their respective contexts.",
"label": "False positive"
},
"in-dossier": {
"description": "Do not annotate \"{value}\" as \"{type}\" in any dossier.",
"label": "No longer annotate as \"{type}\""
"description-bulk": "Do not annotate the selected terms as their respective types in any dossier.",
"label": "No longer annotate as \"{type}\"",
"label-bulk": "No longer annotate in any dossier"
},
"only-here": {
"description": "Do not annotate \"{value}\" at this position in the current document.",
"description-bulk": "Do not annotate the selected terms at this position in the current document.",
"label": "Remove here"
}
}
},
"redacted-text": "Selected annotations"
},
"title": "Remove annotation"
"title": "Remove {count, plural, one{annotation} other {annotations}}"
}
},
"remove-redaction": {
@ -2050,9 +2072,10 @@
"comment-placeholder": "Add remarks or mentions ...",
"options": {
"do-not-recommend": {
"description": "",
"extraOptionLabel": "",
"label": ""
"description": "Do not recommend \"{value}\" as {type} in any document of the current dossier.",
"description-bulk": "Do not recommend the selected values as their respective types in any document of the current dossier.",
"extraOptionLabel": "Apply to all dossiers",
"label": "Remove from dossier"
},
"false-positive": {
"description": "\"{value}\" is not a {type} in this context: \"{context}\".",
@ -2209,16 +2232,17 @@
"title": "Component View"
},
"rules-screen": {
"title": "Rule Editor",
"warning-text": "Warning: experimental feature!",
"error": {
"generic": "Something went wrong... Rules update failed!"
},
"errors-found": "{errors, plural, one{An error} other{{errors} errors}} found in rules",
"revert-changes": "Revert",
"save-changes": "Save Changes",
"success": {
"generic": "Rules updated!"
}
},
"title": "Rule Editor",
"warning-text": "Warning: experimental feature!"
},
"search-screen": {
"cols": {

View File

@ -4,6 +4,7 @@ body.light {
--iqser-primary-2: #4d97dd;
--iqser-red-1: #4d4fdd;
--iqser-red-2: #4d97dd;
--iqser-helpmode-primary: #fdbd00;
/*Override the default light theme here*/
/*Copy CSS variables from theme-template.css file*/
}
@ -14,6 +15,7 @@ body.dark {
--iqser-primary-2: #4d97dd;
--iqser-red-1: #4d4fdd;
--iqser-red-2: #4d97dd;
--iqser-helpmode-primary: #fdbd00;
/*Override the default dark theme here*/
/*Copy CSS variables from theme-template.css file*/
}

View File

@ -0,0 +1,13 @@
/**
* Object containing a string of Drools rules.
*/
export interface IComponentRules {
/**
* The DossierTemplate Id for these rules
*/
dossierTemplateId?: string;
/**
* The actual string of rules.
*/
rules?: string;
}

View File

@ -11,3 +11,4 @@ export * from './admin-side-nav-types';
export * from './charts';
export * from './app-config';
export * from './system-preferences';
export * from './component-rules';