Merge branch 'master' into DM-370

This commit is contained in:
Nicoleta Panaghiu 2023-09-07 15:37:00 +03:00
commit b4892afff5
11 changed files with 139 additions and 35 deletions

View File

@ -29,6 +29,7 @@ export class AnnotationPermissions {
canResizeAnnotation = true;
canRecategorizeAnnotation = true;
canForceHint = true;
canEditAnnotations = true;
static forUser(
isApprover: boolean,
@ -58,6 +59,7 @@ export class AnnotationPermissions {
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 +79,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

@ -13,27 +13,35 @@ 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: './rules-screen.component.html',
styleUrls: ['./rules-screen.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class RulesScreenComponent implements OnInit, ComponentCanDeactivate {
@ViewChild('fileInput')
private _fileInput: ElementRef;
private _codeEditor: ICodeEditor;
private _decorations: string[] = [];
readonly #dossierTemplateId = getParam(DOSSIER_TEMPLATE_ID);
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[] = [];
private _errorGlyphs: string[] = [];
readonly #dossierTemplateId = getParam(DOSSIER_TEMPLATE_ID);
constructor(
readonly permissionsService: PermissionsService,
@ -92,9 +100,12 @@ export class RulesScreenComponent implements OnInit, ComponentCanDeactivate {
).then(
async () => {
await this._initialize();
this._removeErrorMarkers();
this._toaster.success(_('rules-screen.success.generic'));
},
() => {
error => {
const errors = error.error as SyntaxError[] | undefined;
this._drawErrorMarkers(errors);
this._loadingService.stop();
this._toaster.error(_('rules-screen.error.generic'));
},
@ -104,6 +115,7 @@ export class RulesScreenComponent implements OnInit, ComponentCanDeactivate {
revert(): void {
this.currentLines = this.initialLines;
this._decorations = this._codeEditor?.deltaDecorations(this._decorations, []) || [];
this._removeErrorMarkers();
this._changeDetectorRef.detectChanges();
this._loadingService.stop();
}
@ -142,6 +154,45 @@ export class RulesScreenComponent implements OnInit, ComponentCanDeactivate {
} 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 = this._codeEditor.deltaDecorations(this._errorGlyphs, glyphs);
(window as any).monaco.editor.setModelMarkers(model, model.id, markers);
}
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, []) || [];
}
private async _initialize() {
this._loadingService.start();
await firstValueFrom(this._rulesService.download(this.#dossierTemplateId)).then(

View File

@ -35,12 +35,7 @@
<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"

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';
@ -37,6 +37,7 @@ export class AnnotationActionsComponent implements OnChanges {
const hidden = this._annotationManager.hidden();
return this.#annotations.reduce((acc, annotation) => !hidden.has(annotation.id) && acc, true);
});
readonly #isDocumine = getConfig().IS_DOCUMINE;
constructor(
readonly viewModeService: ViewModeService,
@ -54,6 +55,15 @@ export class AnnotationActionsComponent implements OnChanges {
return this.#annotations;
}
get canEdit() {
const canEditRedactions =
this.annotationPermissions.canChangeLegalBasis ||
this.annotationPermissions.canRecategorizeAnnotation ||
this.annotationPermissions.canForceHint ||
this.annotationPermissions.canForceRedaction;
return this.#isDocumine && this.annotations.length > 1 ? this.annotationPermissions.canEditAnnotations : canEditRedactions;
}
@Input()
set annotations(annotations: AnnotationWrapper[]) {
this.#annotations = annotations.filter(a => a !== undefined);

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,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

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

@ -114,9 +114,9 @@ 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,
const recategorizeBody: List<IRecategorizationRequest> = annotations.map(annotation => ({
annotationId: annotation.id,
type: result.type ?? annotation.type,
}));
requests.push(this._manualRedactionService.recategorizeRedactions(recategorizeBody, dossierId, fileId));
}

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,7 +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[] = [];
const permissions = this.#getAnnotationsPermissions(annotations);
@ -47,13 +47,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),
);

View File

@ -1235,9 +1235,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"
}

View File

@ -39,6 +39,11 @@
src: url('./assets/styles/fonts/Inter-VariableFont.ttf') format('truetype');
}
.error-glyph-margin {
background: url('./assets/icons/general/alert-circle.svg') no-repeat center;
background-size: 80%;
}
@include common-variables.configureLight(
$iqser-primary: vars.$primary,
$iqser-primary-rgb: common-functions.hexToRgb(vars.$primary),