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/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/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..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,29 +150,38 @@ icon="red:visibility" > - + + + + + + + + + + + + + + + + + + + + + + + + + + - - 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..100707918 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 @@ -10,6 +10,7 @@ import { HelpModeService, IqserPermissionsService } from '@iqser/common-ui'; import { ViewModeService } from '../../services/view-mode.service'; import { REDAnnotationManager } from '../../../pdf-viewer/services/annotation-manager.service'; import { Roles } from '@users/roles'; +import { RemoveRedactionPermissions } from '../../dialogs/remove-redaction-dialog/remove-redaction-dialog.component'; export const AnnotationButtonTypes = { dark: 'dark', @@ -78,6 +79,10 @@ export class AnnotationActionsComponent implements OnChanges { this.annotationActionsService.removeOrSuggestRemoveAnnotation(this.annotations, removeFromDict); } + removeOrSuggestRemoveRedaction() { + this.annotationActionsService.removeOrSuggestRemoveRedaction(this.annotations[0], this.annotationPermissions); + } + 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 new file mode 100644 index 000000000..f0652bbb7 --- /dev/null +++ b/apps/red-ui/src/app/modules/file-preview/dialogs/redact-text-dialog/redact-text-dialog.component.html @@ -0,0 +1,91 @@ +
+
+
+ +
+
+ + {{ 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..0bc0b2eb8 --- /dev/null +++ b/apps/red-ui/src/app/modules/file-preview/dialogs/redact-text-dialog/redact-text-dialog.component.scss @@ -0,0 +1,8 @@ +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 new file mode 100644 index 000000000..d9ab15bdc --- /dev/null +++ b/apps/red-ui/src/app/modules/file-preview/dialogs/redact-text-dialog/redact-text-dialog.component.ts @@ -0,0 +1,225 @@ +import { Component, OnInit } from '@angular/core'; +import { DetailsRadioOption, IconButtonTypes, IqserPermissionsService } from '@iqser/common-ui'; +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'; +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 { + 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'; +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'; + +interface RedactTextData { + manualRedactionEntryWrapper: ManualRedactionEntryWrapper; + dossierId: string; + file: File; +} + +interface DialogResult { + redaction: IManualRedactionEntry; + dictionary: Dictionary; + applyToAllDossiers: boolean | null; +} + +@Component({ + templateUrl: './redact-text-dialog.component.html', + styleUrls: ['./redact-text-dialog.component.scss'], +}) +export class RedactTextDialogComponent + extends IqserDialogComponent + implements OnInit +{ + readonly roles = Roles; + readonly iconButtonTypes = IconButtonTypes; + readonly options: DetailsRadioOption[]; + readonly type: ManualRedactionEntryType; + dictionaryRequest = false; + legalOptions: LegalBasisOption[] = []; + dictionaries: Dictionary[] = []; + form!: UntypedFormGroup; + + #manualRedactionTypeExists = true; + #applyToAllDossiers = true; + + readonly #translations = redactTextTranslations; + readonly #dossier: Dossier; + readonly #hint: boolean; + + constructor( + private readonly _justificationsService: JustificationsService, + private readonly _activeDossiersService: ActiveDossiersService, + private readonly _dictionaryService: DictionaryService, + private readonly _iqserPermissionsService: IqserPermissionsService, + private readonly _dossierTemplatesService: DossierTemplatesService, + private readonly _formBuilder: FormBuilder, + ) { + super(); + 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.form + .get('option') + .valueChanges.pipe( + tap((option: DetailsRadioOption) => { + this.dictionaryRequest = option.value === RedactTextOptions.IN_DOSSIER; + this.#resetValues(); + }), + takeUntilDestroyed(), + ) + .subscribe(); + } + + 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 = this._dictionaryService.getPossibleDictionaries(this.#dossier.dossierTemplateId, this.#hint); + + 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.legalOptions.sort((a, b) => a.label.localeCompare(b.label)); + + this.#selectReason(); + 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, + }); + } + + #getForm(): UntypedFormGroup { + return this._formBuilder.group({ + selectedText: this.data?.manualRedactionEntryWrapper?.manualRedactionEntry?.value, + reason: [null], + comment: [null], + dictionary: [this.#manualRedactionTypeExists ? SuperTypes.ManualRedaction : null], + classification: ['non-readable content'], + section: [null], + option: [this.options[0]], + }); + } + + #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() { + 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 }, + icon: FOLDER_ICON, + value: RedactTextOptions.IN_DOSSIER, + 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); + return; + } + this.form.get('dictionary').setValue(this.#manualRedactionTypeExists ? SuperTypes.ManualRedaction : null); + } +} 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/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..1ffd08512 --- /dev/null +++ b/apps/red-ui/src/app/modules/file-preview/dialogs/remove-redaction-dialog/remove-redaction-dialog.component.ts @@ -0,0 +1,123 @@ +import { Component } from '@angular/core'; +import { DetailsRadioOption, IconButtonTypes } from '@iqser/common-ui'; +import { RemoveRedactionOption, RemoveRedactionOptions } from './remove-redaction-options'; +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'; +import { tap } from 'rxjs/operators'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; + +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 { + comment: string; + option: DetailsRadioOption; + applyToAllDossiers: boolean | null; +} + +@Component({ + templateUrl: './remove-redaction-dialog.html', + styleUrls: ['./remove-redaction-dialog.component.scss'], +}) +export class RemoveRedactionDialogComponent extends IqserDialogComponent< + RemoveRedactionDialogComponent, + RemoveRedactionData, + RemoveRedactionResult +> { + readonly iconButtonTypes = IconButtonTypes; + readonly options: DetailsRadioOption[]; + + form!: UntypedFormGroup; + + readonly #redaction: AnnotationWrapper; + readonly #permissions: RemoveRedactionPermissions; + readonly #translations = removeRedactionTranslations; + + 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.form + .get('option') + .valueChanges.pipe( + tap(() => { + this.options[1].extraOption.checked = true; + this.options[2].extraOption.checked = true; + }), + takeUntilDestroyed(), + ) + .subscribe(); + } + + save(): void { + this.dialogRef.close(this.form.getRawValue()); + } + + #getForm(): UntypedFormGroup { + return this._formBuilder.group({ + comment: [null], + option: [this.options[0]], + }); + } + + #options() { + 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, + extraOption: { + label: this.#translations.IN_DOSSIER.extraOptionLabel, + checked: true, + }, + }); + } + 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, 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/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..5cdedd715 --- /dev/null +++ b/apps/red-ui/src/app/modules/file-preview/dialogs/remove-redaction-dialog/remove-redaction-dialog.html @@ -0,0 +1,34 @@ +
+
+
+ +
+ + +
+ + +
+
+ +
+ + + +
+
+
+ + +
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-screen.component.ts b/apps/red-ui/src/app/modules/file-preview/file-preview-screen.component.ts index 1c42250e2..7c0e5b32e 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 @@ -39,7 +39,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'; @@ -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 { DossierTemplatesService } from '@services/dossier-templates/dossier-templates.service'; 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, @@ -137,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(); @@ -366,6 +371,34 @@ export class FilePreviewScreenComponent ); } + async openRedactTextDialog(manualRedactionEntryWrapper: ManualRedactionEntryWrapper) { + const file = this.state.file(); + const result = await this._iqserDialog + .openDefault(RedactTextDialogComponent, { + data: { manualRedactionEntryWrapper, dossierId: this.dossierId, file }, + }) + .result(); + + if (result) { + const add$ = this._manualRedactionService.addAnnotation( + [result.redaction], + this.dossierId, + this.fileId, + 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)))); + } + } + toggleFullScreen() { this.fullScreen = !this.fullScreen; if (this.fullScreen) { @@ -623,6 +656,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..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 @@ -65,6 +65,8 @@ 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'; +import { RemoveRedactionDialogComponent } from './dialogs/remove-redaction-dialog/remove-redaction-dialog.component'; const routes: IqserRoutes = [ { @@ -89,6 +91,8 @@ const dialogs = [ ImportRedactionsDialogComponent, 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..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 @@ -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,6 +30,14 @@ 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 { + 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 { DossierTemplatesService } from '@services/dossier-templates/dossier-templates.service'; @Injectable() export class AnnotationActionsService { @@ -37,6 +45,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, @@ -44,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[]) { @@ -129,6 +139,41 @@ export class AnnotationActionsService { }); } + async removeOrSuggestRemoveRedaction(redaction: AnnotationWrapper, permissions) { + const removePermissions: RemoveRedactionPermissions = { + canRemoveOrSuggestToRemoveOnlyHere: permissions.canRemoveOrSuggestToRemoveOnlyHere, + canRemoveOrSuggestToRemoveFromDictionary: permissions.canRemoveOrSuggestToRemoveFromDictionary, + canMarkAsFalsePositive: permissions.canMarkAsFalsePositive, + }; + + const result: RemoveRedactionResult = 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); + } + } + + 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[]) { const data = { annotations, dossier: this._state.dossier() }; const { dossierId, fileId } = this._state; @@ -375,4 +420,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(); + } } 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..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,13 +120,20 @@ 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), + // if (permissions.canRemoveOrSuggestToRemoveOnlyHere) { + // const removeOrSuggestToRemoveOnlyHereButton = this.#getButton( + // 'trash', + // _('annotation-actions.remove-annotation.only-here'), + // () => this.#annotationActionsService.removeOrSuggestRemoveAnnotation(annotations, false), + // ); + // availableActions.push(removeOrSuggestToRemoveOnlyHereButton); + // } + + if (permissions.canRemoveOrSuggestToRemoveRedaction) { + const removeOrSuggestToRemoveButton = this.#getButton('trash', _('annotation-actions.remove-annotation.remove-redaction'), () => + this.#annotationActionsService.removeOrSuggestRemoveRedaction(annotations[0], permissions), ); - availableActions.push(removeOrSuggestToRemoveOnlyHereButton); + availableActions.push(removeOrSuggestToRemoveButton); } return availableActions; @@ -164,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 9ebd6f7ac..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 @@ -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, @@ -159,21 +161,34 @@ export class PdfProxyService { if (this._iqserPermissionsService.has(Roles.redactions.write) || this._iqserPermissionsService.has(Roles.redactions.request)) { popups.push({ type: 'actionButton', - dataElement: TextPopups.ADD_REDACTION, + dataElement: TextPopups.REDACT_TEXT, img: this.#addRedactionIcon, - title: this.#getTitle(ManualRedactionEntryTypes.REDACTION), - onClick: () => this._ngZone.run(() => this._addManualRedactionOfType(ManualRedactionEntryTypes.REDACTION)), + 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)), }); - 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)), + // }); + // } } } @@ -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()); 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/services/entity-services/dictionary.service.ts b/apps/red-ui/src/app/services/entity-services/dictionary.service.ts index adffdd6d5..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 @@ -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.hint) { + 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 new file mode 100644 index 000000000..966406528 --- /dev/null +++ b/apps/red-ui/src/app/translations/redact-text-translations.ts @@ -0,0 +1,32 @@ +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>> = { + 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'), + extraOptionLabel: _('redact-text.dialog.content.options.redact.in-dossier.extraOptionLabel'), + }, + }, + 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'), + extraOptionLabel: _('redact-text.dialog.content.options.hint.in-dossier.extraOptionLabel'), + }, + }, +} as const; 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..93d939562 --- /dev/null +++ b/apps/red-ui/src/app/translations/remove-redaction-translations.ts @@ -0,0 +1,25 @@ +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; + extraOptionLabel?: 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'), + 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 25b9a803e..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,7 @@ "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": { "label": "" @@ -1904,6 +1902,49 @@ }, "header": "Bildtypen bearbeiten" }, + "redact-text": { + "dialog": { + "actions": { + "cancel": "", + "save": "" + }, + "content": { + "comment": "", + "comment-placeholder": "", + "legal-basis": "", + "options": { + "hint": { + "in-dossier": { + "description": "", + "extraOptionLabel": "", + "label": "" + }, + "only-here": { + "description": "", + "label": "" + } + }, + "redact": { + "in-dossier": { + "description": "", + "extraOptionLabel": "", + "label": "" + }, + "only-here": { + "description": "", + "label": "" + } + } + }, + "reason": "", + "reason-placeholder": "", + "selected-text": "", + "type": "", + "type-placeholder": "" + }, + "title": "" + } + }, "redaction-abbreviation": "R", "references": "", "remove-annotations-dialog": { @@ -1921,6 +1962,41 @@ }, "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": "" + }, + "redact": { + "false-positive": { + "extraOptionLabel": "" + }, + "in-dossier": { + "extraOptionLabel": "" + } + } + } + }, + "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 4bb20344d..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,7 @@ "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": { "label": "Remove Selected Earmarks" @@ -1904,6 +1902,49 @@ }, "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 {dossierName}.", + "extraOptionLabel": "Apply to all dossiers", + "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 {dossierName}.", + "extraOptionLabel": "Apply to all dossiers", + "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": { @@ -1921,6 +1962,41 @@ }, "value": "Value" }, + "remove-redaction": { + "dialog": { + "actions": { + "cancel": "Cancel", + "save": "Save" + }, + "content": { + "comment": "Comment", + "comment-placeholder": "Add remarks or mentions ...", + "options": { + "false-positive": { + "description": "\"{value}\" is not a {type} in this context: \"{context}\".", + "label": "False positive" + }, + "in-dossier": { + "description": "Do not redact \"{value}\" in any document of the current dossier.", + "label": "Remove in dossier" + }, + "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" + } + } + } + }, + "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 9de02d917..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,7 @@ "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": { "label": "" @@ -1900,6 +1902,49 @@ }, "header": "Bildtypen bearbeiten" }, + "redact-text": { + "dialog": { + "actions": { + "cancel": "", + "save": "" + }, + "content": { + "comment": "", + "comment-placeholder": "", + "legal-basis": "", + "options": { + "hint": { + "in-dossier": { + "description": "", + "extraOptionLabel": "", + "label": "" + }, + "only-here": { + "description": "", + "label": "" + } + }, + "redact": { + "in-dossier": { + "description": "", + "extraOptionLabel": "", + "label": "" + }, + "only-here": { + "description": "", + "label": "" + } + } + }, + "reason": "", + "reason-placeholder": "", + "selected-text": "", + "type": "", + "type-placeholder": "" + }, + "title": "" + } + }, "redaction-abbreviation": "C", "references": "", "remove-annotations-dialog": { @@ -1917,6 +1962,41 @@ }, "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": "" + }, + "redact": { + "false-positive": { + "extraOptionLabel": "" + }, + "in-dossier": { + "extraOptionLabel": "" + } + } + } + }, + "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 9f266d4fc..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,7 @@ "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": { "label": "Remove Selected Earmarks" @@ -1900,6 +1902,49 @@ }, "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 {dossierName}.", + "extraOptionLabel": "Apply to all dossiers", + "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 {dossierName}.", + "extraOptionLabel": "Apply to all dossiers", + "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": { @@ -1917,6 +1962,41 @@ }, "value": "Value" }, + "remove-redaction": { + "dialog": { + "actions": { + "cancel": "Cancel", + "save": "Save" + }, + "content": { + "comment": "Comment", + "comment-placeholder": "Add remarks or mentions ...", + "options": { + "false-positive": { + "description": "\"{value}\" is not a {type} in this context: \"{context}\".", + "label": "False positive" + }, + "in-dossier": { + "description": "Do not redact \"{value}\" in any document of the current dossier.", + "label": "Remove in dossier" + }, + "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" + } + } + } + }, + "title": "Remove redaction" + } + }, "report-type": { "label": "{length} report {length, plural, one{type} other{types}}" }, 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 @@ + + + diff --git a/libs/common-ui b/libs/common-ui index 2fe65233b..6b45d4aa7 160000 --- a/libs/common-ui +++ b/libs/common-ui @@ -1 +1 @@ -Subproject commit 2fe65233bc4186ba67c924efae4d82cfb8b89fe7 +Subproject commit 6b45d4aa70554b5f41e59d689094d6dad8291c8e