From 0350eb4124ee8b9d5964d8eafdcdf2ae939e907d Mon Sep 17 00:00:00 2001 From: Valentin Mihai Date: Fri, 16 Jun 2023 22:06:52 +0300 Subject: [PATCH 01/10] RED-6774 - added redact dialog --- .../file/manual-redaction-entry.wrapper.ts | 2 + .../redact-text-dialog.component.html | 87 ++++++++++ .../redact-text-dialog.component.scss | 4 + .../redact-text-dialog.component.ts | 163 ++++++++++++++++++ .../redact-text-dialog/redact-text-options.ts | 6 + .../file-preview-screen.component.ts | 52 ++++-- .../file-preview/file-preview.module.ts | 2 + .../services/file-preview-dialog.service.ts | 7 +- .../services/pdf-proxy.service.ts | 24 ++- .../modules/file-preview/utils/constants.ts | 2 + .../src/app/modules/icons/icons.module.ts | 1 + .../services/document-viewer.service.ts | 12 +- .../translations/redact-text-translations.ts | 29 ++++ apps/red-ui/src/assets/i18n/redact/de.json | 41 +++++ apps/red-ui/src/assets/i18n/redact/en.json | 41 +++++ apps/red-ui/src/assets/i18n/scm/de.json | 41 +++++ apps/red-ui/src/assets/i18n/scm/en.json | 41 +++++ .../icons/general/pdftron-action-add-hint.svg | 11 ++ .../general/pdftron-action-add-redaction.svg | 2 +- .../src/assets/icons/general/push-pin.svg | 3 + 20 files changed, 552 insertions(+), 19 deletions(-) create mode 100644 apps/red-ui/src/app/modules/file-preview/dialogs/redact-text-dialog/redact-text-dialog.component.html create mode 100644 apps/red-ui/src/app/modules/file-preview/dialogs/redact-text-dialog/redact-text-dialog.component.scss create mode 100644 apps/red-ui/src/app/modules/file-preview/dialogs/redact-text-dialog/redact-text-dialog.component.ts create mode 100644 apps/red-ui/src/app/modules/file-preview/dialogs/redact-text-dialog/redact-text-options.ts create mode 100644 apps/red-ui/src/app/translations/redact-text-translations.ts create mode 100644 apps/red-ui/src/assets/icons/general/pdftron-action-add-hint.svg create mode 100644 apps/red-ui/src/assets/icons/general/push-pin.svg diff --git a/apps/red-ui/src/app/models/file/manual-redaction-entry.wrapper.ts b/apps/red-ui/src/app/models/file/manual-redaction-entry.wrapper.ts index b197144fe..ceb44ba60 100644 --- a/apps/red-ui/src/app/models/file/manual-redaction-entry.wrapper.ts +++ b/apps/red-ui/src/app/models/file/manual-redaction-entry.wrapper.ts @@ -2,6 +2,8 @@ import { IManualRedactionEntry } from '@red/domain'; export const ManualRedactionEntryTypes = { DICTIONARY: 'DICTIONARY', + REDACT: 'REDACT', + HINT: 'HINT', REDACTION: 'REDACTION', FALSE_POSITIVE: 'FALSE_POSITIVE', } as const; diff --git a/apps/red-ui/src/app/modules/file-preview/dialogs/redact-text-dialog/redact-text-dialog.component.html b/apps/red-ui/src/app/modules/file-preview/dialogs/redact-text-dialog/redact-text-dialog.component.html new file mode 100644 index 000000000..92af9cacc --- /dev/null +++ b/apps/red-ui/src/app/modules/file-preview/dialogs/redact-text-dialog/redact-text-dialog.component.html @@ -0,0 +1,87 @@ +
+
+
+ +
+
+ + {{ form.get('selectedText').value }} +
+ + + + +
+ + + + + {{ option.label }} + + + +
+ +
+ + +
+
+ + +
+ + + + + {{ displayedDictionaryLabel }} + + {{ dictionary.label }} + + + +
+
+ +
+ + +
+
+ +
+ + + +
+
+
+ + +
diff --git a/apps/red-ui/src/app/modules/file-preview/dialogs/redact-text-dialog/redact-text-dialog.component.scss b/apps/red-ui/src/app/modules/file-preview/dialogs/redact-text-dialog/redact-text-dialog.component.scss new file mode 100644 index 000000000..84db58094 --- /dev/null +++ b/apps/red-ui/src/app/modules/file-preview/dialogs/redact-text-dialog/redact-text-dialog.component.scss @@ -0,0 +1,4 @@ +label { + font-weight: bold; + padding-bottom: 8px; +} diff --git a/apps/red-ui/src/app/modules/file-preview/dialogs/redact-text-dialog/redact-text-dialog.component.ts b/apps/red-ui/src/app/modules/file-preview/dialogs/redact-text-dialog/redact-text-dialog.component.ts new file mode 100644 index 000000000..83e36d099 --- /dev/null +++ b/apps/red-ui/src/app/modules/file-preview/dialogs/redact-text-dialog/redact-text-dialog.component.ts @@ -0,0 +1,163 @@ +import { Component, Inject, OnInit } from '@angular/core'; +import { BaseDialogComponent, DetailsRadioOption, IqserPermissionsService } from '@iqser/common-ui'; +import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; +import { Dictionary, Dossier, File, IAddRedactionRequest } from '@red/domain'; +import { UntypedFormGroup, Validators } from '@angular/forms'; +import { Roles } from '@users/roles'; +import { firstValueFrom } from 'rxjs'; +import { JustificationsService } from '@services/entity-services/justifications.service'; +import { ActiveDossiersService } from '@services/dossiers/active-dossiers.service'; +import { LegalBasisOption } from '../manual-redaction-dialog/manual-annotation-dialog.component'; +import { DictionaryService } from '@services/entity-services/dictionary.service'; +import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker'; +import { ManualRedactionEntryType, ManualRedactionEntryWrapper } from '@models/file/manual-redaction-entry.wrapper'; +import { redactTextTranslations } from '@translations/redact-text-translations'; +import { RedactTextOption, RedactTextOptions } from './redact-text-options'; + +const PIN_ICON = 'red:push-pin'; +const FOLDER_ICON = 'red:folder'; + +@Component({ + templateUrl: './redact-text-dialog.component.html', + styleUrls: ['./redact-text-dialog.component.scss'], +}) +export class RedactTextDialogComponent extends BaseDialogComponent implements OnInit { + readonly roles = Roles; + readonly options: DetailsRadioOption[]; + dictionaryRequest = false; + legalOptions: LegalBasisOption[] = []; + dictionaries: Dictionary[] = []; + + readonly #translations = redactTextTranslations; + readonly #dossier: Dossier; + readonly #type: ManualRedactionEntryType; + + constructor( + private _justificationsService: JustificationsService, + private _activeDossiersService: ActiveDossiersService, + private _dictionaryService: DictionaryService, + private _iqserPermissionsService: IqserPermissionsService, + protected readonly _dialogRef: MatDialogRef, + @Inject(MAT_DIALOG_DATA) readonly data: { manualRedactionEntryWrapper: ManualRedactionEntryWrapper; dossierId: string; file: File }, + ) { + super(_dialogRef); + + this.#dossier = _activeDossiersService.find(this.data.dossierId); + this.#type = this.data.manualRedactionEntryWrapper.type; + this.options = this.#options(); + + this.form = this.#getForm(); + this.initialFormValue = this.form.getRawValue(); + } + + async ngOnInit(): Promise { + this.dictionaries = await this._dictionaryService.getDictionariesOptions(this.#dossier.dossierTemplateId, this.#dossier.id); + + const data = await firstValueFrom(this._justificationsService.getForDossierTemplate(this.#dossier.dossierTemplateId)); + this.legalOptions = data.map(lbm => ({ + legalBasis: lbm.reason, + description: lbm.description, + label: lbm.name, + })); + + this.#selectReason(); + this.#formatSelectedTextValue(); + } + + get displayedDictionaryLabel() { + const dictType = this.form.get('dictionary').value; + if (dictType) { + return this.dictionaries.find(d => d.type === dictType).label; + } + return null; + } + + save(): void { + this.#enhanceManualRedaction(this.data.manualRedactionEntryWrapper.manualRedactionEntry); + try { + const annotation = this.data.manualRedactionEntryWrapper.manualRedactionEntry; + this._dialogRef.close({ + annotation, + dictionary: this.dictionaries.find(d => d.type === this.form.get('dictionary').value), + }); + } catch (e) { + this._toaster.error(_('manual-annotation.dialog.error')); + } + } + + // toggleType() { + // console.log('test'); + // this.dictionaryRequest = this.form.get('option').value === RedactTextOptions.IN_DOSSIER; + // } + + #getForm(): UntypedFormGroup { + return this._formBuilder.group({ + selectedText: this.data?.manualRedactionEntryWrapper?.manualRedactionEntry?.value, + reason: [null], + comment: [null], + dictionary: [null, Validators.required], + classification: '', + multiplePages: '', + option: [this.options[0], Validators.required], + }); + } + + #formatSelectedTextValue(): void { + this.data.manualRedactionEntryWrapper.manualRedactionEntry.value = + this.data.manualRedactionEntryWrapper.manualRedactionEntry.value.replace( + // eslint-disable-next-line no-control-regex,max-len + /([^\s\d-]{2,})[-\u00AD]\u000D\u000A|[\u000A\u000B\u000C\u000D\u0085\u2028\u2029]/gi, + '$1', + ); + } + + #selectReason() { + if (this.legalOptions.length === 1) { + this.form.get('reason').setValue(this.legalOptions[0]); + } + } + + #enhanceManualRedaction(addRedactionRequest: IAddRedactionRequest) { + const legalOption: LegalBasisOption = this.form.get('reason').value; + addRedactionRequest.type = this.form.get('dictionary').value; + if (legalOption) { + addRedactionRequest.reason = legalOption.description; + addRedactionRequest.legalBasis = legalOption.legalBasis; + } + + if (this._iqserPermissionsService.has(Roles.getRss)) { + const selectedType = this.dictionaries.find(d => d.type === addRedactionRequest.type); + addRedactionRequest.addToDictionary = selectedType.hasDictionary; + } else { + addRedactionRequest.addToDictionary = this.dictionaryRequest && addRedactionRequest.type !== 'dossier_redaction'; + addRedactionRequest.addToDossierDictionary = this.dictionaryRequest && addRedactionRequest.type === 'dossier_redaction'; + } + + if (!addRedactionRequest.reason) { + addRedactionRequest.reason = 'Dictionary Request'; + } + const commentValue = this.form.get('comment').value; + addRedactionRequest.comment = commentValue ? { text: commentValue } : null; + addRedactionRequest.section = this.form.get('section').value; + addRedactionRequest.value = addRedactionRequest.rectangle + ? this.form.get('classification').value + : this.form.get('selectedText').value; + } + + #options() { + return [ + { + label: this.#translations[this.#type].onlyHere.label, + description: this.#translations[this.#type].onlyHere.description, + icon: PIN_ICON, + value: RedactTextOptions.ONLY_HERE, + }, + { + label: this.#translations[this.#type].inDossier.label, + description: this.#translations[this.#type].inDossier.description, + icon: FOLDER_ICON, + value: RedactTextOptions.IN_DOSSIER, + }, + ]; + } +} diff --git a/apps/red-ui/src/app/modules/file-preview/dialogs/redact-text-dialog/redact-text-options.ts b/apps/red-ui/src/app/modules/file-preview/dialogs/redact-text-dialog/redact-text-options.ts new file mode 100644 index 000000000..05a42de28 --- /dev/null +++ b/apps/red-ui/src/app/modules/file-preview/dialogs/redact-text-dialog/redact-text-options.ts @@ -0,0 +1,6 @@ +export const RedactTextOptions = { + ONLY_HERE: 'ONLY_HERE', + IN_DOSSIER: 'IN_DOSSIER', +} as const; + +export type RedactTextOption = keyof typeof RedactTextOptions; diff --git a/apps/red-ui/src/app/modules/file-preview/file-preview-screen.component.ts b/apps/red-ui/src/app/modules/file-preview/file-preview-screen.component.ts index 9f4cd17c7..e5a86f45b 100644 --- a/apps/red-ui/src/app/modules/file-preview/file-preview-screen.component.ts +++ b/apps/red-ui/src/app/modules/file-preview/file-preview-screen.component.ts @@ -40,7 +40,7 @@ import { ManualRedactionEntryWrapper } from '@models/file/manual-redaction-entry import { AnnotationWrapper } from '@models/file/annotation.wrapper'; import { AnnotationDrawService } from '../pdf-viewer/services/annotation-draw.service'; import { AnnotationProcessingService } from './services/annotation-processing.service'; -import { Dictionary, File, ViewModes } from '@red/domain'; +import { Dictionary, File, IManualRedactionEntry, ViewModes } from '@red/domain'; import { PermissionsService } from '@services/permissions.service'; import { combineLatest, firstValueFrom, of, pairwise } from 'rxjs'; import { PreferencesKeys, UserPreferenceService } from '@users/user-preference.service'; @@ -165,15 +165,15 @@ export class FilePreviewScreenComponent }); effect(() => { - const selectedText = this._documentViewer.selectedText(); - const canPerformActions = this.pdfProxyService.canPerformActions(); - const isCurrentPageExcluded = this.state.file().isPageExcluded(this.pdf.currentPage()); - - if ((selectedText.length > 2 || this._isJapaneseString(selectedText)) && canPerformActions && !isCurrentPageExcluded) { - this.pdf.enable(textActions); - } else { - this.pdf.disable(textActions); - } + // const selectedText = this._documentViewer.selectedText(); + // const canPerformActions = this.pdfProxyService.canPerformActions(); + // const isCurrentPageExcluded = this.state.file().isPageExcluded(this.pdf.currentPage()); + // + // if ((selectedText.length > 2 || this._isJapaneseString(selectedText)) && canPerformActions && !isCurrentPageExcluded) { + // this.pdf.enable(textActions); + // } else { + // this.pdf.disable(textActions); + // } }); } @@ -364,6 +364,34 @@ export class FilePreviewScreenComponent }); } + openRedactTextDialog(manualRedactionEntryWrapper: ManualRedactionEntryWrapper) { + return this._ngZone.run(() => { + const file = this.state.file(); + + manualRedactionEntryWrapper.manualRedactionEntry.value = 'This is selected text'; + this._dialogService.openDialog( + 'redactText', + { manualRedactionEntryWrapper, dossierId: this.dossierId, file }, + (result: { annotation: IManualRedactionEntry; dictionary?: Dictionary }) => { + const selectedAnnotations = this._annotationManager.selected; + if (selectedAnnotations.length > 0) { + this._annotationManager.delete([selectedAnnotations[0].Id]); + } + + const add$ = this._manualRedactionService.addAnnotation( + [result.annotation], + this.dossierId, + this.fileId, + result.dictionary?.label, + ); + + const addAndReload$ = add$.pipe(switchMap(() => this._filesService.reload(this.dossierId, file))); + return firstValueFrom(addAndReload$.pipe(catchError(() => of(undefined)))); + }, + ); + }); + } + toggleFullScreen() { this.fullScreen = !this.fullScreen; if (this.fullScreen) { @@ -623,6 +651,10 @@ export class FilePreviewScreenComponent this.openManualAnnotationDialog($event); }); + this.addActiveScreenSubscription = this.pdfProxyService.redactTextRequested$.subscribe($event => { + this.openRedactTextDialog($event); + }); + this.addActiveScreenSubscription = this.pdfProxyService.pageChanged$.subscribe(page => this._ngZone.run(() => this.#updateQueryParamsPage(page)), ); diff --git a/apps/red-ui/src/app/modules/file-preview/file-preview.module.ts b/apps/red-ui/src/app/modules/file-preview/file-preview.module.ts index 922f8700c..32efb144e 100644 --- a/apps/red-ui/src/app/modules/file-preview/file-preview.module.ts +++ b/apps/red-ui/src/app/modules/file-preview/file-preview.module.ts @@ -65,6 +65,7 @@ import { PagesComponent } from './components/pages/pages.component'; import { SharedModule } from '@shared/shared.module'; import { SharedDossiersModule } from '../shared-dossiers/shared-dossiers.module'; import { FalsePositiveDialogComponent } from './dialogs/false-positive-dialog/false-positive-dialog.component'; +import { RedactTextDialogComponent } from './dialogs/redact-text-dialog/redact-text-dialog.component'; const routes: IqserRoutes = [ { @@ -89,6 +90,7 @@ const dialogs = [ ImportRedactionsDialogComponent, RssDialogComponent, FalsePositiveDialogComponent, + RedactTextDialogComponent, ]; const components = [ diff --git a/apps/red-ui/src/app/modules/file-preview/services/file-preview-dialog.service.ts b/apps/red-ui/src/app/modules/file-preview/services/file-preview-dialog.service.ts index e8abdb76b..b848da12c 100644 --- a/apps/red-ui/src/app/modules/file-preview/services/file-preview-dialog.service.ts +++ b/apps/red-ui/src/app/modules/file-preview/services/file-preview-dialog.service.ts @@ -11,6 +11,7 @@ import { ResizeAnnotationDialogComponent } from '../dialogs/resize-annotation-di import { HighlightActionDialogComponent } from '../dialogs/highlight-action-dialog/highlight-action-dialog.component'; import { RssDialogComponent } from '../dialogs/rss-dialog/rss-dialog.component'; import { FalsePositiveDialogComponent } from '../dialogs/false-positive-dialog/false-positive-dialog.component'; +import { RedactTextDialogComponent } from '../dialogs/redact-text-dialog/redact-text-dialog.component'; type DialogType = | 'confirm' @@ -23,7 +24,8 @@ type DialogType = | 'forceAnnotation' | 'manualAnnotation' | 'highlightAction' - | 'falsePositive'; + | 'falsePositive' + | 'redactText'; @Injectable() export class FilePreviewDialogService extends DialogService { @@ -65,6 +67,9 @@ export class FilePreviewDialogService extends DialogService { falsePositive: { component: FalsePositiveDialogComponent, }, + redactText: { + component: RedactTextDialogComponent, + }, }; constructor(protected readonly _dialog: MatDialog) { diff --git a/apps/red-ui/src/app/modules/file-preview/services/pdf-proxy.service.ts b/apps/red-ui/src/app/modules/file-preview/services/pdf-proxy.service.ts index 9ebd6f7ac..5fc9b1fe1 100644 --- a/apps/red-ui/src/app/modules/file-preview/services/pdf-proxy.service.ts +++ b/apps/red-ui/src/app/modules/file-preview/services/pdf-proxy.service.ts @@ -42,6 +42,7 @@ import Quad = Core.Math.Quad; export class PdfProxyService { readonly annotationSelected$ = this.#annotationSelected$; readonly manualAnnotationRequested$ = new Subject(); + readonly redactTextRequested$ = new Subject(); readonly pageChanged$ = this._pdf.pageChanged$.pipe( tap(() => this._handleCustomActions()), tap(() => this._pdf.resetAnnotationActions()), @@ -60,6 +61,7 @@ export class PdfProxyService { ? this._convertPath('/assets/icons/general/pdftron-action-add-component.svg') : this._convertPath('/assets/icons/general/pdftron-action-add-redaction.svg'); readonly #addDictIcon = this._convertPath('/assets/icons/general/pdftron-action-add-dict.svg'); + readonly #addHintIcon = this._convertPath('/assets/icons/general/pdftron-action-add-hint.svg'); constructor( private readonly _translateService: TranslateService, @@ -157,6 +159,19 @@ export class PdfProxyService { } if (this._iqserPermissionsService.has(Roles.redactions.write) || this._iqserPermissionsService.has(Roles.redactions.request)) { + popups.push({ + type: 'actionButton', + dataElement: TextPopups.REDACT_TEXT, + img: this.#addRedactionIcon, + onClick: () => this._ngZone.run(() => this._redactText(ManualRedactionEntryTypes.REDACT)), + }); + popups.push({ + type: 'actionButton', + dataElement: TextPopups.ADD_HINT, + img: this.#addHintIcon, + onClick: () => this._ngZone.run(() => this._redactText(ManualRedactionEntryTypes.HINT)), + }); + popups.push({ type: 'actionButton', dataElement: TextPopups.ADD_REDACTION, @@ -189,6 +204,13 @@ export class PdfProxyService { this.manualAnnotationRequested$.next({ manualRedactionEntry, type }); } + private _redactText(type: ManualRedactionEntryType) { + const selectedQuads: Record = this._pdf.documentViewer.getSelectedTextQuads(); + const text = this._documentViewer.selectedText(); + const manualRedactionEntry = this._getManualRedaction(selectedQuads, text, true); + this.redactTextRequested$.next({ manualRedactionEntry, type }); + } + private _handleCustomActions() { const isCurrentPageExcluded = this._state.file().isPageExcluded(this._pdf.currentPage()); @@ -208,7 +230,7 @@ export class PdfProxyService { this._pdf.enable(TEXT_POPUPS_TO_TOGGLE); this._viewerHeaderService.enable(HEADER_ITEMS_TO_TOGGLE); - if (this._documentViewer.selectedText().length > 2) { + if (this._documentViewer.selectedText()?.length > 2) { this._pdf.enable([TextPopups.ADD_DICTIONARY, TextPopups.ADD_FALSE_POSITIVE]); } } else { diff --git a/apps/red-ui/src/app/modules/file-preview/utils/constants.ts b/apps/red-ui/src/app/modules/file-preview/utils/constants.ts index 63376f5f0..e8b3d7878 100644 --- a/apps/red-ui/src/app/modules/file-preview/utils/constants.ts +++ b/apps/red-ui/src/app/modules/file-preview/utils/constants.ts @@ -29,6 +29,8 @@ export const HeaderElements = { export type HeaderElementType = ValuesOf; export const TextPopups = { + REDACT_TEXT: 'redact-text', + ADD_HINT: 'add-hint', ADD_REDACTION: 'add-redaction', ADD_DICTIONARY: 'add-dictionary', ADD_RECTANGLE: 'add-rectangle', diff --git a/apps/red-ui/src/app/modules/icons/icons.module.ts b/apps/red-ui/src/app/modules/icons/icons.module.ts index 6ae4652ec..54c90a5a9 100644 --- a/apps/red-ui/src/app/modules/icons/icons.module.ts +++ b/apps/red-ui/src/app/modules/icons/icons.module.ts @@ -63,6 +63,7 @@ export class IconsModule { 'padding-top-bottom', 'page', 'preview', + 'push-pin', 'put-back', 'read-only', 'ready-for-approval', diff --git a/apps/red-ui/src/app/modules/pdf-viewer/services/document-viewer.service.ts b/apps/red-ui/src/app/modules/pdf-viewer/services/document-viewer.service.ts index 2f16097a2..42db40a8d 100644 --- a/apps/red-ui/src/app/modules/pdf-viewer/services/document-viewer.service.ts +++ b/apps/red-ui/src/app/modules/pdf-viewer/services/document-viewer.service.ts @@ -144,12 +144,12 @@ export class REDDocumentViewer { } #listenForDocEvents() { - this.#document.addEventListener('textSelected', ([, selectedText, pageNumber]: [Quad, string, number]) => { - this.#ngZone.run(() => { - this.#disableTextPopupIfCompareMode(pageNumber); - this.#selectedText.set(selectedText); - }); - }); + // this.#document.addEventListener('textSelected', ([, selectedText, pageNumber]: [Quad, string, number]) => { + // this.#ngZone.run(() => { + // this.#disableTextPopupIfCompareMode(pageNumber); + // this.#selectedText.set(selectedText); + // }); + // }); this.#document.addEventListener('pageComplete', event => { this.#ngZone.run(() => { diff --git a/apps/red-ui/src/app/translations/redact-text-translations.ts b/apps/red-ui/src/app/translations/redact-text-translations.ts new file mode 100644 index 000000000..4eb6f883c --- /dev/null +++ b/apps/red-ui/src/app/translations/redact-text-translations.ts @@ -0,0 +1,29 @@ +import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker'; + +interface Option { + label: string; + description: string; +} + +export const redactTextTranslations: Record<'REDACT' | 'HINT', Record<'onlyHere' | 'inDossier', Option>> = { + REDACT: { + onlyHere: { + label: _('redact-text.dialog.content.options.redact.only-here.label'), + description: _('redact-text.dialog.content.options.redact.only-here.description'), + }, + inDossier: { + label: _('redact-text.dialog.content.options.redact.in-dossier.label'), + description: _('redact-text.dialog.content.options.redact.in-dossier.description'), + }, + }, + HINT: { + onlyHere: { + label: _('redact-text.dialog.content.options.hint.only-here.label'), + description: _('redact-text.dialog.content.options.hint.only-here.description'), + }, + inDossier: { + label: _('redact-text.dialog.content.options.hint.in-dossier.label'), + description: _('redact-text.dialog.content.options.hint.in-dossier.description'), + }, + }, +} as const; diff --git a/apps/red-ui/src/assets/i18n/redact/de.json b/apps/red-ui/src/assets/i18n/redact/de.json index c470bf8ca..2d621be68 100644 --- a/apps/red-ui/src/assets/i18n/redact/de.json +++ b/apps/red-ui/src/assets/i18n/redact/de.json @@ -1900,6 +1900,47 @@ }, "header": "Bildtypen bearbeiten" }, + "redact-text": { + "dialog": { + "actions": { + "cancel": "", + "save": "" + }, + "content": { + "comment": "", + "comment-placeholder": "", + "legal-basis": "", + "options": { + "hint": { + "in-dossier": { + "description": "", + "label": "" + }, + "only-here": { + "description": "", + "label": "" + } + }, + "redact": { + "in-dossier": { + "description": "", + "label": "" + }, + "only-here": { + "description": "", + "label": "" + } + } + }, + "reason": "", + "reason-placeholder": "", + "selected-text": "", + "type": "", + "type-placeholder": "" + }, + "title": "" + } + }, "redaction-abbreviation": "R", "references": "", "remove-annotations-dialog": { diff --git a/apps/red-ui/src/assets/i18n/redact/en.json b/apps/red-ui/src/assets/i18n/redact/en.json index 6af3171a7..0446bd7a9 100644 --- a/apps/red-ui/src/assets/i18n/redact/en.json +++ b/apps/red-ui/src/assets/i18n/redact/en.json @@ -1900,6 +1900,47 @@ }, "header": "Edit Image Type" }, + "redact-text": { + "dialog": { + "actions": { + "cancel": "Cancel", + "save": "Save" + }, + "content": { + "comment": "Comment", + "comment-placeholder": "Add remarks or mentions ...", + "legal-basis": "Legal Basis", + "options": { + "hint": { + "in-dossier": { + "description": "Add hint in every document in Dossier Alpha.", + "label": "Add hint in dossier" + }, + "only-here": { + "description": "Add hint only at this position in this document.", + "label": "Add hint only here" + } + }, + "redact": { + "in-dossier": { + "description": "Add redaction in every document in Dossier Alpha.", + "label": "Redact in dossier" + }, + "only-here": { + "description": "Add redaction only at this position in this document.", + "label": "Redact only here" + } + } + }, + "reason": "Reason", + "reason-placeholder": "Select a reason ...", + "selected-text": "Selected text:", + "type": "Type", + "type-placeholder": "Select type ..." + }, + "title": "Redact text" + } + }, "redaction-abbreviation": "R", "references": "{count} {count, plural, one{reference} other{references}}", "remove-annotations-dialog": { diff --git a/apps/red-ui/src/assets/i18n/scm/de.json b/apps/red-ui/src/assets/i18n/scm/de.json index 9de02d917..6cdc7d44e 100644 --- a/apps/red-ui/src/assets/i18n/scm/de.json +++ b/apps/red-ui/src/assets/i18n/scm/de.json @@ -1900,6 +1900,47 @@ }, "header": "Bildtypen bearbeiten" }, + "redact-text": { + "dialog": { + "actions": { + "cancel": "", + "save": "" + }, + "content": { + "comment": "", + "comment-placeholder": "", + "legal-basis": "", + "options": { + "hint": { + "in-dossier": { + "description": "", + "label": "" + }, + "only-here": { + "description": "", + "label": "" + } + }, + "redact": { + "in-dossier": { + "description": "", + "label": "" + }, + "only-here": { + "description": "", + "label": "" + } + } + }, + "reason": "", + "reason-placeholder": "", + "selected-text": "", + "type": "", + "type-placeholder": "" + }, + "title": "" + } + }, "redaction-abbreviation": "C", "references": "", "remove-annotations-dialog": { diff --git a/apps/red-ui/src/assets/i18n/scm/en.json b/apps/red-ui/src/assets/i18n/scm/en.json index 9f266d4fc..349592572 100644 --- a/apps/red-ui/src/assets/i18n/scm/en.json +++ b/apps/red-ui/src/assets/i18n/scm/en.json @@ -1900,6 +1900,47 @@ }, "header": "Edit Image Type" }, + "redact-text": { + "dialog": { + "actions": { + "cancel": "Cancel", + "save": "Save" + }, + "content": { + "comment": "Comment", + "comment-placeholder": "Add remarks or mentions ...", + "legal-basis": "Legal Basis", + "options": { + "hint": { + "in-dossier": { + "description": "Add hint in every document in Dossier Alpha.", + "label": "Add hint in dossier" + }, + "only-here": { + "description": "Add hint only at this position in this document.", + "label": "Add hint only here" + } + }, + "redact": { + "in-dossier": { + "description": "Add redaction in every document in Dossier Alpha.", + "label": "Redact in dossier" + }, + "only-here": { + "description": "Add redaction only at this position in this document.", + "label": "Redact only here" + } + } + }, + "reason": "Reason", + "reason-placeholder": "Select a reason ...", + "selected-text": "Selected text:", + "type": "Type", + "type-placeholder": "Select type ..." + }, + "title": "Redact text" + } + }, "redaction-abbreviation": "C", "references": "{count} {count, plural, one{reference} other{references}}", "remove-annotations-dialog": { diff --git a/apps/red-ui/src/assets/icons/general/pdftron-action-add-hint.svg b/apps/red-ui/src/assets/icons/general/pdftron-action-add-hint.svg new file mode 100644 index 000000000..1fe57ac1b --- /dev/null +++ b/apps/red-ui/src/assets/icons/general/pdftron-action-add-hint.svg @@ -0,0 +1,11 @@ + + + + + + H + + + diff --git a/apps/red-ui/src/assets/icons/general/pdftron-action-add-redaction.svg b/apps/red-ui/src/assets/icons/general/pdftron-action-add-redaction.svg index 8e984bddf..584946975 100644 --- a/apps/red-ui/src/assets/icons/general/pdftron-action-add-redaction.svg +++ b/apps/red-ui/src/assets/icons/general/pdftron-action-add-redaction.svg @@ -3,7 +3,7 @@ xmlns="http://www.w3.org/2000/svg"> - R diff --git a/apps/red-ui/src/assets/icons/general/push-pin.svg b/apps/red-ui/src/assets/icons/general/push-pin.svg new file mode 100644 index 000000000..1b0667a76 --- /dev/null +++ b/apps/red-ui/src/assets/icons/general/push-pin.svg @@ -0,0 +1,3 @@ + + + From 337861971c6f93bc8c5a4d3e5cb2f313f78b7645 Mon Sep 17 00:00:00 2001 From: Valentin Mihai Date: Fri, 16 Jun 2023 22:09:32 +0300 Subject: [PATCH 02/10] RED-6774 - update common ui --- libs/common-ui | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/common-ui b/libs/common-ui index a4692e8ee..aa329a726 160000 --- a/libs/common-ui +++ b/libs/common-ui @@ -1 +1 @@ -Subproject commit a4692e8ee3de3e7a32e165b3bcf288f950594cdf +Subproject commit aa329a726f093b9d803cbdeb396ad0e400020196 From 8d468bf2bc74e8c35eb5632cbae6e62c2f56d4f0 Mon Sep 17 00:00:00 2001 From: Valentin Mihai Date: Fri, 16 Jun 2023 22:10:48 +0300 Subject: [PATCH 03/10] RED-6774 - update common ui --- libs/common-ui | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/common-ui b/libs/common-ui index aa329a726..e23f355ae 160000 --- a/libs/common-ui +++ b/libs/common-ui @@ -1 +1 @@ -Subproject commit aa329a726f093b9d803cbdeb396ad0e400020196 +Subproject commit e23f355aefa0d723783eff1860fa549e56d7cf71 From 9a967c0c0db6e98a43d52e853a360d1607cffe36 Mon Sep 17 00:00:00 2001 From: Valentin Mihai Date: Sun, 18 Jun 2023 22:36:51 +0300 Subject: [PATCH 04/10] RED-6774 - added remove dialog --- .../annotation-actions.component.html | 9 +++ .../annotation-actions.component.ts | 4 ++ .../redact-text-dialog.component.html | 4 +- .../redact-text-dialog.component.scss | 4 ++ .../redact-text-dialog.component.ts | 31 +++++---- .../remove-redaction-dialog.component.scss | 0 .../remove-redaction-dialog.component.ts | 65 +++++++++++++++++++ .../remove-redaction-dialog.html | 35 ++++++++++ .../remove-redaction-options.ts | 7 ++ .../file-preview/file-preview.module.ts | 2 + .../services/annotation-actions.service.ts | 18 +++++ .../services/file-preview-dialog.service.ts | 7 +- .../pdf-annotation-actions.service.ts | 7 ++ .../remove-redaction-translations.ts | 22 +++++++ apps/red-ui/src/assets/i18n/redact/de.json | 30 ++++++++- apps/red-ui/src/assets/i18n/redact/en.json | 30 ++++++++- apps/red-ui/src/assets/i18n/scm/de.json | 30 ++++++++- apps/red-ui/src/assets/i18n/scm/en.json | 30 ++++++++- libs/common-ui | 2 +- 19 files changed, 317 insertions(+), 20 deletions(-) create mode 100644 apps/red-ui/src/app/modules/file-preview/dialogs/remove-redaction-dialog/remove-redaction-dialog.component.scss create mode 100644 apps/red-ui/src/app/modules/file-preview/dialogs/remove-redaction-dialog/remove-redaction-dialog.component.ts create mode 100644 apps/red-ui/src/app/modules/file-preview/dialogs/remove-redaction-dialog/remove-redaction-dialog.html create mode 100644 apps/red-ui/src/app/modules/file-preview/dialogs/remove-redaction-dialog/remove-redaction-options.ts create mode 100644 apps/red-ui/src/app/translations/remove-redaction-translations.ts diff --git a/apps/red-ui/src/app/modules/file-preview/components/annotation-actions/annotation-actions.component.html b/apps/red-ui/src/app/modules/file-preview/components/annotation-actions/annotation-actions.component.html index 06ef6596b..cf088d9bf 100644 --- a/apps/red-ui/src/app/modules/file-preview/components/annotation-actions/annotation-actions.component.html +++ b/apps/red-ui/src/app/modules/file-preview/components/annotation-actions/annotation-actions.component.html @@ -176,5 +176,14 @@ [type]="buttonType" icon="iqser:trash" > + + diff --git a/apps/red-ui/src/app/modules/file-preview/components/annotation-actions/annotation-actions.component.ts b/apps/red-ui/src/app/modules/file-preview/components/annotation-actions/annotation-actions.component.ts index 7162ba49a..395c17f01 100644 --- a/apps/red-ui/src/app/modules/file-preview/components/annotation-actions/annotation-actions.component.ts +++ b/apps/red-ui/src/app/modules/file-preview/components/annotation-actions/annotation-actions.component.ts @@ -78,6 +78,10 @@ export class AnnotationActionsComponent implements OnChanges { this.annotationActionsService.removeOrSuggestRemoveAnnotation(this.annotations, removeFromDict); } + removeOrSuggestRemoveRedaction() { + this.annotationActionsService.removeOrSuggestRemoveRedaction(this.annotations); + } + markAsFalsePositive() { this.annotationActionsService.markAsFalsePositive(this.annotations); } diff --git a/apps/red-ui/src/app/modules/file-preview/dialogs/redact-text-dialog/redact-text-dialog.component.html b/apps/red-ui/src/app/modules/file-preview/dialogs/redact-text-dialog/redact-text-dialog.component.html index 92af9cacc..9b9426339 100644 --- a/apps/red-ui/src/app/modules/file-preview/dialogs/redact-text-dialog/redact-text-dialog.component.html +++ b/apps/red-ui/src/app/modules/file-preview/dialogs/redact-text-dialog/redact-text-dialog.component.html @@ -10,7 +10,7 @@ - +
@@ -37,7 +37,7 @@
- +
diff --git a/apps/red-ui/src/app/modules/file-preview/dialogs/redact-text-dialog/redact-text-dialog.component.scss b/apps/red-ui/src/app/modules/file-preview/dialogs/redact-text-dialog/redact-text-dialog.component.scss index 84db58094..0bc0b2eb8 100644 --- a/apps/red-ui/src/app/modules/file-preview/dialogs/redact-text-dialog/redact-text-dialog.component.scss +++ b/apps/red-ui/src/app/modules/file-preview/dialogs/redact-text-dialog/redact-text-dialog.component.scss @@ -2,3 +2,7 @@ label { font-weight: bold; padding-bottom: 8px; } + +iqser-details-radio { + padding-top: 20px; +} diff --git a/apps/red-ui/src/app/modules/file-preview/dialogs/redact-text-dialog/redact-text-dialog.component.ts b/apps/red-ui/src/app/modules/file-preview/dialogs/redact-text-dialog/redact-text-dialog.component.ts index 83e36d099..e7db28d0d 100644 --- a/apps/red-ui/src/app/modules/file-preview/dialogs/redact-text-dialog/redact-text-dialog.component.ts +++ b/apps/red-ui/src/app/modules/file-preview/dialogs/redact-text-dialog/redact-text-dialog.component.ts @@ -13,6 +13,7 @@ import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker'; import { ManualRedactionEntryType, ManualRedactionEntryWrapper } from '@models/file/manual-redaction-entry.wrapper'; import { redactTextTranslations } from '@translations/redact-text-translations'; import { RedactTextOption, RedactTextOptions } from './redact-text-options'; +import { tap } from 'rxjs/operators'; const PIN_ICON = 'red:push-pin'; const FOLDER_ICON = 'red:folder'; @@ -24,13 +25,13 @@ const FOLDER_ICON = 'red:folder'; export class RedactTextDialogComponent extends BaseDialogComponent implements OnInit { readonly roles = Roles; readonly options: DetailsRadioOption[]; + readonly type: ManualRedactionEntryType; dictionaryRequest = false; legalOptions: LegalBasisOption[] = []; dictionaries: Dictionary[] = []; readonly #translations = redactTextTranslations; readonly #dossier: Dossier; - readonly #type: ManualRedactionEntryType; constructor( private _justificationsService: JustificationsService, @@ -43,7 +44,7 @@ export class RedactTextDialogComponent extends BaseDialogComponent implements On super(_dialogRef); this.#dossier = _activeDossiersService.find(this.data.dossierId); - this.#type = this.data.manualRedactionEntryWrapper.type; + this.type = this.data.manualRedactionEntryWrapper.type; this.options = this.#options(); this.form = this.#getForm(); @@ -62,6 +63,17 @@ export class RedactTextDialogComponent extends BaseDialogComponent implements On this.#selectReason(); this.#formatSelectedTextValue(); + + this._subscriptions.add( + this.form + .get('option') + .valueChanges.pipe( + tap((option: DetailsRadioOption) => { + this.dictionaryRequest = option.value === RedactTextOptions.IN_DOSSIER; + }), + ) + .subscribe(), + ); } get displayedDictionaryLabel() { @@ -85,11 +97,6 @@ export class RedactTextDialogComponent extends BaseDialogComponent implements On } } - // toggleType() { - // console.log('test'); - // this.dictionaryRequest = this.form.get('option').value === RedactTextOptions.IN_DOSSIER; - // } - #getForm(): UntypedFormGroup { return this._formBuilder.group({ selectedText: this.data?.manualRedactionEntryWrapper?.manualRedactionEntry?.value, @@ -98,7 +105,7 @@ export class RedactTextDialogComponent extends BaseDialogComponent implements On dictionary: [null, Validators.required], classification: '', multiplePages: '', - option: [this.options[0], Validators.required], + option: [this.options[0]], }); } @@ -147,14 +154,14 @@ export class RedactTextDialogComponent extends BaseDialogComponent implements On #options() { return [ { - label: this.#translations[this.#type].onlyHere.label, - description: this.#translations[this.#type].onlyHere.description, + label: this.#translations[this.type].onlyHere.label, + description: this.#translations[this.type].onlyHere.description, icon: PIN_ICON, value: RedactTextOptions.ONLY_HERE, }, { - label: this.#translations[this.#type].inDossier.label, - description: this.#translations[this.#type].inDossier.description, + label: this.#translations[this.type].inDossier.label, + description: this.#translations[this.type].inDossier.description, icon: FOLDER_ICON, value: RedactTextOptions.IN_DOSSIER, }, diff --git a/apps/red-ui/src/app/modules/file-preview/dialogs/remove-redaction-dialog/remove-redaction-dialog.component.scss b/apps/red-ui/src/app/modules/file-preview/dialogs/remove-redaction-dialog/remove-redaction-dialog.component.scss new file mode 100644 index 000000000..e69de29bb diff --git a/apps/red-ui/src/app/modules/file-preview/dialogs/remove-redaction-dialog/remove-redaction-dialog.component.ts b/apps/red-ui/src/app/modules/file-preview/dialogs/remove-redaction-dialog/remove-redaction-dialog.component.ts new file mode 100644 index 000000000..942bb7d78 --- /dev/null +++ b/apps/red-ui/src/app/modules/file-preview/dialogs/remove-redaction-dialog/remove-redaction-dialog.component.ts @@ -0,0 +1,65 @@ +import { Component, Inject } from '@angular/core'; +import { BaseDialogComponent, DetailsRadioOption } from '@iqser/common-ui'; +import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; +import { RemoveRedactionOption, RemoveRedactionOptions } from './remove-redaction-options'; +import { UntypedFormGroup } from '@angular/forms'; +import { removeRedactionTranslations } from '@translations/remove-redaction-translations'; + +const PIN_ICON = 'red:push-pin'; +const FOLDER_ICON = 'red:folder'; +const REMOVE_FROM_DICT_ICON = 'red:remove-from-dict'; + +@Component({ + templateUrl: './remove-redaction-dialog.html', + styleUrls: ['./remove-redaction-dialog.component.scss'], +}) +export class RemoveRedactionDialogComponent extends BaseDialogComponent { + readonly options: DetailsRadioOption[]; + + readonly #translations = removeRedactionTranslations; + + constructor( + protected readonly _dialogRef: MatDialogRef, + @Inject(MAT_DIALOG_DATA) readonly data: NonNullable, + ) { + super(_dialogRef); + + this.options = this.#options(); + this.form = this.#getForm(); + this.initialFormValue = this.form.getRawValue(); + } + + save(): void { + console.log('test'); + } + + #getForm(): UntypedFormGroup { + return this._formBuilder.group({ + comment: [null], + option: [this.options[0]], + }); + } + + #options() { + return [ + { + label: this.#translations.ONLY_HERE.label, + description: this.#translations.ONLY_HERE.description, + icon: PIN_ICON, + value: RemoveRedactionOptions.ONLY_HERE, + }, + { + label: this.#translations.IN_DOSSIER.label, + description: this.#translations.IN_DOSSIER.description, + icon: FOLDER_ICON, + value: RemoveRedactionOptions.IN_DOSSIER, + }, + { + label: this.#translations.FALSE_POSITIVE.label, + description: this.#translations.FALSE_POSITIVE.description, + icon: REMOVE_FROM_DICT_ICON, + value: RemoveRedactionOptions.FALSE_POSITIVE, + }, + ]; + } +} diff --git a/apps/red-ui/src/app/modules/file-preview/dialogs/remove-redaction-dialog/remove-redaction-dialog.html b/apps/red-ui/src/app/modules/file-preview/dialogs/remove-redaction-dialog/remove-redaction-dialog.html new file mode 100644 index 000000000..9ef3350bd --- /dev/null +++ b/apps/red-ui/src/app/modules/file-preview/dialogs/remove-redaction-dialog/remove-redaction-dialog.html @@ -0,0 +1,35 @@ +
+
+
+ +
+ + +
+ + +
+
+ +
+ + + +
+
+
+ + +
diff --git a/apps/red-ui/src/app/modules/file-preview/dialogs/remove-redaction-dialog/remove-redaction-options.ts b/apps/red-ui/src/app/modules/file-preview/dialogs/remove-redaction-dialog/remove-redaction-options.ts new file mode 100644 index 000000000..56a0bde4e --- /dev/null +++ b/apps/red-ui/src/app/modules/file-preview/dialogs/remove-redaction-dialog/remove-redaction-options.ts @@ -0,0 +1,7 @@ +export const RemoveRedactionOptions = { + ONLY_HERE: 'ONLY_HERE', + IN_DOSSIER: 'IN_DOSSIER', + FALSE_POSITIVE: 'FALSE_POSITIVE', +} as const; + +export type RemoveRedactionOption = keyof typeof RemoveRedactionOptions; diff --git a/apps/red-ui/src/app/modules/file-preview/file-preview.module.ts b/apps/red-ui/src/app/modules/file-preview/file-preview.module.ts index 32efb144e..1dbaf9ceb 100644 --- a/apps/red-ui/src/app/modules/file-preview/file-preview.module.ts +++ b/apps/red-ui/src/app/modules/file-preview/file-preview.module.ts @@ -66,6 +66,7 @@ import { SharedModule } from '@shared/shared.module'; import { SharedDossiersModule } from '../shared-dossiers/shared-dossiers.module'; import { FalsePositiveDialogComponent } from './dialogs/false-positive-dialog/false-positive-dialog.component'; import { RedactTextDialogComponent } from './dialogs/redact-text-dialog/redact-text-dialog.component'; +import { RemoveRedactionDialogComponent } from './dialogs/remove-redaction-dialog/remove-redaction-dialog.component'; const routes: IqserRoutes = [ { @@ -91,6 +92,7 @@ const dialogs = [ RssDialogComponent, FalsePositiveDialogComponent, RedactTextDialogComponent, + RemoveRedactionDialogComponent, ]; const components = [ diff --git a/apps/red-ui/src/app/modules/file-preview/services/annotation-actions.service.ts b/apps/red-ui/src/app/modules/file-preview/services/annotation-actions.service.ts index 6163a1650..7955420a9 100644 --- a/apps/red-ui/src/app/modules/file-preview/services/annotation-actions.service.ts +++ b/apps/red-ui/src/app/modules/file-preview/services/annotation-actions.service.ts @@ -129,6 +129,24 @@ export class AnnotationActionsService { }); } + removeOrSuggestRemoveRedaction(annotations: AnnotationWrapper[]) { + const data = { + annotationsToRemove: annotations, + dossier: this._state.dossier(), + hint: annotations[0].hintDictionary, + }; + const { dossierId, fileId } = this._state; + this._dialogService.openDialog('removeRedaction', data, (result: { comment: string }) => { + const body = annotations.map(annotation => ({ + annotationId: annotation.id, + comment: result.comment, + })); + this.#processObsAndEmit( + this._manualRedactionService.removeOrSuggestRemove(body, dossierId, fileId, true, annotations[0].isHint), + ).then(); + }); + } + recategorizeImages(annotations: AnnotationWrapper[]) { const data = { annotations, dossier: this._state.dossier() }; const { dossierId, fileId } = this._state; diff --git a/apps/red-ui/src/app/modules/file-preview/services/file-preview-dialog.service.ts b/apps/red-ui/src/app/modules/file-preview/services/file-preview-dialog.service.ts index b848da12c..e3f995715 100644 --- a/apps/red-ui/src/app/modules/file-preview/services/file-preview-dialog.service.ts +++ b/apps/red-ui/src/app/modules/file-preview/services/file-preview-dialog.service.ts @@ -12,6 +12,7 @@ import { HighlightActionDialogComponent } from '../dialogs/highlight-action-dial import { RssDialogComponent } from '../dialogs/rss-dialog/rss-dialog.component'; import { FalsePositiveDialogComponent } from '../dialogs/false-positive-dialog/false-positive-dialog.component'; import { RedactTextDialogComponent } from '../dialogs/redact-text-dialog/redact-text-dialog.component'; +import { RemoveRedactionDialogComponent } from '../dialogs/remove-redaction-dialog/remove-redaction-dialog.component'; type DialogType = | 'confirm' @@ -25,7 +26,8 @@ type DialogType = | 'manualAnnotation' | 'highlightAction' | 'falsePositive' - | 'redactText'; + | 'redactText' + | 'removeRedaction'; @Injectable() export class FilePreviewDialogService extends DialogService { @@ -70,6 +72,9 @@ export class FilePreviewDialogService extends DialogService { redactText: { component: RedactTextDialogComponent, }, + removeRedaction: { + component: RemoveRedactionDialogComponent, + }, }; constructor(protected readonly _dialog: MatDialog) { diff --git a/apps/red-ui/src/app/modules/file-preview/services/pdf-annotation-actions.service.ts b/apps/red-ui/src/app/modules/file-preview/services/pdf-annotation-actions.service.ts index 0fe12e20f..d63300232 100644 --- a/apps/red-ui/src/app/modules/file-preview/services/pdf-annotation-actions.service.ts +++ b/apps/red-ui/src/app/modules/file-preview/services/pdf-annotation-actions.service.ts @@ -129,6 +129,13 @@ export class PdfAnnotationActionsService { availableActions.push(removeOrSuggestToRemoveOnlyHereButton); } + if (permissions.canRemoveOrSuggestToRemoveOnlyHere) { + const removeOrSuggestToRemoveButton = this.#getButton('trash', _('annotation-actions.remove-annotation.remove-redaction'), () => + this.#annotationActionsService.removeOrSuggestRemoveRedaction(annotations), + ); + availableActions.push(removeOrSuggestToRemoveButton); + } + return availableActions; } diff --git a/apps/red-ui/src/app/translations/remove-redaction-translations.ts b/apps/red-ui/src/app/translations/remove-redaction-translations.ts new file mode 100644 index 000000000..d90195846 --- /dev/null +++ b/apps/red-ui/src/app/translations/remove-redaction-translations.ts @@ -0,0 +1,22 @@ +import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker'; +import { RemoveRedactionOption } from '../modules/file-preview/dialogs/remove-redaction-dialog/remove-redaction-options'; + +interface Option { + label: string; + description: string; +} + +export const removeRedactionTranslations: { [key in RemoveRedactionOption]: Option } = { + ONLY_HERE: { + label: _('remove-redaction.dialog.content.options.only-here.label'), + description: _('remove-redaction.dialog.content.options.only-here.description'), + }, + IN_DOSSIER: { + label: _('remove-redaction.dialog.content.options.in-dossier.label'), + description: _('remove-redaction.dialog.content.options.in-dossier.description'), + }, + FALSE_POSITIVE: { + label: _('remove-redaction.dialog.content.options.false-positive.label'), + description: _('remove-redaction.dialog.content.options.false-positive.description'), + }, +}; diff --git a/apps/red-ui/src/assets/i18n/redact/de.json b/apps/red-ui/src/assets/i18n/redact/de.json index 2d621be68..9f00d6961 100644 --- a/apps/red-ui/src/assets/i18n/redact/de.json +++ b/apps/red-ui/src/assets/i18n/redact/de.json @@ -328,7 +328,8 @@ "remove-annotation": { "false-positive": "Falsch positiv", "only-here": "nur hier entfernen", - "remove-from-dict": "Aus dem Wörterbuch entfernen" + "remove-from-dict": "Aus dem Wörterbuch entfernen", + "remove-redaction": "" }, "remove-highlights": { "label": "" @@ -1958,6 +1959,33 @@ }, "value": "Wert" }, + "remove-redaction": { + "dialog": { + "actions": { + "cancel": "", + "save": "" + }, + "content": { + "comment": "", + "comment-placeholder": "", + "options": { + "false-positive": { + "description": "", + "label": "" + }, + "in-dossier": { + "description": "", + "label": "" + }, + "only-here": { + "description": "", + "label": "" + } + } + }, + "title": "" + } + }, "report-type": { "label": "{length} {length, plural, one{Berichtstyp} other{Berichtstypen}}" }, diff --git a/apps/red-ui/src/assets/i18n/redact/en.json b/apps/red-ui/src/assets/i18n/redact/en.json index 0446bd7a9..1712bdf73 100644 --- a/apps/red-ui/src/assets/i18n/redact/en.json +++ b/apps/red-ui/src/assets/i18n/redact/en.json @@ -328,7 +328,8 @@ "remove-annotation": { "false-positive": "False Positive", "only-here": "Remove only here", - "remove-from-dict": "Remove from dictionary" + "remove-from-dict": "Remove from dictionary", + "remove-redaction": "Remove" }, "remove-highlights": { "label": "Remove Selected Earmarks" @@ -1958,6 +1959,33 @@ }, "value": "Value" }, + "remove-redaction": { + "dialog": { + "actions": { + "cancel": "Cancel", + "save": "Save" + }, + "content": { + "comment": "Comment", + "comment-placeholder": "Add remarks or mentions ...", + "options": { + "false-positive": { + "description": "\"White\" is not a CBI Author in this context: \"White rabbits are\".", + "label": "False positive" + }, + "in-dossier": { + "description": "Do not redact \"White\" in any document of the current dossier.", + "label": "Remove in dossier" + }, + "only-here": { + "description": "Do not redact \"White\" at this position in the current document.", + "label": "Remove here" + } + } + }, + "title": "Remove redaction" + } + }, "report-type": { "label": "{length} report {length, plural, one{type} other{types}}" }, diff --git a/apps/red-ui/src/assets/i18n/scm/de.json b/apps/red-ui/src/assets/i18n/scm/de.json index 6cdc7d44e..a086a88cc 100644 --- a/apps/red-ui/src/assets/i18n/scm/de.json +++ b/apps/red-ui/src/assets/i18n/scm/de.json @@ -328,7 +328,8 @@ "remove-annotation": { "false-positive": "Falsch positiv", "only-here": "nur hier entfernen", - "remove-from-dict": "Aus dem Wörterbuch entfernen" + "remove-from-dict": "Aus dem Wörterbuch entfernen", + "remove-redaction": "" }, "remove-highlights": { "label": "" @@ -1958,6 +1959,33 @@ }, "value": "Wert" }, + "remove-redaction": { + "dialog": { + "actions": { + "cancel": "", + "save": "" + }, + "content": { + "comment": "", + "comment-placeholder": "", + "options": { + "false-positive": { + "description": "", + "label": "" + }, + "in-dossier": { + "description": "", + "label": "" + }, + "only-here": { + "description": "", + "label": "" + } + } + }, + "title": "" + } + }, "report-type": { "label": "{length} {length, plural, one{Berichtstyp} other{Berichtstypen}}" }, diff --git a/apps/red-ui/src/assets/i18n/scm/en.json b/apps/red-ui/src/assets/i18n/scm/en.json index 349592572..2eaf5bbd8 100644 --- a/apps/red-ui/src/assets/i18n/scm/en.json +++ b/apps/red-ui/src/assets/i18n/scm/en.json @@ -328,7 +328,8 @@ "remove-annotation": { "false-positive": "False Positive", "only-here": "Remove only here", - "remove-from-dict": "Remove from dictionary" + "remove-from-dict": "Remove from dictionary", + "remove-redaction": "Remove" }, "remove-highlights": { "label": "Remove Selected Earmarks" @@ -1958,6 +1959,33 @@ }, "value": "Value" }, + "remove-redaction": { + "dialog": { + "actions": { + "cancel": "Cancel", + "save": "Save" + }, + "content": { + "comment": "Comment", + "comment-placeholder": "Add remarks or mentions ...", + "options": { + "false-positive": { + "description": "\"White\" is not a CBI Author in this context: \"White rabbits are\".", + "label": "False positive" + }, + "in-dossier": { + "description": "Do not redact \"White\" in any document of the current dossier.", + "label": "Remove in dossier" + }, + "only-here": { + "description": "Do not redact \"White\" at this position in the current document.", + "label": "Remove here" + } + } + }, + "title": "Remove redaction" + } + }, "report-type": { "label": "{length} report {length, plural, one{type} other{types}}" }, diff --git a/libs/common-ui b/libs/common-ui index e23f355ae..5a027f7e1 160000 --- a/libs/common-ui +++ b/libs/common-ui @@ -1 +1 @@ -Subproject commit e23f355aefa0d723783eff1860fa549e56d7cf71 +Subproject commit 5a027f7e1cb7aa1b093fc2f664bf6c1d3b919a8f From 6ade5eeba11eea3b5bdab225aa85f3df35c67568 Mon Sep 17 00:00:00 2001 From: Valentin Mihai Date: Sun, 18 Jun 2023 22:38:52 +0300 Subject: [PATCH 05/10] RED-6774 - update common ui --- libs/common-ui | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/common-ui b/libs/common-ui index 5a027f7e1..490c01f85 160000 --- a/libs/common-ui +++ b/libs/common-ui @@ -1 +1 @@ -Subproject commit 5a027f7e1cb7aa1b093fc2f664bf6c1d3b919a8f +Subproject commit 490c01f85b4d97e69549b9ad0f206223b9456f5c From 2d76b6c1eb15edd6b1572e1d971be9353dee391b Mon Sep 17 00:00:00 2001 From: Valentin Mihai Date: Mon, 19 Jun 2023 07:45:26 +0300 Subject: [PATCH 06/10] RED-6774 - updated dialog logic to be able to redact and add hints --- .../annotation-actions.component.ts | 2 +- .../redact-text-dialog.component.html | 2 +- .../redact-text-dialog.component.ts | 74 +++++++++++++------ .../remove-redaction-dialog.component.ts | 16 +++- .../file-preview-screen.component.ts | 28 +++---- .../services/annotation-actions.service.ts | 19 ++--- .../pdf-annotation-actions.service.ts | 2 +- .../services/pdf-proxy.service.ts | 2 +- .../entity-services/dictionary.service.ts | 18 +++++ .../translations/redact-text-translations.ts | 3 + apps/red-ui/src/assets/config/config.json | 4 +- apps/red-ui/src/assets/i18n/redact/de.json | 3 + apps/red-ui/src/assets/i18n/redact/en.json | 13 ++-- apps/red-ui/src/assets/i18n/scm/de.json | 3 + apps/red-ui/src/assets/i18n/scm/en.json | 13 ++-- libs/common-ui | 2 +- 16 files changed, 137 insertions(+), 67 deletions(-) diff --git a/apps/red-ui/src/app/modules/file-preview/components/annotation-actions/annotation-actions.component.ts b/apps/red-ui/src/app/modules/file-preview/components/annotation-actions/annotation-actions.component.ts index 395c17f01..99dbcd039 100644 --- a/apps/red-ui/src/app/modules/file-preview/components/annotation-actions/annotation-actions.component.ts +++ b/apps/red-ui/src/app/modules/file-preview/components/annotation-actions/annotation-actions.component.ts @@ -79,7 +79,7 @@ export class AnnotationActionsComponent implements OnChanges { } removeOrSuggestRemoveRedaction() { - this.annotationActionsService.removeOrSuggestRemoveRedaction(this.annotations); + this.annotationActionsService.removeOrSuggestRemoveRedaction(this.annotations[0]); } markAsFalsePositive() { diff --git a/apps/red-ui/src/app/modules/file-preview/dialogs/redact-text-dialog/redact-text-dialog.component.html b/apps/red-ui/src/app/modules/file-preview/dialogs/redact-text-dialog/redact-text-dialog.component.html index 9b9426339..a63abac23 100644 --- a/apps/red-ui/src/app/modules/file-preview/dialogs/redact-text-dialog/redact-text-dialog.component.html +++ b/apps/red-ui/src/app/modules/file-preview/dialogs/redact-text-dialog/redact-text-dialog.component.html @@ -72,10 +72,10 @@
diff --git a/apps/red-ui/src/app/modules/file-preview/dialogs/redact-text-dialog/redact-text-dialog.component.ts b/apps/red-ui/src/app/modules/file-preview/dialogs/redact-text-dialog/redact-text-dialog.component.ts index e7db28d0d..641a844f3 100644 --- a/apps/red-ui/src/app/modules/file-preview/dialogs/redact-text-dialog/redact-text-dialog.component.ts +++ b/apps/red-ui/src/app/modules/file-preview/dialogs/redact-text-dialog/redact-text-dialog.component.ts @@ -1,7 +1,7 @@ import { Component, Inject, OnInit } from '@angular/core'; import { BaseDialogComponent, DetailsRadioOption, IqserPermissionsService } from '@iqser/common-ui'; import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; -import { Dictionary, Dossier, File, IAddRedactionRequest } from '@red/domain'; +import { Dictionary, Dossier, File, IAddRedactionRequest, SuperTypes } from '@red/domain'; import { UntypedFormGroup, Validators } from '@angular/forms'; import { Roles } from '@users/roles'; import { firstValueFrom } from 'rxjs'; @@ -10,7 +10,11 @@ import { ActiveDossiersService } from '@services/dossiers/active-dossiers.servic import { LegalBasisOption } from '../manual-redaction-dialog/manual-annotation-dialog.component'; import { DictionaryService } from '@services/entity-services/dictionary.service'; import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker'; -import { ManualRedactionEntryType, ManualRedactionEntryWrapper } from '@models/file/manual-redaction-entry.wrapper'; +import { + ManualRedactionEntryType, + ManualRedactionEntryTypes, + ManualRedactionEntryWrapper, +} from '@models/file/manual-redaction-entry.wrapper'; import { redactTextTranslations } from '@translations/redact-text-translations'; import { RedactTextOption, RedactTextOptions } from './redact-text-options'; import { tap } from 'rxjs/operators'; @@ -30,8 +34,11 @@ export class RedactTextDialogComponent extends BaseDialogComponent implements On legalOptions: LegalBasisOption[] = []; dictionaries: Dictionary[] = []; + #manualRedactionTypeExists = true; + readonly #translations = redactTextTranslations; readonly #dossier: Dossier; + readonly #hint: boolean; constructor( private _justificationsService: JustificationsService, @@ -45,14 +52,31 @@ export class RedactTextDialogComponent extends BaseDialogComponent implements On this.#dossier = _activeDossiersService.find(this.data.dossierId); this.type = this.data.manualRedactionEntryWrapper.type; + this.#hint = this.type === ManualRedactionEntryTypes.HINT; + this.#manualRedactionTypeExists = this._dictionaryService.hasManualType(this.#dossier.dossierTemplateId); this.options = this.#options(); this.form = this.#getForm(); this.initialFormValue = this.form.getRawValue(); } + get displayedDictionaryLabel() { + const dictType = this.form.get('dictionary').value; + if (dictType) { + return this.dictionaries.find(d => d.type === dictType)?.label ?? null; + } + return null; + } + + get disabled() { + if (this.dictionaryRequest || this.#hint) { + return !this.form.get('dictionary').value; + } + return !this.form.get('reason').value; + } + async ngOnInit(): Promise { - this.dictionaries = await this._dictionaryService.getDictionariesOptions(this.#dossier.dossierTemplateId, this.#dossier.id); + this.dictionaries = this._dictionaryService.getPossibleDictionaries(this.#dossier.dossierTemplateId, this.#hint); const data = await firstValueFrom(this._justificationsService.getForDossierTemplate(this.#dossier.dossierTemplateId)); this.legalOptions = data.map(lbm => ({ @@ -60,6 +84,7 @@ export class RedactTextDialogComponent extends BaseDialogComponent implements On description: lbm.description, label: lbm.name, })); + this.legalOptions.sort((a, b) => a.label.localeCompare(b.label)); this.#selectReason(); this.#formatSelectedTextValue(); @@ -70,31 +95,20 @@ export class RedactTextDialogComponent extends BaseDialogComponent implements On .valueChanges.pipe( tap((option: DetailsRadioOption) => { this.dictionaryRequest = option.value === RedactTextOptions.IN_DOSSIER; + this.#resetValues(); }), ) .subscribe(), ); } - get displayedDictionaryLabel() { - const dictType = this.form.get('dictionary').value; - if (dictType) { - return this.dictionaries.find(d => d.type === dictType).label; - } - return null; - } - save(): void { this.#enhanceManualRedaction(this.data.manualRedactionEntryWrapper.manualRedactionEntry); - try { - const annotation = this.data.manualRedactionEntryWrapper.manualRedactionEntry; - this._dialogRef.close({ - annotation, - dictionary: this.dictionaries.find(d => d.type === this.form.get('dictionary').value), - }); - } catch (e) { - this._toaster.error(_('manual-annotation.dialog.error')); - } + const redaction = this.data.manualRedactionEntryWrapper.manualRedactionEntry; + this._dialogRef.close({ + redaction, + dictionary: this.dictionaries.find(d => d.type === this.form.get('dictionary').value), + }); } #getForm(): UntypedFormGroup { @@ -102,9 +116,9 @@ export class RedactTextDialogComponent extends BaseDialogComponent implements On selectedText: this.data?.manualRedactionEntryWrapper?.manualRedactionEntry?.value, reason: [null], comment: [null], - dictionary: [null, Validators.required], - classification: '', - multiplePages: '', + dictionary: [this.#manualRedactionTypeExists ? SuperTypes.ManualRedaction : null], + classification: ['non-readable content'], + section: [null], option: [this.options[0]], }); } @@ -162,9 +176,23 @@ export class RedactTextDialogComponent extends BaseDialogComponent implements On { label: this.#translations[this.type].inDossier.label, description: this.#translations[this.type].inDossier.description, + descriptionParams: { dossierName: this.#dossier.dossierName }, icon: FOLDER_ICON, value: RedactTextOptions.IN_DOSSIER, + // extraOption: { + // label: this.#translations[this.type].inDossier.extraOptionLabel, + // checked: true, + // }, }, ]; } + + #resetValues() { + if (this.dictionaryRequest) { + this.form.get('reason').setValue(null); + this.form.get('dictionary').setValue(null); + return; + } + this.form.get('dictionary').setValue(this.#manualRedactionTypeExists ? SuperTypes.ManualRedaction : null); + } } diff --git a/apps/red-ui/src/app/modules/file-preview/dialogs/remove-redaction-dialog/remove-redaction-dialog.component.ts b/apps/red-ui/src/app/modules/file-preview/dialogs/remove-redaction-dialog/remove-redaction-dialog.component.ts index 942bb7d78..4bb948abc 100644 --- a/apps/red-ui/src/app/modules/file-preview/dialogs/remove-redaction-dialog/remove-redaction-dialog.component.ts +++ b/apps/red-ui/src/app/modules/file-preview/dialogs/remove-redaction-dialog/remove-redaction-dialog.component.ts @@ -4,26 +4,37 @@ import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { RemoveRedactionOption, RemoveRedactionOptions } from './remove-redaction-options'; import { UntypedFormGroup } from '@angular/forms'; import { removeRedactionTranslations } from '@translations/remove-redaction-translations'; +import { AnnotationWrapper } from '@models/file/annotation.wrapper'; +import { Dossier } from '@red/domain'; const PIN_ICON = 'red:push-pin'; const FOLDER_ICON = 'red:folder'; const REMOVE_FROM_DICT_ICON = 'red:remove-from-dict'; +export interface RemoveRedactionData { + redaction: AnnotationWrapper; + dossier: Dossier; + hint: boolean; +} + @Component({ templateUrl: './remove-redaction-dialog.html', styleUrls: ['./remove-redaction-dialog.component.scss'], }) export class RemoveRedactionDialogComponent extends BaseDialogComponent { readonly options: DetailsRadioOption[]; + readonly #redaction: AnnotationWrapper; readonly #translations = removeRedactionTranslations; constructor( protected readonly _dialogRef: MatDialogRef, - @Inject(MAT_DIALOG_DATA) readonly data: NonNullable, + @Inject(MAT_DIALOG_DATA) readonly data: RemoveRedactionData, ) { super(_dialogRef); + this.#redaction = data.redaction; + console.log('redaction: ', this.#redaction); this.options = this.#options(); this.form = this.#getForm(); this.initialFormValue = this.form.getRawValue(); @@ -45,18 +56,21 @@ export class RemoveRedactionDialogComponent extends BaseDialogComponent { { label: this.#translations.ONLY_HERE.label, description: this.#translations.ONLY_HERE.description, + descriptionParams: { value: this.#redaction.value }, icon: PIN_ICON, value: RemoveRedactionOptions.ONLY_HERE, }, { label: this.#translations.IN_DOSSIER.label, description: this.#translations.IN_DOSSIER.description, + descriptionParams: { value: this.#redaction.value }, icon: FOLDER_ICON, value: RemoveRedactionOptions.IN_DOSSIER, }, { label: this.#translations.FALSE_POSITIVE.label, description: this.#translations.FALSE_POSITIVE.description, + descriptionParams: { value: this.#redaction.value, type: this.#redaction.type }, icon: REMOVE_FROM_DICT_ICON, value: RemoveRedactionOptions.FALSE_POSITIVE, }, diff --git a/apps/red-ui/src/app/modules/file-preview/file-preview-screen.component.ts b/apps/red-ui/src/app/modules/file-preview/file-preview-screen.component.ts index 797194dc3..2944806b8 100644 --- a/apps/red-ui/src/app/modules/file-preview/file-preview-screen.component.ts +++ b/apps/red-ui/src/app/modules/file-preview/file-preview-screen.component.ts @@ -163,15 +163,15 @@ export class FilePreviewScreenComponent }); effect(() => { - // const selectedText = this._documentViewer.selectedText(); - // const canPerformActions = this.pdfProxyService.canPerformActions(); - // const isCurrentPageExcluded = this.state.file().isPageExcluded(this.pdf.currentPage()); - // - // if ((selectedText.length > 2 || this._isJapaneseString(selectedText)) && canPerformActions && !isCurrentPageExcluded) { - // this.pdf.enable(textActions); - // } else { - // this.pdf.disable(textActions); - // } + const selectedText = this._documentViewer.selectedText(); + const canPerformActions = this.pdfProxyService.canPerformActions(); + const isCurrentPageExcluded = this.state.file().isPageExcluded(this.pdf.currentPage()); + + if ((selectedText.length > 2 || this._isJapaneseString(selectedText)) && canPerformActions && !isCurrentPageExcluded) { + this.pdf.enable(textActions); + } else { + this.pdf.disable(textActions); + } }); effect(() => { @@ -370,18 +370,12 @@ export class FilePreviewScreenComponent return this._ngZone.run(() => { const file = this.state.file(); - manualRedactionEntryWrapper.manualRedactionEntry.value = 'This is selected text'; this._dialogService.openDialog( 'redactText', { manualRedactionEntryWrapper, dossierId: this.dossierId, file }, - (result: { annotation: IManualRedactionEntry; dictionary?: Dictionary }) => { - const selectedAnnotations = this._annotationManager.selected; - if (selectedAnnotations.length > 0) { - this._annotationManager.delete([selectedAnnotations[0].Id]); - } - + (result: { redaction: IManualRedactionEntry; dictionary?: Dictionary }) => { const add$ = this._manualRedactionService.addAnnotation( - [result.annotation], + [result.redaction], this.dossierId, this.fileId, result.dictionary?.label, diff --git a/apps/red-ui/src/app/modules/file-preview/services/annotation-actions.service.ts b/apps/red-ui/src/app/modules/file-preview/services/annotation-actions.service.ts index 7955420a9..159e517bb 100644 --- a/apps/red-ui/src/app/modules/file-preview/services/annotation-actions.service.ts +++ b/apps/red-ui/src/app/modules/file-preview/services/annotation-actions.service.ts @@ -30,6 +30,7 @@ import { PdfViewer } from '../../pdf-viewer/services/pdf-viewer.service'; import { REDAnnotationManager } from '../../pdf-viewer/services/annotation-manager.service'; import { SkippedService } from './skipped.service'; import { REDDocumentViewer } from '../../pdf-viewer/services/document-viewer.service'; +import { RemoveRedactionData } from '../dialogs/remove-redaction-dialog/remove-redaction-dialog.component'; @Injectable() export class AnnotationActionsService { @@ -129,20 +130,20 @@ export class AnnotationActionsService { }); } - removeOrSuggestRemoveRedaction(annotations: AnnotationWrapper[]) { - const data = { - annotationsToRemove: annotations, + removeOrSuggestRemoveRedaction(redaction: AnnotationWrapper) { + const data: RemoveRedactionData = { + redaction, dossier: this._state.dossier(), - hint: annotations[0].hintDictionary, + hint: redaction.hintDictionary, }; - const { dossierId, fileId } = this._state; this._dialogService.openDialog('removeRedaction', data, (result: { comment: string }) => { - const body = annotations.map(annotation => ({ - annotationId: annotation.id, + const body = { + annotationId: redaction.id, comment: result.comment, - })); + }; + const { dossierId, fileId } = this._state; this.#processObsAndEmit( - this._manualRedactionService.removeOrSuggestRemove(body, dossierId, fileId, true, annotations[0].isHint), + this._manualRedactionService.removeOrSuggestRemove([body], dossierId, fileId, true, redaction.isHint), ).then(); }); } diff --git a/apps/red-ui/src/app/modules/file-preview/services/pdf-annotation-actions.service.ts b/apps/red-ui/src/app/modules/file-preview/services/pdf-annotation-actions.service.ts index d63300232..6e8eda29e 100644 --- a/apps/red-ui/src/app/modules/file-preview/services/pdf-annotation-actions.service.ts +++ b/apps/red-ui/src/app/modules/file-preview/services/pdf-annotation-actions.service.ts @@ -131,7 +131,7 @@ export class PdfAnnotationActionsService { if (permissions.canRemoveOrSuggestToRemoveOnlyHere) { const removeOrSuggestToRemoveButton = this.#getButton('trash', _('annotation-actions.remove-annotation.remove-redaction'), () => - this.#annotationActionsService.removeOrSuggestRemoveRedaction(annotations), + this.#annotationActionsService.removeOrSuggestRemoveRedaction(annotations[0]), ); availableActions.push(removeOrSuggestToRemoveButton); } diff --git a/apps/red-ui/src/app/modules/file-preview/services/pdf-proxy.service.ts b/apps/red-ui/src/app/modules/file-preview/services/pdf-proxy.service.ts index 5fc9b1fe1..3bba6c424 100644 --- a/apps/red-ui/src/app/modules/file-preview/services/pdf-proxy.service.ts +++ b/apps/red-ui/src/app/modules/file-preview/services/pdf-proxy.service.ts @@ -230,7 +230,7 @@ export class PdfProxyService { this._pdf.enable(TEXT_POPUPS_TO_TOGGLE); this._viewerHeaderService.enable(HEADER_ITEMS_TO_TOGGLE); - if (this._documentViewer.selectedText()?.length > 2) { + if (this._documentViewer.selectedText().length > 2) { this._pdf.enable([TextPopups.ADD_DICTIONARY, TextPopups.ADD_FALSE_POSITIVE]); } } else { diff --git a/apps/red-ui/src/app/services/entity-services/dictionary.service.ts b/apps/red-ui/src/app/services/entity-services/dictionary.service.ts index adffdd6d5..46277ea1c 100644 --- a/apps/red-ui/src/app/services/entity-services/dictionary.service.ts +++ b/apps/red-ui/src/app/services/entity-services/dictionary.service.ts @@ -145,6 +145,24 @@ export class DictionaryService extends EntitiesService return !!this._dictionariesMapService.get(dossierTemplateId).find(e => e.type === SuperTypes.ManualRedaction && !e.virtual); } + getPossibleDictionaries(dossierTemplateId: string, hintTypes: boolean): Dictionary[] { + const possibleDictionaries: Dictionary[] = []; + + this._dictionariesMapService.get(dossierTemplateId).forEach((d: Dictionary) => { + if (!hintTypes) { + if (!d.virtual && !d.hint && !d.systemManaged) { + possibleDictionaries.push(d); + } + } else if (d.hint) { + possibleDictionaries.push(d); + } + }); + + possibleDictionaries.sort((a, b) => a.label.localeCompare(b.label)); + + return possibleDictionaries; + } + getRedactionTypes(dossierTemplateId: string): Dictionary[] { const possibleDictionaries: Dictionary[] = []; diff --git a/apps/red-ui/src/app/translations/redact-text-translations.ts b/apps/red-ui/src/app/translations/redact-text-translations.ts index 4eb6f883c..966406528 100644 --- a/apps/red-ui/src/app/translations/redact-text-translations.ts +++ b/apps/red-ui/src/app/translations/redact-text-translations.ts @@ -3,6 +3,7 @@ import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker'; interface Option { label: string; description: string; + extraOptionLabel?: string; } export const redactTextTranslations: Record<'REDACT' | 'HINT', Record<'onlyHere' | 'inDossier', Option>> = { @@ -14,6 +15,7 @@ export const redactTextTranslations: Record<'REDACT' | 'HINT', Record<'onlyHere' inDossier: { label: _('redact-text.dialog.content.options.redact.in-dossier.label'), description: _('redact-text.dialog.content.options.redact.in-dossier.description'), + extraOptionLabel: _('redact-text.dialog.content.options.redact.in-dossier.extraOptionLabel'), }, }, HINT: { @@ -24,6 +26,7 @@ export const redactTextTranslations: Record<'REDACT' | 'HINT', Record<'onlyHere' inDossier: { label: _('redact-text.dialog.content.options.hint.in-dossier.label'), description: _('redact-text.dialog.content.options.hint.in-dossier.description'), + extraOptionLabel: _('redact-text.dialog.content.options.hint.in-dossier.extraOptionLabel'), }, }, } as const; diff --git a/apps/red-ui/src/assets/config/config.json b/apps/red-ui/src/assets/config/config.json index 59c62b3be..1b83a4f2b 100644 --- a/apps/red-ui/src/assets/config/config.json +++ b/apps/red-ui/src/assets/config/config.json @@ -1,7 +1,7 @@ { "ADMIN_CONTACT_NAME": null, "ADMIN_CONTACT_URL": null, - "API_URL": "https://dev-04.iqser.cloud/redaction-gateway-v1", + "API_URL": "https://dev-08.iqser.cloud/redaction-gateway-v1", "APP_NAME": "RedactManager", "AUTO_READ_TIME": 3, "BACKEND_APP_VERSION": "4.4.40", @@ -11,7 +11,7 @@ "MAX_RETRIES_ON_SERVER_ERROR": 3, "OAUTH_CLIENT_ID": "redaction", "OAUTH_IDP_HINT": null, - "OAUTH_URL": "https://dev-04.iqser.cloud/auth", + "OAUTH_URL": "https://dev-08.iqser.cloud/auth", "RECENT_PERIOD_IN_HOURS": 24, "SELECTION_MODE": "structural", "MANUAL_BASE_URL": "https://docs.redactmanager.com/preview", diff --git a/apps/red-ui/src/assets/i18n/redact/de.json b/apps/red-ui/src/assets/i18n/redact/de.json index 9f00d6961..2b45f6636 100644 --- a/apps/red-ui/src/assets/i18n/redact/de.json +++ b/apps/red-ui/src/assets/i18n/redact/de.json @@ -1915,6 +1915,7 @@ "hint": { "in-dossier": { "description": "", + "extraOptionLabel": "", "label": "" }, "only-here": { @@ -1925,6 +1926,7 @@ "redact": { "in-dossier": { "description": "", + "extraOptionLabel": "", "label": "" }, "only-here": { @@ -1939,6 +1941,7 @@ "type": "", "type-placeholder": "" }, + "error": "", "title": "" } }, diff --git a/apps/red-ui/src/assets/i18n/redact/en.json b/apps/red-ui/src/assets/i18n/redact/en.json index 1712bdf73..df39dafe3 100644 --- a/apps/red-ui/src/assets/i18n/redact/en.json +++ b/apps/red-ui/src/assets/i18n/redact/en.json @@ -1914,7 +1914,8 @@ "options": { "hint": { "in-dossier": { - "description": "Add hint in every document in Dossier Alpha.", + "description": "Add hint in every document in {dossierName}.", + "extraOptionLabel": "Apply to all dossiers", "label": "Add hint in dossier" }, "only-here": { @@ -1924,7 +1925,8 @@ }, "redact": { "in-dossier": { - "description": "Add redaction in every document in Dossier Alpha.", + "description": "Add redaction in every document in {dossierName}.", + "extraOptionLabel": "Apply to all dossiers", "label": "Redact in dossier" }, "only-here": { @@ -1939,6 +1941,7 @@ "type": "Type", "type-placeholder": "Select type ..." }, + "error": "Error! Invalid page selection", "title": "Redact text" } }, @@ -1970,15 +1973,15 @@ "comment-placeholder": "Add remarks or mentions ...", "options": { "false-positive": { - "description": "\"White\" is not a CBI Author in this context: \"White rabbits are\".", + "description": "\"{value}\" is not a {type} in this context: \"{context}\".", "label": "False positive" }, "in-dossier": { - "description": "Do not redact \"White\" in any document of the current dossier.", + "description": "Do not redact \"{value}\" in any document of the current dossier.", "label": "Remove in dossier" }, "only-here": { - "description": "Do not redact \"White\" at this position in the current document.", + "description": "Do not redact \"{value}\" at this position in the current document.", "label": "Remove here" } } diff --git a/apps/red-ui/src/assets/i18n/scm/de.json b/apps/red-ui/src/assets/i18n/scm/de.json index a086a88cc..353c3901e 100644 --- a/apps/red-ui/src/assets/i18n/scm/de.json +++ b/apps/red-ui/src/assets/i18n/scm/de.json @@ -1915,6 +1915,7 @@ "hint": { "in-dossier": { "description": "", + "extraOptionLabel": "", "label": "" }, "only-here": { @@ -1925,6 +1926,7 @@ "redact": { "in-dossier": { "description": "", + "extraOptionLabel": "", "label": "" }, "only-here": { @@ -1939,6 +1941,7 @@ "type": "", "type-placeholder": "" }, + "error": "", "title": "" } }, diff --git a/apps/red-ui/src/assets/i18n/scm/en.json b/apps/red-ui/src/assets/i18n/scm/en.json index 2eaf5bbd8..83c2c8246 100644 --- a/apps/red-ui/src/assets/i18n/scm/en.json +++ b/apps/red-ui/src/assets/i18n/scm/en.json @@ -1914,7 +1914,8 @@ "options": { "hint": { "in-dossier": { - "description": "Add hint in every document in Dossier Alpha.", + "description": "Add hint in every document in {dossierName}.", + "extraOptionLabel": "Apply to all dossiers", "label": "Add hint in dossier" }, "only-here": { @@ -1924,7 +1925,8 @@ }, "redact": { "in-dossier": { - "description": "Add redaction in every document in Dossier Alpha.", + "description": "Add redaction in every document in {dossierName}.", + "extraOptionLabel": "Apply to all dossiers", "label": "Redact in dossier" }, "only-here": { @@ -1939,6 +1941,7 @@ "type": "Type", "type-placeholder": "Select type ..." }, + "error": "Error! Invalid page selection", "title": "Redact text" } }, @@ -1970,15 +1973,15 @@ "comment-placeholder": "Add remarks or mentions ...", "options": { "false-positive": { - "description": "\"White\" is not a CBI Author in this context: \"White rabbits are\".", + "description": "\"{value}\" is not a {type} in this context: \"{context}\".", "label": "False positive" }, "in-dossier": { - "description": "Do not redact \"White\" in any document of the current dossier.", + "description": "Do not redact \"{value}\" in any document of the current dossier.", "label": "Remove in dossier" }, "only-here": { - "description": "Do not redact \"White\" at this position in the current document.", + "description": "Do not redact \"{value}\" at this position in the current document.", "label": "Remove here" } } diff --git a/libs/common-ui b/libs/common-ui index 490c01f85..ae296dec4 160000 --- a/libs/common-ui +++ b/libs/common-ui @@ -1 +1 @@ -Subproject commit 490c01f85b4d97e69549b9ad0f206223b9456f5c +Subproject commit ae296dec490ace06295b3baef3777262f8fdc83c From f0ae6b659fd92ee76fe57d760ad2ac0e2e71b272 Mon Sep 17 00:00:00 2001 From: Valentin Mihai Date: Mon, 19 Jun 2023 11:42:31 +0300 Subject: [PATCH 07/10] RED-6774 - updates for remove here, remove in dossier and set as false positive --- .../annotation-actions.component.html | 2 +- .../remove-redaction-dialog.component.ts | 13 +++-- .../remove-redaction-dialog.html | 1 - .../services/annotation-actions.service.ts | 52 +++++++++++++++---- 4 files changed, 50 insertions(+), 18 deletions(-) diff --git a/apps/red-ui/src/app/modules/file-preview/components/annotation-actions/annotation-actions.component.html b/apps/red-ui/src/app/modules/file-preview/components/annotation-actions/annotation-actions.component.html index cf088d9bf..fb06ed7b0 100644 --- a/apps/red-ui/src/app/modules/file-preview/components/annotation-actions/annotation-actions.component.html +++ b/apps/red-ui/src/app/modules/file-preview/components/annotation-actions/annotation-actions.component.html @@ -179,7 +179,7 @@ ; } @Component({ @@ -28,20 +33,18 @@ export class RemoveRedactionDialogComponent extends BaseDialogComponent { readonly #translations = removeRedactionTranslations; constructor( - protected readonly _dialogRef: MatDialogRef, + protected readonly _dialogRef: MatDialogRef, @Inject(MAT_DIALOG_DATA) readonly data: RemoveRedactionData, ) { super(_dialogRef); - this.#redaction = data.redaction; - console.log('redaction: ', this.#redaction); this.options = this.#options(); this.form = this.#getForm(); this.initialFormValue = this.form.getRawValue(); } save(): void { - console.log('test'); + this._dialogRef.close(this.form.getRawValue()); } #getForm(): UntypedFormGroup { diff --git a/apps/red-ui/src/app/modules/file-preview/dialogs/remove-redaction-dialog/remove-redaction-dialog.html b/apps/red-ui/src/app/modules/file-preview/dialogs/remove-redaction-dialog/remove-redaction-dialog.html index 9ef3350bd..5cdedd715 100644 --- a/apps/red-ui/src/app/modules/file-preview/dialogs/remove-redaction-dialog/remove-redaction-dialog.html +++ b/apps/red-ui/src/app/modules/file-preview/dialogs/remove-redaction-dialog/remove-redaction-dialog.html @@ -20,7 +20,6 @@
{ - const body = { - annotationId: redaction.id, - comment: result.comment, - }; - const { dossierId, fileId } = this._state; - this.#processObsAndEmit( - this._manualRedactionService.removeOrSuggestRemove([body], dossierId, fileId, true, redaction.isHint), - ).then(); + + this._dialogService.openDialog('removeRedaction', data, (result: RemoveRedactionResult) => { + if (result.option.value === RemoveRedactionOptions.FALSE_POSITIVE) { + this.#setAsFalsePositive(redaction, result.comment); + } else { + const removeFromDictionary = result.option.value === RemoveRedactionOptions.IN_DOSSIER; + this.#removeRedaction(redaction, result.comment, removeFromDictionary); + } }); } @@ -394,4 +394,34 @@ export class AnnotationActionsService { } return words; } + + #setAsFalsePositive(redaction: AnnotationWrapper, comment: string) { + const request = { + sourceId: redaction.id, + value: this._getFalsePositiveText(redaction), + type: redaction.type, + positions: redaction.positions, + addToDictionary: true, + reason: 'False Positive', + dictionaryEntryType: redaction.isRecommendation + ? DictionaryEntryTypes.FALSE_RECOMMENDATION + : DictionaryEntryTypes.FALSE_POSITIVE, + comment: comment ? { text: comment } : null, + }; + const { dossierId, fileId } = this._state; + + this.#processObsAndEmit(this._manualRedactionService.addAnnotation([request], dossierId, fileId)).then(); + } + + #removeRedaction(redaction: AnnotationWrapper, comment: string, removeFromDictionary: boolean) { + const body = { + annotationId: redaction.id, + comment: comment, + removeFromDictionary, + }; + const { dossierId, fileId } = this._state; + this.#processObsAndEmit( + this._manualRedactionService.removeOrSuggestRemove([body], dossierId, fileId, removeFromDictionary, redaction.isHint), + ).then(); + } } From ba944b1f8cf0ac8ab3b43979d639d576e724e5ec Mon Sep 17 00:00:00 2001 From: Valentin Mihai Date: Tue, 20 Jun 2023 14:48:20 +0300 Subject: [PATCH 08/10] RED-6774 - update common ui --- libs/common-ui | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/common-ui b/libs/common-ui index ae296dec4..afd750015 160000 --- a/libs/common-ui +++ b/libs/common-ui @@ -1 +1 @@ -Subproject commit ae296dec490ace06295b3baef3777262f8fdc83c +Subproject commit afd750015e73c1fc35a76ed21a9a42432cf15277 From bf4b8cba33779d6745f3efce9a9e9e983870568f Mon Sep 17 00:00:00 2001 From: Valentin Mihai Date: Tue, 20 Jun 2023 18:02:20 +0300 Subject: [PATCH 09/10] RED-6774 - checked file permissions for each dialog operation --- .../app/models/file/annotation.permissions.ts | 7 ++ .../annotation-actions.component.html | 50 +++++------ .../annotation-actions.component.ts | 3 +- .../redact-text-dialog.component.ts | 83 +++++++++++-------- .../remove-redaction-dialog.component.ts | 65 ++++++++++----- .../file-preview-screen.component.ts | 39 +++++---- .../services/annotation-actions.service.ts | 35 ++++++-- .../services/file-preview-dialog.service.ts | 12 +-- .../pdf-annotation-actions.service.ts | 49 +++++------ .../services/pdf-proxy.service.ts | 34 ++++---- .../entity-services/dictionary.service.ts | 2 +- apps/red-ui/src/assets/config/config.json | 4 +- libs/common-ui | 2 +- 13 files changed, 220 insertions(+), 165 deletions(-) diff --git a/apps/red-ui/src/app/models/file/annotation.permissions.ts b/apps/red-ui/src/app/models/file/annotation.permissions.ts index 3932f2142..3a06df2db 100644 --- a/apps/red-ui/src/app/models/file/annotation.permissions.ts +++ b/apps/red-ui/src/app/models/file/annotation.permissions.ts @@ -10,6 +10,7 @@ export class AnnotationPermissions { canMarkAsFalsePositive = true; canRemoveOrSuggestToRemoveOnlyHere = true; canRemoveOrSuggestToRemoveFromDictionary = true; + canRemoveOrSuggestToRemoveRedaction = true; canAcceptSuggestion = true; canRejectSuggestion = true; canForceRedaction = true; @@ -63,6 +64,12 @@ export class AnnotationPermissions { !annotation.pending && !annotation.hasBeenResized; + permissions.canRemoveOrSuggestToRemoveRedaction = + annotations.length === 1 && + (permissions.canRemoveOrSuggestToRemoveOnlyHere || + permissions.canRemoveOrSuggestToRemoveFromDictionary || + permissions.canMarkAsFalsePositive); + permissions.canChangeLegalBasis = canAddOrRequestRedaction && annotation.isRedacted && !annotation.pending; permissions.canRecategorizeImage = diff --git a/apps/red-ui/src/app/modules/file-preview/components/annotation-actions/annotation-actions.component.html b/apps/red-ui/src/app/modules/file-preview/components/annotation-actions/annotation-actions.component.html index fb06ed7b0..bec5289bf 100644 --- a/apps/red-ui/src/app/modules/file-preview/components/annotation-actions/annotation-actions.component.html +++ b/apps/red-ui/src/app/modules/file-preview/components/annotation-actions/annotation-actions.component.html @@ -150,36 +150,36 @@ icon="red:visibility" > - + + + + + + + + - + + + + + + + + - + + + + + + + + + implements OnInit +{ readonly roles = Roles; + readonly iconButtonTypes = IconButtonTypes; readonly options: DetailsRadioOption[]; readonly type: ManualRedactionEntryType; dictionaryRequest = false; legalOptions: LegalBasisOption[] = []; dictionaries: Dictionary[] = []; + form!: UntypedFormGroup; #manualRedactionTypeExists = true; @@ -41,15 +58,13 @@ export class RedactTextDialogComponent extends BaseDialogComponent implements On readonly #hint: boolean; constructor( - private _justificationsService: JustificationsService, - private _activeDossiersService: ActiveDossiersService, - private _dictionaryService: DictionaryService, - private _iqserPermissionsService: IqserPermissionsService, - protected readonly _dialogRef: MatDialogRef, - @Inject(MAT_DIALOG_DATA) readonly data: { manualRedactionEntryWrapper: ManualRedactionEntryWrapper; dossierId: string; file: File }, + private readonly _justificationsService: JustificationsService, + private readonly _activeDossiersService: ActiveDossiersService, + private readonly _dictionaryService: DictionaryService, + private readonly _iqserPermissionsService: IqserPermissionsService, + private readonly _formBuilder: FormBuilder, ) { - super(_dialogRef); - + super(); this.#dossier = _activeDossiersService.find(this.data.dossierId); this.type = this.data.manualRedactionEntryWrapper.type; this.#hint = this.type === ManualRedactionEntryTypes.HINT; @@ -57,7 +72,18 @@ export class RedactTextDialogComponent extends BaseDialogComponent implements On this.options = this.#options(); this.form = this.#getForm(); - this.initialFormValue = this.form.getRawValue(); + + this.form + .get('option') + .valueChanges.pipe( + tap((option: DetailsRadioOption) => { + this.dictionaryRequest = option.value === RedactTextOptions.IN_DOSSIER; + console.log('this.dictionaryRequest: ', this.dictionaryRequest); + this.#resetValues(); + }), + takeUntilDestroyed(), + ) + .subscribe(); } get displayedDictionaryLabel() { @@ -88,24 +114,12 @@ export class RedactTextDialogComponent extends BaseDialogComponent implements On this.#selectReason(); this.#formatSelectedTextValue(); - - this._subscriptions.add( - this.form - .get('option') - .valueChanges.pipe( - tap((option: DetailsRadioOption) => { - this.dictionaryRequest = option.value === RedactTextOptions.IN_DOSSIER; - this.#resetValues(); - }), - ) - .subscribe(), - ); } save(): void { this.#enhanceManualRedaction(this.data.manualRedactionEntryWrapper.manualRedactionEntry); const redaction = this.data.manualRedactionEntryWrapper.manualRedactionEntry; - this._dialogRef.close({ + this.dialogRef.close({ redaction, dictionary: this.dictionaries.find(d => d.type === this.form.get('dictionary').value), }); @@ -166,14 +180,16 @@ export class RedactTextDialogComponent extends BaseDialogComponent implements On } #options() { - return [ + const options: DetailsRadioOption[] = [ { label: this.#translations[this.type].onlyHere.label, description: this.#translations[this.type].onlyHere.description, icon: PIN_ICON, value: RedactTextOptions.ONLY_HERE, }, - { + ]; + if (!this._iqserPermissionsService.has(Roles.getRss)) { + options.push({ label: this.#translations[this.type].inDossier.label, description: this.#translations[this.type].inDossier.description, descriptionParams: { dossierName: this.#dossier.dossierName }, @@ -183,8 +199,9 @@ export class RedactTextDialogComponent extends BaseDialogComponent implements On // label: this.#translations[this.type].inDossier.extraOptionLabel, // checked: true, // }, - }, - ]; + }); + } + return options; } #resetValues() { diff --git a/apps/red-ui/src/app/modules/file-preview/dialogs/remove-redaction-dialog/remove-redaction-dialog.component.ts b/apps/red-ui/src/app/modules/file-preview/dialogs/remove-redaction-dialog/remove-redaction-dialog.component.ts index 699365f47..a9832aef3 100644 --- a/apps/red-ui/src/app/modules/file-preview/dialogs/remove-redaction-dialog/remove-redaction-dialog.component.ts +++ b/apps/red-ui/src/app/modules/file-preview/dialogs/remove-redaction-dialog/remove-redaction-dialog.component.ts @@ -1,20 +1,28 @@ -import { Component, Inject } from '@angular/core'; -import { BaseDialogComponent, DetailsRadioOption } from '@iqser/common-ui'; -import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; +import { Component } from '@angular/core'; +import { DetailsRadioOption, IconButtonTypes } from '@iqser/common-ui'; import { RemoveRedactionOption, RemoveRedactionOptions } from './remove-redaction-options'; -import { UntypedFormGroup } from '@angular/forms'; +import { FormBuilder, UntypedFormGroup } from '@angular/forms'; import { removeRedactionTranslations } from '@translations/remove-redaction-translations'; import { AnnotationWrapper } from '@models/file/annotation.wrapper'; import { Dossier } from '@red/domain'; +import { IqserDialogComponent } from '../../../../../../../../libs/common-ui/src/lib/dialog/iqser-dialog-component.directive'; +import { PermissionsService } from '@services/permissions.service'; const PIN_ICON = 'red:push-pin'; const FOLDER_ICON = 'red:folder'; const REMOVE_FROM_DICT_ICON = 'red:remove-from-dict'; +export interface RemoveRedactionPermissions { + canRemoveOrSuggestToRemoveOnlyHere: boolean; + canRemoveOrSuggestToRemoveFromDictionary: boolean; + canMarkAsFalsePositive: boolean; +} + export interface RemoveRedactionData { redaction: AnnotationWrapper; dossier: Dossier; falsePositiveContext: string; + permissions: RemoveRedactionPermissions; } export interface RemoveRedactionResult { @@ -26,25 +34,30 @@ export interface RemoveRedactionResult { templateUrl: './remove-redaction-dialog.html', styleUrls: ['./remove-redaction-dialog.component.scss'], }) -export class RemoveRedactionDialogComponent extends BaseDialogComponent { +export class RemoveRedactionDialogComponent extends IqserDialogComponent< + RemoveRedactionDialogComponent, + RemoveRedactionData, + RemoveRedactionResult +> { + readonly iconButtonTypes = IconButtonTypes; readonly options: DetailsRadioOption[]; - readonly #redaction: AnnotationWrapper; + form!: UntypedFormGroup; + + readonly #redaction: AnnotationWrapper; + readonly #permissions: RemoveRedactionPermissions; readonly #translations = removeRedactionTranslations; - constructor( - protected readonly _dialogRef: MatDialogRef, - @Inject(MAT_DIALOG_DATA) readonly data: RemoveRedactionData, - ) { - super(_dialogRef); - this.#redaction = data.redaction; + constructor(private readonly _formBuilder: FormBuilder, private readonly _permissionsService: PermissionsService) { + super(); + this.#redaction = this.data.redaction; + this.#permissions = this.data.permissions; this.options = this.#options(); this.form = this.#getForm(); - this.initialFormValue = this.form.getRawValue(); } save(): void { - this._dialogRef.close(this.form.getRawValue()); + this.dialogRef.close(this.form.getRawValue()); } #getForm(): UntypedFormGroup { @@ -55,28 +68,34 @@ export class RemoveRedactionDialogComponent extends BaseDialogComponent { } #options() { - return [ - { + const options: DetailsRadioOption[] = []; + if (this.#permissions.canRemoveOrSuggestToRemoveOnlyHere) { + options.push({ label: this.#translations.ONLY_HERE.label, description: this.#translations.ONLY_HERE.description, descriptionParams: { value: this.#redaction.value }, icon: PIN_ICON, value: RemoveRedactionOptions.ONLY_HERE, - }, - { + }); + } + if (this.#permissions.canRemoveOrSuggestToRemoveFromDictionary) { + options.push({ label: this.#translations.IN_DOSSIER.label, description: this.#translations.IN_DOSSIER.description, descriptionParams: { value: this.#redaction.value }, icon: FOLDER_ICON, value: RemoveRedactionOptions.IN_DOSSIER, - }, - { + }); + } + if (this.#permissions.canMarkAsFalsePositive) { + options.push({ label: this.#translations.FALSE_POSITIVE.label, description: this.#translations.FALSE_POSITIVE.description, - descriptionParams: { value: this.#redaction.value, type: this.#redaction.type }, + descriptionParams: { value: this.#redaction.value, type: this.#redaction.type, context: this.data.falsePositiveContext }, icon: REMOVE_FROM_DICT_ICON, value: RemoveRedactionOptions.FALSE_POSITIVE, - }, - ]; + }); + } + return options; } } diff --git a/apps/red-ui/src/app/modules/file-preview/file-preview-screen.component.ts b/apps/red-ui/src/app/modules/file-preview/file-preview-screen.component.ts index 2944806b8..f09e02638 100644 --- a/apps/red-ui/src/app/modules/file-preview/file-preview-screen.component.ts +++ b/apps/red-ui/src/app/modules/file-preview/file-preview-screen.component.ts @@ -74,6 +74,9 @@ import { ConfigService } from '@services/config.service'; import { ReadableRedactionsService } from '../pdf-viewer/services/readable-redactions.service'; import { Roles } from '@users/roles'; import { SuggestionsService } from './services/suggestions.service'; +import { IqserDialog } from '../../../../../../libs/common-ui/src/lib/dialog/iqser-dialog.service'; +import { RedactTextDialogComponent } from './dialogs/redact-text-dialog/redact-text-dialog.component'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; const textActions = [TextPopups.ADD_DICTIONARY, TextPopups.ADD_FALSE_POSITIVE]; @@ -124,6 +127,7 @@ export class FilePreviewScreenComponent private readonly _changeRef: ChangeDetectorRef, private readonly _tenantsService: TenantsService, private readonly _dialogService: FilePreviewDialogService, + private readonly _iqserDialog: IqserDialog, private readonly _pageRotationService: PageRotationService, private readonly _viewerHeaderService: ViewerHeaderService, private readonly _annotationDrawService: AnnotationDrawService, @@ -366,26 +370,25 @@ export class FilePreviewScreenComponent ); } - openRedactTextDialog(manualRedactionEntryWrapper: ManualRedactionEntryWrapper) { - return this._ngZone.run(() => { - const file = this.state.file(); + async openRedactTextDialog(manualRedactionEntryWrapper: ManualRedactionEntryWrapper) { + const file = this.state.file(); + const result = await this._iqserDialog + .openDefault(RedactTextDialogComponent, { + data: { manualRedactionEntryWrapper, dossierId: this.dossierId, file }, + }) + .result(); - this._dialogService.openDialog( - 'redactText', - { manualRedactionEntryWrapper, dossierId: this.dossierId, file }, - (result: { redaction: IManualRedactionEntry; dictionary?: Dictionary }) => { - const add$ = this._manualRedactionService.addAnnotation( - [result.redaction], - this.dossierId, - this.fileId, - result.dictionary?.label, - ); - - const addAndReload$ = add$.pipe(switchMap(() => this._filesService.reload(this.dossierId, file))); - return firstValueFrom(addAndReload$.pipe(catchError(() => of(undefined)))); - }, + if (result) { + const add$ = this._manualRedactionService.addAnnotation( + [result.redaction], + this.dossierId, + this.fileId, + result.dictionary?.label, ); - }); + + const addAndReload$ = add$.pipe(switchMap(() => this._filesService.reload(this.dossierId, file))); + return firstValueFrom(addAndReload$.pipe(catchError(() => of(undefined)))); + } } toggleFullScreen() { diff --git a/apps/red-ui/src/app/modules/file-preview/services/annotation-actions.service.ts b/apps/red-ui/src/app/modules/file-preview/services/annotation-actions.service.ts index c0f4c1274..0625c2ca0 100644 --- a/apps/red-ui/src/app/modules/file-preview/services/annotation-actions.service.ts +++ b/apps/red-ui/src/app/modules/file-preview/services/annotation-actions.service.ts @@ -1,7 +1,7 @@ import { Injectable } from '@angular/core'; import { ManualRedactionService } from './manual-redaction.service'; import { AnnotationWrapper } from '@models/file/annotation.wrapper'; -import { firstValueFrom, Observable } from 'rxjs'; +import { firstValueFrom, Observable, of } from 'rxjs'; import { getFirstRelevantTextPart } from '../../../utils'; import { Core } from '@pdftron/webviewer'; import { @@ -30,8 +30,13 @@ import { PdfViewer } from '../../pdf-viewer/services/pdf-viewer.service'; import { REDAnnotationManager } from '../../pdf-viewer/services/annotation-manager.service'; import { SkippedService } from './skipped.service'; import { REDDocumentViewer } from '../../pdf-viewer/services/document-viewer.service'; -import { RemoveRedactionData, RemoveRedactionResult } from '../dialogs/remove-redaction-dialog/remove-redaction-dialog.component'; +import { + RemoveRedactionDialogComponent, + RemoveRedactionPermissions, +} from '../dialogs/remove-redaction-dialog/remove-redaction-dialog.component'; import { RemoveRedactionOptions } from '../dialogs/remove-redaction-dialog/remove-redaction-options'; +import { IqserDialog } from '../../../../../../../libs/common-ui/src/lib/dialog/iqser-dialog.service'; +import { AnnotationPermissions } from '@models/file/annotation.permissions'; @Injectable() export class AnnotationActionsService { @@ -39,6 +44,7 @@ export class AnnotationActionsService { private readonly _manualRedactionService: ManualRedactionService, private readonly _dialogService: FilePreviewDialogService, private readonly _dialog: MatDialog, + private readonly _iqserDialog: IqserDialog, private readonly _pdf: PdfViewer, private readonly _documentViewer: REDDocumentViewer, private readonly _annotationManager: REDAnnotationManager, @@ -131,21 +137,32 @@ export class AnnotationActionsService { }); } - removeOrSuggestRemoveRedaction(redaction: AnnotationWrapper) { - const data: RemoveRedactionData = { - redaction, - dossier: this._state.dossier(), - falsePositiveContext: this._getFalsePositiveText(redaction), + async removeOrSuggestRemoveRedaction(redaction: AnnotationWrapper, permissions) { + const removePermissions: RemoveRedactionPermissions = { + canRemoveOrSuggestToRemoveOnlyHere: permissions.canRemoveOrSuggestToRemoveOnlyHere, + canRemoveOrSuggestToRemoveFromDictionary: permissions.canRemoveOrSuggestToRemoveFromDictionary, + canMarkAsFalsePositive: permissions.canMarkAsFalsePositive, }; - this._dialogService.openDialog('removeRedaction', data, (result: RemoveRedactionResult) => { + const result = await this._iqserDialog + .openDefault(RemoveRedactionDialogComponent, { + data: { + redaction, + dossier: this._state.dossier(), + falsePositiveContext: this._getFalsePositiveText(redaction), + permissions: removePermissions, + }, + }) + .result(); + + if (result) { if (result.option.value === RemoveRedactionOptions.FALSE_POSITIVE) { this.#setAsFalsePositive(redaction, result.comment); } else { const removeFromDictionary = result.option.value === RemoveRedactionOptions.IN_DOSSIER; this.#removeRedaction(redaction, result.comment, removeFromDictionary); } - }); + } } recategorizeImages(annotations: AnnotationWrapper[]) { diff --git a/apps/red-ui/src/app/modules/file-preview/services/file-preview-dialog.service.ts b/apps/red-ui/src/app/modules/file-preview/services/file-preview-dialog.service.ts index e3f995715..e8abdb76b 100644 --- a/apps/red-ui/src/app/modules/file-preview/services/file-preview-dialog.service.ts +++ b/apps/red-ui/src/app/modules/file-preview/services/file-preview-dialog.service.ts @@ -11,8 +11,6 @@ import { ResizeAnnotationDialogComponent } from '../dialogs/resize-annotation-di import { HighlightActionDialogComponent } from '../dialogs/highlight-action-dialog/highlight-action-dialog.component'; import { RssDialogComponent } from '../dialogs/rss-dialog/rss-dialog.component'; import { FalsePositiveDialogComponent } from '../dialogs/false-positive-dialog/false-positive-dialog.component'; -import { RedactTextDialogComponent } from '../dialogs/redact-text-dialog/redact-text-dialog.component'; -import { RemoveRedactionDialogComponent } from '../dialogs/remove-redaction-dialog/remove-redaction-dialog.component'; type DialogType = | 'confirm' @@ -25,9 +23,7 @@ type DialogType = | 'forceAnnotation' | 'manualAnnotation' | 'highlightAction' - | 'falsePositive' - | 'redactText' - | 'removeRedaction'; + | 'falsePositive'; @Injectable() export class FilePreviewDialogService extends DialogService { @@ -69,12 +65,6 @@ export class FilePreviewDialogService extends DialogService { falsePositive: { component: FalsePositiveDialogComponent, }, - redactText: { - component: RedactTextDialogComponent, - }, - removeRedaction: { - component: RemoveRedactionDialogComponent, - }, }; constructor(protected readonly _dialog: MatDialog) { diff --git a/apps/red-ui/src/app/modules/file-preview/services/pdf-annotation-actions.service.ts b/apps/red-ui/src/app/modules/file-preview/services/pdf-annotation-actions.service.ts index 6e8eda29e..8af835fd4 100644 --- a/apps/red-ui/src/app/modules/file-preview/services/pdf-annotation-actions.service.ts +++ b/apps/red-ui/src/app/modules/file-preview/services/pdf-annotation-actions.service.ts @@ -62,14 +62,14 @@ export class PdfAnnotationActionsService { availableActions.push(recategorizeButton); } - if (permissions.canRemoveOrSuggestToRemoveFromDictionary) { - const removeFromDictButton = this.#getButton( - 'remove-from-dict', - _('annotation-actions.remove-annotation.remove-from-dict'), - () => this.#annotationActionsService.removeOrSuggestRemoveAnnotation(annotations, true), - ); - availableActions.push(removeFromDictButton); - } + // if (permissions.canRemoveOrSuggestToRemoveFromDictionary) { + // const removeFromDictButton = this.#getButton( + // 'remove-from-dict', + // _('annotation-actions.remove-annotation.remove-from-dict'), + // () => this.#annotationActionsService.removeOrSuggestRemoveAnnotation(annotations, true), + // ); + // availableActions.push(removeFromDictButton); + // } if (permissions.canAcceptRecommendation) { const acceptRecommendationButton = this.#getButton('check', _('annotation-actions.accept-recommendation.label'), () => @@ -92,12 +92,12 @@ export class PdfAnnotationActionsService { availableActions.push(undoButton); } - if (permissions.canMarkAsFalsePositive) { - const markAsFalsePositiveButton = this.#getButton('thumb-down', _('annotation-actions.remove-annotation.false-positive'), () => - this.#annotationActionsService.markAsFalsePositive(annotations), - ); - availableActions.push(markAsFalsePositiveButton); - } + // if (permissions.canMarkAsFalsePositive) { + // const markAsFalsePositiveButton = this.#getButton('thumb-down', _('annotation-actions.remove-annotation.false-positive'), () => + // this.#annotationActionsService.markAsFalsePositive(annotations), + // ); + // availableActions.push(markAsFalsePositiveButton); + // } if (permissions.canForceRedaction) { const forceRedactionButton = this.#getButton('thumb-up', _('annotation-actions.force-redaction.label'), () => @@ -120,18 +120,18 @@ export class PdfAnnotationActionsService { availableActions.push(rejectSuggestionButton); } - if (permissions.canRemoveOrSuggestToRemoveOnlyHere) { - const removeOrSuggestToRemoveOnlyHereButton = this.#getButton( - 'trash', - _('annotation-actions.remove-annotation.only-here'), - () => this.#annotationActionsService.removeOrSuggestRemoveAnnotation(annotations, false), - ); - availableActions.push(removeOrSuggestToRemoveOnlyHereButton); - } + // if (permissions.canRemoveOrSuggestToRemoveOnlyHere) { + // const removeOrSuggestToRemoveOnlyHereButton = this.#getButton( + // 'trash', + // _('annotation-actions.remove-annotation.only-here'), + // () => this.#annotationActionsService.removeOrSuggestRemoveAnnotation(annotations, false), + // ); + // availableActions.push(removeOrSuggestToRemoveOnlyHereButton); + // } - if (permissions.canRemoveOrSuggestToRemoveOnlyHere) { + if (permissions.canRemoveOrSuggestToRemoveRedaction) { const removeOrSuggestToRemoveButton = this.#getButton('trash', _('annotation-actions.remove-annotation.remove-redaction'), () => - this.#annotationActionsService.removeOrSuggestRemoveRedaction(annotations[0]), + this.#annotationActionsService.removeOrSuggestRemoveRedaction(annotations[0], permissions), ); availableActions.push(removeOrSuggestToRemoveButton); } @@ -171,6 +171,7 @@ export class PdfAnnotationActionsService { canForceHint: permissions.reduce((acc, next) => acc && next.canForceHint, true), canRejectSuggestion: permissions.reduce((acc, next) => acc && next.canRejectSuggestion, true), canRemoveOrSuggestToRemoveOnlyHere: permissions.reduce((acc, next) => acc && next.canRemoveOrSuggestToRemoveOnlyHere, true), + canRemoveOrSuggestToRemoveRedaction: permissions.reduce((acc, next) => acc && next.canRemoveOrSuggestToRemoveRedaction, true), }; } } diff --git a/apps/red-ui/src/app/modules/file-preview/services/pdf-proxy.service.ts b/apps/red-ui/src/app/modules/file-preview/services/pdf-proxy.service.ts index 3bba6c424..d668be0f2 100644 --- a/apps/red-ui/src/app/modules/file-preview/services/pdf-proxy.service.ts +++ b/apps/red-ui/src/app/modules/file-preview/services/pdf-proxy.service.ts @@ -172,23 +172,23 @@ export class PdfProxyService { onClick: () => this._ngZone.run(() => this._redactText(ManualRedactionEntryTypes.HINT)), }); - popups.push({ - type: 'actionButton', - dataElement: TextPopups.ADD_REDACTION, - img: this.#addRedactionIcon, - title: this.#getTitle(ManualRedactionEntryTypes.REDACTION), - onClick: () => this._ngZone.run(() => this._addManualRedactionOfType(ManualRedactionEntryTypes.REDACTION)), - }); - - if (!this._iqserPermissionsService.has(Roles.getRss)) { - popups.push({ - type: 'actionButton', - dataElement: TextPopups.ADD_DICTIONARY, - img: this.#addDictIcon, - title: this.#getTitle(ManualRedactionEntryTypes.DICTIONARY), - onClick: () => this._ngZone.run(() => this._addManualRedactionOfType(ManualRedactionEntryTypes.DICTIONARY)), - }); - } + // popups.push({ + // type: 'actionButton', + // dataElement: TextPopups.ADD_REDACTION, + // img: this.#addRedactionIcon, + // title: this.#getTitle(ManualRedactionEntryTypes.REDACTION), + // onClick: () => this._ngZone.run(() => this._addManualRedactionOfType(ManualRedactionEntryTypes.REDACTION)), + // }); + // + // if (!this._iqserPermissionsService.has(Roles.getRss)) { + // popups.push({ + // type: 'actionButton', + // dataElement: TextPopups.ADD_DICTIONARY, + // img: this.#addDictIcon, + // title: this.#getTitle(ManualRedactionEntryTypes.DICTIONARY), + // onClick: () => this._ngZone.run(() => this._addManualRedactionOfType(ManualRedactionEntryTypes.DICTIONARY)), + // }); + // } } } diff --git a/apps/red-ui/src/app/services/entity-services/dictionary.service.ts b/apps/red-ui/src/app/services/entity-services/dictionary.service.ts index 46277ea1c..32f63b54f 100644 --- a/apps/red-ui/src/app/services/entity-services/dictionary.service.ts +++ b/apps/red-ui/src/app/services/entity-services/dictionary.service.ts @@ -150,7 +150,7 @@ export class DictionaryService extends EntitiesService this._dictionariesMapService.get(dossierTemplateId).forEach((d: Dictionary) => { if (!hintTypes) { - if (!d.virtual && !d.hint && !d.systemManaged) { + if (!d.hint) { possibleDictionaries.push(d); } } else if (d.hint) { diff --git a/apps/red-ui/src/assets/config/config.json b/apps/red-ui/src/assets/config/config.json index 135b0a653..378c6448c 100644 --- a/apps/red-ui/src/assets/config/config.json +++ b/apps/red-ui/src/assets/config/config.json @@ -1,7 +1,7 @@ { "ADMIN_CONTACT_NAME": null, "ADMIN_CONTACT_URL": null, - "API_URL": "https://dev-08.iqser.cloud/redaction-gateway-v1", + "API_URL": "https://dev-04.iqser.cloud/redaction-gateway-v1", "APP_NAME": "RedactManager", "AUTO_READ_TIME": 3, "BACKEND_APP_VERSION": "4.4.40", @@ -11,7 +11,7 @@ "MAX_RETRIES_ON_SERVER_ERROR": 3, "OAUTH_CLIENT_ID": "redaction", "OAUTH_IDP_HINT": null, - "OAUTH_URL": "https://dev-08.iqser.cloud/auth", + "OAUTH_URL": "https://dev-04.iqser.cloud/auth", "RECENT_PERIOD_IN_HOURS": 24, "SELECTION_MODE": "structural", "MANUAL_BASE_URL": "https://docs.redactmanager.com/preview", diff --git a/libs/common-ui b/libs/common-ui index afd750015..6b45d4aa7 160000 --- a/libs/common-ui +++ b/libs/common-ui @@ -1 +1 @@ -Subproject commit afd750015e73c1fc35a76ed21a9a42432cf15277 +Subproject commit 6b45d4aa70554b5f41e59d689094d6dad8291c8e From 14139ab40577521ecc2212833bc691184392acb2 Mon Sep 17 00:00:00 2001 From: Valentin Mihai Date: Wed, 21 Jun 2023 00:11:00 +0300 Subject: [PATCH 10/10] RED-6774 - added "Apply to all dossiers" extra option for detailed options component --- .../redact-text-dialog.component.html | 6 ++++- .../redact-text-dialog.component.ts | 22 ++++++++++++++----- .../remove-redaction-dialog.component.ts | 22 +++++++++++++++++++ .../file-preview-screen.component.ts | 10 ++++++++- .../services/annotation-actions.service.ts | 13 +++++++++-- .../remove-redaction-translations.ts | 3 +++ apps/red-ui/src/assets/i18n/redact/de.json | 22 +++++++++++-------- apps/red-ui/src/assets/i18n/redact/en.json | 22 +++++++++++-------- apps/red-ui/src/assets/i18n/scm/de.json | 16 ++++++++++---- apps/red-ui/src/assets/i18n/scm/en.json | 16 ++++++++++---- 10 files changed, 116 insertions(+), 36 deletions(-) diff --git a/apps/red-ui/src/app/modules/file-preview/dialogs/redact-text-dialog/redact-text-dialog.component.html b/apps/red-ui/src/app/modules/file-preview/dialogs/redact-text-dialog/redact-text-dialog.component.html index a63abac23..f0652bbb7 100644 --- a/apps/red-ui/src/app/modules/file-preview/dialogs/redact-text-dialog/redact-text-dialog.component.html +++ b/apps/red-ui/src/app/modules/file-preview/dialogs/redact-text-dialog/redact-text-dialog.component.html @@ -8,7 +8,11 @@ {{ form.get('selectedText').value }}
- +
diff --git a/apps/red-ui/src/app/modules/file-preview/dialogs/redact-text-dialog/redact-text-dialog.component.ts b/apps/red-ui/src/app/modules/file-preview/dialogs/redact-text-dialog/redact-text-dialog.component.ts index f535dd0e3..d9ab15bdc 100644 --- a/apps/red-ui/src/app/modules/file-preview/dialogs/redact-text-dialog/redact-text-dialog.component.ts +++ b/apps/red-ui/src/app/modules/file-preview/dialogs/redact-text-dialog/redact-text-dialog.component.ts @@ -1,6 +1,6 @@ import { Component, OnInit } from '@angular/core'; import { DetailsRadioOption, IconButtonTypes, IqserPermissionsService } from '@iqser/common-ui'; -import { Dictionary, Dossier, File, IAddRedactionRequest, IManualRedactionEntry, SuperTypes } from '@red/domain'; +import { Dictionary, Dossier, DossierTemplate, File, IAddRedactionRequest, IManualRedactionEntry, SuperTypes } from '@red/domain'; import { FormBuilder, UntypedFormGroup } from '@angular/forms'; import { Roles } from '@users/roles'; import { firstValueFrom } from 'rxjs'; @@ -19,6 +19,7 @@ import { RedactTextOption, RedactTextOptions } from './redact-text-options'; import { tap } from 'rxjs/operators'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { IqserDialogComponent } from '../../../../../../../../libs/common-ui/src/lib/dialog/iqser-dialog-component.directive'; +import { DossierTemplatesService } from '@services/dossier-templates/dossier-templates.service'; const PIN_ICON = 'red:push-pin'; const FOLDER_ICON = 'red:folder'; @@ -32,6 +33,7 @@ interface RedactTextData { interface DialogResult { redaction: IManualRedactionEntry; dictionary: Dictionary; + applyToAllDossiers: boolean | null; } @Component({ @@ -52,6 +54,7 @@ export class RedactTextDialogComponent form!: UntypedFormGroup; #manualRedactionTypeExists = true; + #applyToAllDossiers = true; readonly #translations = redactTextTranslations; readonly #dossier: Dossier; @@ -62,6 +65,7 @@ export class RedactTextDialogComponent private readonly _activeDossiersService: ActiveDossiersService, private readonly _dictionaryService: DictionaryService, private readonly _iqserPermissionsService: IqserPermissionsService, + private readonly _dossierTemplatesService: DossierTemplatesService, private readonly _formBuilder: FormBuilder, ) { super(); @@ -78,7 +82,6 @@ export class RedactTextDialogComponent .valueChanges.pipe( tap((option: DetailsRadioOption) => { this.dictionaryRequest = option.value === RedactTextOptions.IN_DOSSIER; - console.log('this.dictionaryRequest: ', this.dictionaryRequest); this.#resetValues(); }), takeUntilDestroyed(), @@ -116,12 +119,17 @@ export class RedactTextDialogComponent this.#formatSelectedTextValue(); } + extraOptionChanged(option: DetailsRadioOption): void { + this.#applyToAllDossiers = option.extraOption.checked; + } + save(): void { this.#enhanceManualRedaction(this.data.manualRedactionEntryWrapper.manualRedactionEntry); const redaction = this.data.manualRedactionEntryWrapper.manualRedactionEntry; this.dialogRef.close({ redaction, dictionary: this.dictionaries.find(d => d.type === this.form.get('dictionary').value), + applyToAllDossiers: this.dictionaryRequest ? this.#applyToAllDossiers : null, }); } @@ -195,16 +203,18 @@ export class RedactTextDialogComponent descriptionParams: { dossierName: this.#dossier.dossierName }, icon: FOLDER_ICON, value: RedactTextOptions.IN_DOSSIER, - // extraOption: { - // label: this.#translations[this.type].inDossier.extraOptionLabel, - // checked: true, - // }, + extraOption: { + label: this.#translations[this.type].inDossier.extraOptionLabel, + checked: true, + }, }); } return options; } #resetValues() { + this.#applyToAllDossiers = true; + this.options[1].extraOption.checked = true; if (this.dictionaryRequest) { this.form.get('reason').setValue(null); this.form.get('dictionary').setValue(null); diff --git a/apps/red-ui/src/app/modules/file-preview/dialogs/remove-redaction-dialog/remove-redaction-dialog.component.ts b/apps/red-ui/src/app/modules/file-preview/dialogs/remove-redaction-dialog/remove-redaction-dialog.component.ts index a9832aef3..1ffd08512 100644 --- a/apps/red-ui/src/app/modules/file-preview/dialogs/remove-redaction-dialog/remove-redaction-dialog.component.ts +++ b/apps/red-ui/src/app/modules/file-preview/dialogs/remove-redaction-dialog/remove-redaction-dialog.component.ts @@ -7,6 +7,8 @@ import { AnnotationWrapper } from '@models/file/annotation.wrapper'; import { Dossier } from '@red/domain'; import { IqserDialogComponent } from '../../../../../../../../libs/common-ui/src/lib/dialog/iqser-dialog-component.directive'; import { PermissionsService } from '@services/permissions.service'; +import { tap } from 'rxjs/operators'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; const PIN_ICON = 'red:push-pin'; const FOLDER_ICON = 'red:folder'; @@ -28,6 +30,7 @@ export interface RemoveRedactionData { export interface RemoveRedactionResult { comment: string; option: DetailsRadioOption; + applyToAllDossiers: boolean | null; } @Component({ @@ -54,6 +57,17 @@ export class RemoveRedactionDialogComponent extends IqserDialogComponent< this.#permissions = this.data.permissions; this.options = this.#options(); this.form = this.#getForm(); + + this.form + .get('option') + .valueChanges.pipe( + tap(() => { + this.options[1].extraOption.checked = true; + this.options[2].extraOption.checked = true; + }), + takeUntilDestroyed(), + ) + .subscribe(); } save(): void { @@ -85,6 +99,10 @@ export class RemoveRedactionDialogComponent extends IqserDialogComponent< descriptionParams: { value: this.#redaction.value }, icon: FOLDER_ICON, value: RemoveRedactionOptions.IN_DOSSIER, + extraOption: { + label: this.#translations.IN_DOSSIER.extraOptionLabel, + checked: true, + }, }); } if (this.#permissions.canMarkAsFalsePositive) { @@ -94,6 +112,10 @@ export class RemoveRedactionDialogComponent extends IqserDialogComponent< descriptionParams: { value: this.#redaction.value, type: this.#redaction.type, context: this.data.falsePositiveContext }, icon: REMOVE_FROM_DICT_ICON, value: RemoveRedactionOptions.FALSE_POSITIVE, + extraOption: { + label: this.#translations.FALSE_POSITIVE.extraOptionLabel, + checked: true, + }, }); } return options; diff --git a/apps/red-ui/src/app/modules/file-preview/file-preview-screen.component.ts b/apps/red-ui/src/app/modules/file-preview/file-preview-screen.component.ts index f09e02638..f61b83323 100644 --- a/apps/red-ui/src/app/modules/file-preview/file-preview-screen.component.ts +++ b/apps/red-ui/src/app/modules/file-preview/file-preview-screen.component.ts @@ -76,7 +76,7 @@ import { Roles } from '@users/roles'; import { SuggestionsService } from './services/suggestions.service'; import { IqserDialog } from '../../../../../../libs/common-ui/src/lib/dialog/iqser-dialog.service'; import { RedactTextDialogComponent } from './dialogs/redact-text-dialog/redact-text-dialog.component'; -import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; +import { DossierTemplatesService } from '@services/dossier-templates/dossier-templates.service'; const textActions = [TextPopups.ADD_DICTIONARY, TextPopups.ADD_FALSE_POSITIVE]; @@ -141,6 +141,7 @@ export class FilePreviewScreenComponent private readonly _readableRedactionsService: ReadableRedactionsService, private readonly _helpModeService: HelpModeService, private readonly _suggestionsService: SuggestionsService, + private readonly _dossierTemplatesService: DossierTemplatesService, private readonly _dialog: MatDialog, ) { super(); @@ -386,6 +387,13 @@ export class FilePreviewScreenComponent result.dictionary?.label, ); + if (result.applyToAllDossiers !== null) { + const dossierTemplate = this._dossierTemplatesService.find(this.state.dossierTemplateId); + const { ...body } = dossierTemplate; + body.applyDictionaryUpdatesToAllDossiersByDefault = result.applyToAllDossiers; + await this._dossierTemplatesService.createOrUpdate(body); + } + const addAndReload$ = add$.pipe(switchMap(() => this._filesService.reload(this.dossierId, file))); return firstValueFrom(addAndReload$.pipe(catchError(() => of(undefined)))); } diff --git a/apps/red-ui/src/app/modules/file-preview/services/annotation-actions.service.ts b/apps/red-ui/src/app/modules/file-preview/services/annotation-actions.service.ts index 0625c2ca0..17d76a25d 100644 --- a/apps/red-ui/src/app/modules/file-preview/services/annotation-actions.service.ts +++ b/apps/red-ui/src/app/modules/file-preview/services/annotation-actions.service.ts @@ -33,10 +33,11 @@ import { REDDocumentViewer } from '../../pdf-viewer/services/document-viewer.ser import { RemoveRedactionDialogComponent, RemoveRedactionPermissions, + RemoveRedactionResult, } from '../dialogs/remove-redaction-dialog/remove-redaction-dialog.component'; import { RemoveRedactionOptions } from '../dialogs/remove-redaction-dialog/remove-redaction-options'; import { IqserDialog } from '../../../../../../../libs/common-ui/src/lib/dialog/iqser-dialog.service'; -import { AnnotationPermissions } from '@models/file/annotation.permissions'; +import { DossierTemplatesService } from '@services/dossier-templates/dossier-templates.service'; @Injectable() export class AnnotationActionsService { @@ -52,6 +53,7 @@ export class AnnotationActionsService { private readonly _state: FilePreviewStateService, private readonly _fileDataService: FileDataService, private readonly _skippedService: SkippedService, + private readonly _dossierTemplatesService: DossierTemplatesService, ) {} acceptSuggestion(annotations: AnnotationWrapper[]) { @@ -144,7 +146,7 @@ export class AnnotationActionsService { canMarkAsFalsePositive: permissions.canMarkAsFalsePositive, }; - const result = await this._iqserDialog + const result: RemoveRedactionResult = await this._iqserDialog .openDefault(RemoveRedactionDialogComponent, { data: { redaction, @@ -163,6 +165,13 @@ export class AnnotationActionsService { this.#removeRedaction(redaction, result.comment, removeFromDictionary); } } + + if (result.option.extraOption) { + const dossierTemplate = this._dossierTemplatesService.find(this._state.dossierTemplateId); + const { ...body } = dossierTemplate; + body.applyDictionaryUpdatesToAllDossiersByDefault = result.applyToAllDossiers; + await this._dossierTemplatesService.createOrUpdate(body); + } } recategorizeImages(annotations: AnnotationWrapper[]) { diff --git a/apps/red-ui/src/app/translations/remove-redaction-translations.ts b/apps/red-ui/src/app/translations/remove-redaction-translations.ts index d90195846..93d939562 100644 --- a/apps/red-ui/src/app/translations/remove-redaction-translations.ts +++ b/apps/red-ui/src/app/translations/remove-redaction-translations.ts @@ -4,6 +4,7 @@ import { RemoveRedactionOption } from '../modules/file-preview/dialogs/remove-re interface Option { label: string; description: string; + extraOptionLabel?: string; } export const removeRedactionTranslations: { [key in RemoveRedactionOption]: Option } = { @@ -14,9 +15,11 @@ export const removeRedactionTranslations: { [key in RemoveRedactionOption]: Opti IN_DOSSIER: { label: _('remove-redaction.dialog.content.options.in-dossier.label'), description: _('remove-redaction.dialog.content.options.in-dossier.description'), + extraOptionLabel: _('remove-redaction.dialog.content.options.redact.in-dossier.extraOptionLabel'), }, FALSE_POSITIVE: { label: _('remove-redaction.dialog.content.options.false-positive.label'), description: _('remove-redaction.dialog.content.options.false-positive.description'), + extraOptionLabel: _('remove-redaction.dialog.content.options.redact.false-positive.extraOptionLabel'), }, }; diff --git a/apps/red-ui/src/assets/i18n/redact/de.json b/apps/red-ui/src/assets/i18n/redact/de.json index a6bddeaee..88d1fc086 100644 --- a/apps/red-ui/src/assets/i18n/redact/de.json +++ b/apps/red-ui/src/assets/i18n/redact/de.json @@ -43,6 +43,10 @@ "generic": "Fehler beim Erstellen der Dossiervorlage." }, "form": { + "apply-updates-default": { + "description": "", + "heading": "" + }, "description": "Beschreibung", "description-placeholder": "Beschreibung eingeben", "hidden-text": { @@ -61,11 +65,7 @@ "title": "" }, "valid-from": "Gültig ab", - "valid-to": "Gültig bis", - "apply-updates-default": { - "heading": "", - "description": "" - } + "valid-to": "Gültig bis" }, "save": "Dossier-Vorlage speichern", "title": "{type, select, edit{Dossier-Vorlage {name} bearbeiten} create{Dossier-Vorlage erstellen} clone{} other{}}" @@ -330,9 +330,6 @@ "recategorize-image": "neu kategorisieren", "reject-suggestion": "Vorschlag ablehnen", "remove-annotation": { - "false-positive": "Falsch positiv", - "only-here": "nur hier entfernen", - "remove-from-dict": "Aus dem Wörterbuch entfernen", "remove-redaction": "" }, "remove-highlights": { @@ -1945,7 +1942,6 @@ "type": "", "type-placeholder": "" }, - "error": "", "title": "" } }, @@ -1987,6 +1983,14 @@ "only-here": { "description": "", "label": "" + }, + "redact": { + "false-positive": { + "extraOptionLabel": "" + }, + "in-dossier": { + "extraOptionLabel": "" + } } } }, diff --git a/apps/red-ui/src/assets/i18n/redact/en.json b/apps/red-ui/src/assets/i18n/redact/en.json index 43c8c7528..567695e9d 100644 --- a/apps/red-ui/src/assets/i18n/redact/en.json +++ b/apps/red-ui/src/assets/i18n/redact/en.json @@ -43,6 +43,10 @@ "generic": "Failed to create dossier template." }, "form": { + "apply-updates-default": { + "description": "Apply dictionary updates to all dossiers by default", + "heading": "Entity configuration" + }, "description": "Description", "description-placeholder": "Enter Description", "hidden-text": { @@ -61,11 +65,7 @@ "title": "Keep overlapping elements" }, "valid-from": "Valid from", - "valid-to": "Valid to", - "apply-updates-default": { - "heading": "Entity configuration", - "description": "Apply dictionary updates to all dossiers by default" - } + "valid-to": "Valid to" }, "save": "Save Dossier Template", "title": "{type, select, edit{Edit {name}} create{Create} clone{Clone} other{}} Dossier Template" @@ -330,9 +330,6 @@ "recategorize-image": "Recategorize", "reject-suggestion": "Reject Suggestion", "remove-annotation": { - "false-positive": "False Positive", - "only-here": "Remove only here", - "remove-from-dict": "Remove from dictionary", "remove-redaction": "Remove" }, "remove-highlights": { @@ -1945,7 +1942,6 @@ "type": "Type", "type-placeholder": "Select type ..." }, - "error": "Error! Invalid page selection", "title": "Redact text" } }, @@ -1987,6 +1983,14 @@ "only-here": { "description": "Do not redact \"{value}\" at this position in the current document.", "label": "Remove here" + }, + "redact": { + "false-positive": { + "extraOptionLabel": "Apply to all dossiers" + }, + "in-dossier": { + "extraOptionLabel": "Apply to all dossiers" + } } } }, diff --git a/apps/red-ui/src/assets/i18n/scm/de.json b/apps/red-ui/src/assets/i18n/scm/de.json index 353c3901e..0fd3be53c 100644 --- a/apps/red-ui/src/assets/i18n/scm/de.json +++ b/apps/red-ui/src/assets/i18n/scm/de.json @@ -43,6 +43,10 @@ "generic": "Fehler beim Erstellen der Dossiervorlage." }, "form": { + "apply-updates-default": { + "description": "", + "heading": "" + }, "description": "Beschreibung", "description-placeholder": "Beschreibung eingeben", "hidden-text": { @@ -326,9 +330,6 @@ "recategorize-image": "neu kategorisieren", "reject-suggestion": "Vorschlag ablehnen", "remove-annotation": { - "false-positive": "Falsch positiv", - "only-here": "nur hier entfernen", - "remove-from-dict": "Aus dem Wörterbuch entfernen", "remove-redaction": "" }, "remove-highlights": { @@ -1941,7 +1942,6 @@ "type": "", "type-placeholder": "" }, - "error": "", "title": "" } }, @@ -1983,6 +1983,14 @@ "only-here": { "description": "", "label": "" + }, + "redact": { + "false-positive": { + "extraOptionLabel": "" + }, + "in-dossier": { + "extraOptionLabel": "" + } } } }, diff --git a/apps/red-ui/src/assets/i18n/scm/en.json b/apps/red-ui/src/assets/i18n/scm/en.json index 83c2c8246..6ed676a41 100644 --- a/apps/red-ui/src/assets/i18n/scm/en.json +++ b/apps/red-ui/src/assets/i18n/scm/en.json @@ -43,6 +43,10 @@ "generic": "Failed to create dossier template." }, "form": { + "apply-updates-default": { + "description": "", + "heading": "" + }, "description": "Description", "description-placeholder": "Enter Description", "hidden-text": { @@ -326,9 +330,6 @@ "recategorize-image": "Recategorize", "reject-suggestion": "Reject Suggestion", "remove-annotation": { - "false-positive": "False Positive", - "only-here": "Remove only here", - "remove-from-dict": "Remove from dictionary", "remove-redaction": "Remove" }, "remove-highlights": { @@ -1941,7 +1942,6 @@ "type": "Type", "type-placeholder": "Select type ..." }, - "error": "Error! Invalid page selection", "title": "Redact text" } }, @@ -1983,6 +1983,14 @@ "only-here": { "description": "Do not redact \"{value}\" at this position in the current document.", "label": "Remove here" + }, + "redact": { + "false-positive": { + "extraOptionLabel": "Apply to all dossiers" + }, + "in-dossier": { + "extraOptionLabel": "Apply to all dossiers" + } } } },