diff --git a/apps/red-ui/src/app/app.module.ts b/apps/red-ui/src/app/app.module.ts index dbe3ba001..2cbdb6539 100644 --- a/apps/red-ui/src/app/app.module.ts +++ b/apps/red-ui/src/app/app.module.ts @@ -47,7 +47,6 @@ import { LogoComponent } from './logo/logo.component'; import { CompositeRouteGuard } from './utils/composite-route.guard'; import { AppStateGuard } from './state/app-state.guard'; import { SimpleDoughnutChartComponent } from './components/simple-doughnut-chart/simple-doughnut-chart.component'; -import { ManualRedactionDialogComponent } from './dialogs/manual-redaction-dialog/manual-redaction-dialog.component'; import { MatCheckboxModule } from '@angular/material/checkbox'; import { MatSlideToggleModule } from '@angular/material/slide-toggle'; import { AnnotationIconComponent } from './components/annotation-icon/annotation-icon.component'; @@ -61,6 +60,7 @@ import { MatNativeDateModule } from '@angular/material/core'; import { MatInputModule } from '@angular/material/input'; import { ProjectMemberGuard } from './auth/project-member-guard.service'; import { HumanizePipe } from './utils/humanize.pipe'; +import { ManualAnnotationDialogComponent } from './dialogs/manual-redaction-dialog/manual-annotation-dialog.component'; export function HttpLoaderFactory(httpClient: HttpClient) { return new TranslateHttpLoader(httpClient, '/assets/i18n/', '.json'); @@ -83,7 +83,7 @@ export function HttpLoaderFactory(httpClient: HttpClient) { StatusBarComponent, LogoComponent, SimpleDoughnutChartComponent, - ManualRedactionDialogComponent, + ManualAnnotationDialogComponent, AnnotationIconComponent, AuthErrorComponent, HumanizePipe diff --git a/apps/red-ui/src/app/dialogs/dialog.service.ts b/apps/red-ui/src/app/dialogs/dialog.service.ts index 6a3c0fb58..23e535b49 100644 --- a/apps/red-ui/src/app/dialogs/dialog.service.ts +++ b/apps/red-ui/src/app/dialogs/dialog.service.ts @@ -5,7 +5,6 @@ import { FileStatus, FileUploadControllerService, ManualRedactionControllerService, - ManualRedactionEntry, Project } from '@redaction/red-ui-http'; import { ConfirmationDialogComponent } from '../common/confirmation-dialog/confirmation-dialog.component'; @@ -14,10 +13,10 @@ import { TranslateService } from '@ngx-translate/core'; import { AppStateService } from '../state/app-state.service'; import { AddEditProjectDialogComponent } from './add-edit-project-dialog/add-edit-project-dialog.component'; import { AssignOwnerDialogComponent } from './assign-owner-dialog/assign-owner-dialog.component'; -import { ManualRedactionDialogComponent } from './manual-redaction-dialog/manual-redaction-dialog.component'; -import { Annotations } from '@pdftron/webviewer'; import { ManualRedactionEntryWrapper } from '../screens/file/model/manual-redaction-entry.wrapper'; import { AnnotationWrapper } from '../screens/file/model/annotation.wrapper'; +import { ManualAnnotationDialogComponent } from './manual-redaction-dialog/manual-annotation-dialog.component'; +import { ManualAnnotationService } from '../screens/file/service/manual-annotation.service'; const dialogConfig = { width: '600px', @@ -35,6 +34,7 @@ export class DialogService { private readonly _appStateService: AppStateService, private readonly _fileUploadControllerService: FileUploadControllerService, private readonly _notificationService: NotificationService, + private readonly _manualAnnotationService: ManualAnnotationService, private readonly _manualRedactionControllerService: ManualRedactionControllerService ) {} @@ -83,8 +83,8 @@ export class DialogService { public openManualRedactionDialog( $event: ManualRedactionEntryWrapper, cb?: Function - ): MatDialogRef { - const ref = this._dialog.open(ManualRedactionDialogComponent, { + ): MatDialogRef { + const ref = this._dialog.open(ManualAnnotationDialogComponent, { ...dialogConfig, autoFocus: true, data: $event @@ -99,51 +99,25 @@ export class DialogService { return ref; } - public acceptSuggestionAnnotation( + public acceptSuggestion( $event: MouseEvent, - annotation: AnnotationWrapper, - projectId: string, - fileId: string + annotation: AnnotationWrapper ): MatDialogRef { $event.stopPropagation(); const ref = this._dialog.open(ConfirmationDialogComponent, dialogConfig); ref.afterClosed().subscribe((result) => { if (result) { - this._manualRedactionControllerService - .approveRequest(projectId, fileId, annotation.uuid) - .subscribe( - () => { - this._notificationService.showToastNotification( - this._translateService.instant( - 'manual-redaction.confirm-annotation.success.label' - ), - null, - NotificationType.SUCCESS - ); - }, - (err) => { - this._notificationService.showToastNotification( - this._translateService.instant( - 'manual-redaction.confirm-annotation.failed.label', - err - ), - null, - NotificationType.ERROR - ); - } - ); + this._manualAnnotationService.acceptSuggestion(annotation).subscribe(() => {}); } }); return ref; } - public suggestRemoveAnnotation( + public rejectSuggestion( $event: MouseEvent, - annotation: AnnotationWrapper, - projectId: string, - fileId: string + annotation: AnnotationWrapper ): MatDialogRef { $event.stopPropagation(); @@ -154,29 +128,7 @@ export class DialogService { ref.afterClosed().subscribe((result) => { if (result) { - this._manualRedactionControllerService - .undo(projectId, fileId, annotation.uuid) - .subscribe( - (ok) => { - this._notificationService.showToastNotification( - this._translateService.instant( - 'manual-redaction.remove-annotation.success.label' - ), - null, - NotificationType.SUCCESS - ); - }, - (err) => { - this._notificationService.showToastNotification( - this._translateService.instant( - 'manual-redaction.remove-annotation.failed.label', - err - ), - null, - NotificationType.ERROR - ); - } - ); + this._manualAnnotationService.rejectSuggestion(annotation).subscribe(() => {}); } }); diff --git a/apps/red-ui/src/app/dialogs/manual-redaction-dialog/manual-redaction-dialog.component.html b/apps/red-ui/src/app/dialogs/manual-redaction-dialog/manual-annotation-dialog.component.html similarity index 87% rename from apps/red-ui/src/app/dialogs/manual-redaction-dialog/manual-redaction-dialog.component.html rename to apps/red-ui/src/app/dialogs/manual-redaction-dialog/manual-annotation-dialog.component.html index e0553bffd..2f7574f2d 100644 --- a/apps/red-ui/src/app/dialogs/manual-redaction-dialog/manual-redaction-dialog.component.html +++ b/apps/red-ui/src/app/dialogs/manual-redaction-dialog/manual-annotation-dialog.component.html @@ -8,7 +8,10 @@ {{ manualRedactionEntryWrapper.manualRedactionEntry.value }} -
+
@@ -29,15 +32,6 @@
- -
- {{ - 'manual-redaction.dialog.content.dictionary.add.label' | translate - }} -
diff --git a/apps/red-ui/src/app/dialogs/manual-redaction-dialog/manual-redaction-dialog.component.scss b/apps/red-ui/src/app/dialogs/manual-redaction-dialog/manual-annotation-dialog.component.scss similarity index 100% rename from apps/red-ui/src/app/dialogs/manual-redaction-dialog/manual-redaction-dialog.component.scss rename to apps/red-ui/src/app/dialogs/manual-redaction-dialog/manual-annotation-dialog.component.scss diff --git a/apps/red-ui/src/app/dialogs/manual-redaction-dialog/manual-annotation-dialog.component.ts b/apps/red-ui/src/app/dialogs/manual-redaction-dialog/manual-annotation-dialog.component.ts new file mode 100644 index 000000000..6c40fb94a --- /dev/null +++ b/apps/red-ui/src/app/dialogs/manual-redaction-dialog/manual-annotation-dialog.component.ts @@ -0,0 +1,102 @@ +import { Component, Inject, OnInit } from '@angular/core'; +import { FormBuilder, FormGroup, Validators } from '@angular/forms'; +import { AppStateService } from '../../state/app-state.service'; +import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; +import { AddRedactionRequest, TypeValue } from '@redaction/red-ui-http'; +import { NotificationService } from '../../notification/notification.service'; +import { TranslateService } from '@ngx-translate/core'; +import { UserService } from '../../user/user.service'; +import { ManualRedactionEntryWrapper } from '../../screens/file/model/manual-redaction-entry.wrapper'; +import { ManualAnnotationService } from '../../screens/file/service/manual-annotation.service'; + +@Component({ + selector: 'redaction-manual-annotation-dialog', + templateUrl: './manual-annotation-dialog.component.html', + styleUrls: ['./manual-annotation-dialog.component.scss'] +}) +export class ManualAnnotationDialogComponent implements OnInit { + dictionaryOptions: TypeValue[] = []; + redactionForm: FormGroup; + isDocumentAdmin: boolean; + + constructor( + private readonly _appStateService: AppStateService, + private readonly _userService: UserService, + private readonly _formBuilder: FormBuilder, + private readonly _notificationService: NotificationService, + private readonly _translateService: TranslateService, + private readonly _manualAnnotationService: ManualAnnotationService, + public dialogRef: MatDialogRef, + @Inject(MAT_DIALOG_DATA) public manualRedactionEntryWrapper: ManualRedactionEntryWrapper + ) {} + + async ngOnInit() { + this.isDocumentAdmin = this._appStateService.isActiveProjectOwner; + const commentField = this.isDocumentAdmin ? [null] : [null, Validators.required]; + this.redactionForm = this._formBuilder.group({ + reason: + this.manualRedactionEntryWrapper.type === 'DICTIONARY' + ? null + : [null, Validators.required], + dictionary: [null, Validators.required], + comment: commentField + }); + + for (const key of Object.keys(this._appStateService.dictionaryData)) { + const dictionaryData = this._appStateService.dictionaryData[key]; + if (!dictionaryData.virtual) { + if (this.manualRedactionEntryWrapper.type === 'DICTIONARY') { + this.dictionaryOptions.push(dictionaryData); + } + if (!dictionaryData.hint && this.manualRedactionEntryWrapper.type === 'REDACTION') { + this.dictionaryOptions.push(dictionaryData); + } + } + } + } + + handleAddRedaction() { + this._enhanceManualRedaction(this.manualRedactionEntryWrapper.manualRedactionEntry); + + if (this.manualRedactionEntryWrapper.type === 'DICTIONARY') { + this._manualAnnotationService + .makeDictionaryEntry(this.manualRedactionEntryWrapper.manualRedactionEntry) + .subscribe( + (response) => { + this.dialogRef.close(this.manualRedactionEntryWrapper); + }, + () => { + this.dialogRef.close(); + } + ); + } else { + this._manualAnnotationService + .makeRedaction(this.manualRedactionEntryWrapper.manualRedactionEntry) + .subscribe( + (response) => { + this.dialogRef.close(this.manualRedactionEntryWrapper); + }, + () => { + this.dialogRef.close(); + } + ); + } + } + + get title() { + return this._manualAnnotationService.getTitle(this.manualRedactionEntryWrapper.type); + } + + private _enhanceManualRedaction(addRedactionRequest: AddRedactionRequest) { + addRedactionRequest.type = this.redactionForm.get('dictionary').value; + addRedactionRequest.addToDictionary = + this.manualRedactionEntryWrapper.type === 'DICTIONARY'; + addRedactionRequest.reason = this.redactionForm.get('reason').value; + // todo fix this in backend + if (!addRedactionRequest.reason) { + addRedactionRequest.reason = 'ADD_TO_DICTIONARY_REQUEST'; + } + const commentValue = this.redactionForm.get('comment').value; + addRedactionRequest.comment = commentValue ? { text: commentValue } : null; + } +} diff --git a/apps/red-ui/src/app/dialogs/manual-redaction-dialog/manual-redaction-dialog.component.ts b/apps/red-ui/src/app/dialogs/manual-redaction-dialog/manual-redaction-dialog.component.ts deleted file mode 100644 index 7542b5d23..000000000 --- a/apps/red-ui/src/app/dialogs/manual-redaction-dialog/manual-redaction-dialog.component.ts +++ /dev/null @@ -1,161 +0,0 @@ -import { Component, Inject, OnInit } from '@angular/core'; -import { FormBuilder, FormGroup, Validators } from '@angular/forms'; -import { AppStateService } from '../../state/app-state.service'; -import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; -import { - AddRedactionRequest, - DictionaryControllerService, - ManualRedactionControllerService, - TypeValue -} from '@redaction/red-ui-http'; -import { NotificationService, NotificationType } from '../../notification/notification.service'; -import { TranslateService } from '@ngx-translate/core'; -import { UserService } from '../../user/user.service'; -import { ManualRedactionEntryWrapper } from '../../screens/file/model/manual-redaction-entry.wrapper'; - -@Component({ - selector: 'redaction-manual-redaction-dialog', - templateUrl: './manual-redaction-dialog.component.html', - styleUrls: ['./manual-redaction-dialog.component.scss'] -}) -export class ManualRedactionDialogComponent implements OnInit { - dictionaryOptions: TypeValue[] = []; - redactionForm: FormGroup; - isDocumentAdmin: boolean; - - constructor( - private readonly _appStateService: AppStateService, - private readonly _userService: UserService, - private readonly _formBuilder: FormBuilder, - private readonly _notificationService: NotificationService, - private readonly _translateService: TranslateService, - private readonly _manualRedactionControllerService: ManualRedactionControllerService, - private readonly _dictionaryControllerService: DictionaryControllerService, - public dialogRef: MatDialogRef, - @Inject(MAT_DIALOG_DATA) public manualRedactionEntryWrapper: ManualRedactionEntryWrapper - ) {} - - async ngOnInit() { - this.isDocumentAdmin = this._appStateService.isActiveProjectOwner; - const commentField = this.isDocumentAdmin ? [null] : [null, Validators.required]; - this.redactionForm = this._formBuilder.group({ - addToDictionary: [false], - reason: [null, Validators.required], - dictionary: [null, Validators.required], - comment: commentField - }); - - for (const key of Object.keys(this._appStateService.dictionaryData)) { - const dictionaryData = this._appStateService.dictionaryData[key]; - if (!dictionaryData.virtual) { - if (dictionaryData.hint && this.manualRedactionEntryWrapper.type === 'HINT') { - this.dictionaryOptions.push(dictionaryData); - } - if (!dictionaryData.hint && this.manualRedactionEntryWrapper.type === 'REDACTION') { - this.dictionaryOptions.push(dictionaryData); - } - } - } - } - - handleAddRedaction() { - if (this.isDocumentAdmin) { - this.addManualRedaction(); - } else { - this.suggestManualRedaction(); - } - } - - suggestManualRedaction() { - const mre = Object.assign({}, this.manualRedactionEntryWrapper.manualRedactionEntry); - this._enhanceManualRedaction(mre); - this._manualRedactionControllerService - .requestAddRedaction( - mre, - this._appStateService.activeProject.project.projectId, - this._appStateService.activeFile.fileId - ) - .subscribe( - (ok) => { - this._notificationService.showToastNotification( - this._translateService.instant( - 'manual-redaction.dialog.add-redaction.success.label' - ), - null, - NotificationType.SUCCESS - ); - this.dialogRef.close({ mode: 'suggestion', request: mre }); - }, - (err) => { - this._notificationService.showToastNotification( - this._translateService.instant( - 'manual-redaction.dialog.add-redaction.failed.label', - err - ), - null, - NotificationType.ERROR - ); - } - ); - } - - addManualRedaction() { - const mre = Object.assign({}, this.manualRedactionEntryWrapper.manualRedactionEntry); - this._enhanceManualRedaction(mre); - this._manualRedactionControllerService - .addRedaction( - mre, - this._appStateService.activeProject.project.projectId, - this._appStateService.activeFile.fileId - ) - .subscribe( - (ok) => { - this._notificationService.showToastNotification( - this._translateService.instant( - 'manual-redaction.dialog.add-redaction.success.label' - ), - null, - NotificationType.SUCCESS - ); - this.dialogRef.close({ mode: 'redaction', request: mre }); - }, - (err) => { - this._notificationService.showToastNotification( - this._translateService.instant( - 'manual-redaction.dialog.add-redaction.failed.label', - err - ), - null, - NotificationType.ERROR - ); - } - ); - } - - get title() { - if (this.isDocumentAdmin) { - if (this.manualRedactionEntryWrapper.type === 'HINT') { - return 'manual-redaction.dialog.header.hint.label'; - } else { - return 'manual-redaction.dialog.header.redaction.label'; - } - } else { - if (this.manualRedactionEntryWrapper.type === 'HINT') { - return 'manual-redaction.dialog.header.request-hint.label'; - } else { - return 'manual-redaction.dialog.header.request-redaction.label'; - } - } - } - - private _enhanceManualRedaction(addRedactionRequest: AddRedactionRequest) { - addRedactionRequest.type = this.redactionForm.get('dictionary').value; - addRedactionRequest.addToDictionary = - this.manualRedactionEntryWrapper.type === 'HINT' - ? true - : this.redactionForm.get('addToDictionary').value; - addRedactionRequest.reason = this.redactionForm.get('reason').value; - const commentValue = this.redactionForm.get('comment').value; - addRedactionRequest.comment = commentValue ? { text: commentValue } : null; - } -} diff --git a/apps/red-ui/src/app/screens/file/file-preview-screen/file-preview-screen.component.html b/apps/red-ui/src/app/screens/file/file-preview-screen/file-preview-screen.component.html index 3b488d3b3..36cadd258 100644 --- a/apps/red-ui/src/app/screens/file/file-preview-screen/file-preview-screen.component.html +++ b/apps/red-ui/src/app/screens/file/file-preview-screen/file-preview-screen.component.html @@ -267,14 +267,13 @@ > diff --git a/apps/red-ui/src/app/screens/file/file-preview-screen/file-preview-screen.component.ts b/apps/red-ui/src/app/screens/file/file-preview-screen/file-preview-screen.component.ts index f8b280ca9..a22e09cfa 100644 --- a/apps/red-ui/src/app/screens/file/file-preview-screen/file-preview-screen.component.ts +++ b/apps/red-ui/src/app/screens/file/file-preview-screen/file-preview-screen.component.ts @@ -26,6 +26,7 @@ import { MatDialogRef, MatDialogState } from '@angular/material/dialog'; import { ManualRedactionEntryWrapper } from '../model/manual-redaction-entry.wrapper'; import { hexToRgb } from '../../../utils/functions'; import { AnnotationWrapper } from '../model/annotation.wrapper'; +import { ManualAnnotationService } from '../service/manual-annotation.service'; @Component({ selector: 'redaction-file-preview-screen', @@ -60,6 +61,7 @@ export class FilePreviewScreenComponent implements OnInit { private readonly _activatedRoute: ActivatedRoute, private readonly _dialogService: DialogService, private readonly _router: Router, + private readonly _manualAnnotationService: ManualAnnotationService, private readonly _fileDownloadService: FileDownloadService, private readonly _reanalysisControllerService: ReanalysisControllerService, private readonly _filtersService: FiltersService, @@ -193,11 +195,12 @@ export class FilePreviewScreenComponent implements OnInit { this.ngZone.run(() => { this._dialogRef = this._dialogService.openManualRedactionDialog( $event, - (response: any) => { - const manualRedactionEntry: ManualRedactionEntry = response.request; + (response: ManualRedactionEntryWrapper) => { + const manualRedactionEntry: ManualRedactionEntry = + response.manualRedactionEntry; const annotManager = this.activeViewer.annotManager; - const originalQuads = manualRedactionEntry.quads; + const originalQuads = $event.quads; for (const key of Object.keys(originalQuads)) { const pageNumber = parseInt(key, 10); const highlight = new this.activeViewer.Annotations.TextHighlightAnnotation(); @@ -205,7 +208,7 @@ export class FilePreviewScreenComponent implements OnInit { highlight.StrokeColor = this._getColor(manualRedactionEntry); highlight.setContents(manualRedactionEntry.reason); highlight.Quads = originalQuads[key]; - highlight.Id = this._computeId($event.type, manualRedactionEntry); + highlight.Id = this._computeId(response.type, manualRedactionEntry); annotManager.addAnnotation(highlight, true); annotManager.redrawAnnotation(highlight); } @@ -259,25 +262,15 @@ export class FilePreviewScreenComponent implements OnInit { } } - public acceptSuggestionAnnotation($event: MouseEvent, annotation: AnnotationWrapper) { + public acceptSuggestion($event: MouseEvent, annotation: AnnotationWrapper) { this.ngZone.run(() => { - this._dialogRef = this._dialogService.acceptSuggestionAnnotation( - $event, - annotation, - this.projectId, - this.fileId - ); + this._dialogRef = this._dialogService.acceptSuggestion($event, annotation); }); } - public suggestRemoveAnnotation($event: MouseEvent, annotation: AnnotationWrapper) { + public rejectSuggestion($event: MouseEvent, annotation: AnnotationWrapper) { this.ngZone.run(() => { - this._dialogRef = this._dialogService.suggestRemoveAnnotation( - $event, - annotation, - this.projectId, - this.fileId - ); + this._dialogRef = this._dialogService.rejectSuggestion($event, annotation); }); } @@ -477,9 +470,21 @@ export class FilePreviewScreenComponent implements OnInit { this._changeDetectorRef.detectChanges(); } - private _computeId(type: 'HINT' | 'REDACTION', request: ManualRedactionEntry) { - const prefix = this.appStateService.isActiveProjectOwner ? type.toLowerCase() : 'request'; - return prefix + ':' + request.type + ':' + new Date().getTime(); + private _computeId(type: 'DICTIONARY' | 'REDACTION', request: ManualRedactionEntry) { + // if owner or not set the request prefix in the id + const prefix = this.appStateService.isActiveProjectOwner ? '' : 'request:add:'; + + if (prefix.length > 0) { + return prefix + request.type + ':' + new Date().getTime(); + } else { + const dictionaryType: 'hint' | 'redaction' = + type === 'REDACTION' + ? 'redaction' + : this.appStateService.getDictionaryTypeValue(request.type).hint + ? 'hint' + : 'redaction'; + return dictionaryType + ':' + request.type + ':' + new Date().getTime(); + } } private _getColor(manualRedactionEntry: ManualRedactionEntry) { diff --git a/apps/red-ui/src/app/screens/file/model/manual-redaction-entry.wrapper.ts b/apps/red-ui/src/app/screens/file/model/manual-redaction-entry.wrapper.ts index 5444940b0..cf748f4cb 100644 --- a/apps/red-ui/src/app/screens/file/model/manual-redaction-entry.wrapper.ts +++ b/apps/red-ui/src/app/screens/file/model/manual-redaction-entry.wrapper.ts @@ -1,10 +1,9 @@ import { ManualRedactionEntry } from '@redaction/red-ui-http'; export class ManualRedactionEntryWrapper { - public mode: 'REQUEST' | 'ACTUAL'; - constructor( + public readonly quads: any, public readonly manualRedactionEntry: ManualRedactionEntry, - public readonly type: 'HINT' | 'REDACTION' + public readonly type: 'DICTIONARY' | 'REDACTION' ) {} } diff --git a/apps/red-ui/src/app/screens/file/pdf-viewer/pdf-viewer.component.ts b/apps/red-ui/src/app/screens/file/pdf-viewer/pdf-viewer.component.ts index b9d56c40f..ba38c9247 100644 --- a/apps/red-ui/src/app/screens/file/pdf-viewer/pdf-viewer.component.ts +++ b/apps/red-ui/src/app/screens/file/pdf-viewer/pdf-viewer.component.ts @@ -12,7 +12,12 @@ import { ViewChild } from '@angular/core'; import { AppConfigKey, AppConfigService } from '../../../app-config/app-config.service'; -import { FileStatus, ManualRedactionEntry, Rectangle } from '@redaction/red-ui-http'; +import { + FileStatus, + ManualRedactionEntry, + ManualRedactions, + Rectangle +} from '@redaction/red-ui-http'; import WebViewer, { Annotations, WebViewerInstance } from '@pdftron/webviewer'; import { TranslateService } from '@ngx-translate/core'; import { FileDownloadService } from '../service/file-download.service'; @@ -22,6 +27,7 @@ import { ManualRedactionEntryWrapper } from '../model/manual-redaction-entry.wra import { AppStateService } from '../../../state/app-state.service'; import { AnnotationWrapper } from '../model/annotation.wrapper'; import { AnnotationUtils } from '../../../utils/annotation-utils'; +import { ManualAnnotationService } from '../service/manual-annotation.service'; export interface ViewerState { displayMode?: any; @@ -58,12 +64,14 @@ export class PdfViewerComponent implements OnInit, AfterViewInit, OnChanges { @ViewChild('viewer', { static: true }) viewer: ElementRef; instance: WebViewerInstance; + private _manualAnnotations: ManualRedactions; constructor( private readonly _appStateService: AppStateService, private readonly _translateService: TranslateService, private readonly _fileDownloadService: FileDownloadService, private readonly _appConfigService: AppConfigService, + private readonly _manualAnnotationService: ManualAnnotationService, private readonly _ngZone: NgZone ) {} @@ -87,6 +95,9 @@ export class PdfViewerComponent implements OnInit, AfterViewInit, OnChanges { 200 ); }); + this._manualAnnotationService.getManualAnnotations().subscribe((manualAnnotation) => { + this._manualAnnotations = manualAnnotation; + }); } ngOnChanges(changes: SimpleChanges): void { @@ -195,14 +206,18 @@ export class PdfViewerComponent implements OnInit, AfterViewInit, OnChanges { if (this._appStateService.isActiveFileDocumentReviewer) { this.instance.textPopup.add({ type: 'actionButton', - img: '/assets/icons/general/add-hint.svg', + img: '/assets/icons/general/add-dictionary.svg', title: this._translateService.instant( - 'pdf-viewer.text-popup.actions.suggestion-hint.label' + this._manualAnnotationService.getTitle('DICTIONARY') ), onClick: () => { const mre = this._getManualRedactionEntry(); this.manualAnnotationRequested.emit( - new ManualRedactionEntryWrapper(mre, 'HINT') + new ManualRedactionEntryWrapper( + this.instance.docViewer.getSelectedTextQuads(), + mre, + 'DICTIONARY' + ) ); } }); @@ -210,12 +225,16 @@ export class PdfViewerComponent implements OnInit, AfterViewInit, OnChanges { type: 'actionButton', img: '/assets/icons/general/add-redaction.svg', title: this._translateService.instant( - 'pdf-viewer.text-popup.actions.suggestion-redaction.label' + this._manualAnnotationService.getTitle('REDACTION') ), onClick: () => { const mre = this._getManualRedactionEntry(); this.manualAnnotationRequested.emit( - new ManualRedactionEntryWrapper(mre, 'REDACTION') + new ManualRedactionEntryWrapper( + this.instance.docViewer.getSelectedTextQuads(), + mre, + 'REDACTION' + ) ); } }); @@ -231,7 +250,6 @@ export class PdfViewerComponent implements OnInit, AfterViewInit, OnChanges { entry.positions.push(this.toPosition(parseInt(key, 10), quad)); } } - entry.quads = selectedQuads; entry.value = text; return entry; } diff --git a/apps/red-ui/src/app/screens/file/service/manual-annotation.service.ts b/apps/red-ui/src/app/screens/file/service/manual-annotation.service.ts new file mode 100644 index 000000000..a1be2f032 --- /dev/null +++ b/apps/red-ui/src/app/screens/file/service/manual-annotation.service.ts @@ -0,0 +1,185 @@ +import { Injectable } from '@angular/core'; +import { AppStateService } from '../../../state/app-state.service'; +import { + DictionaryControllerService, + ManualRedactionControllerService, + ManualRedactionEntry +} from '@redaction/red-ui-http'; +import { AnnotationWrapper } from '../model/annotation.wrapper'; +import { NotificationService, NotificationType } from '../../../notification/notification.service'; +import { TranslateService } from '@ngx-translate/core'; +import { tap } from 'rxjs/operators'; + +@Injectable({ + providedIn: 'root' +}) +export class ManualAnnotationService { + constructor( + private readonly _appStateService: AppStateService, + private readonly _translateService: TranslateService, + private readonly _notificationService: NotificationService, + private readonly _manualRedactionControllerService: ManualRedactionControllerService, + private readonly _dictionaryControllerService: DictionaryControllerService + ) {} + + public makeDictionaryEntry(manualRedactionEntry: ManualRedactionEntry) { + if (this._appStateService.isActiveProjectOwner) { + return this._makeDictionaryEntry(manualRedactionEntry); + } else { + return this._makeDictionaryRequest(manualRedactionEntry); + } + } + + public makeRedaction(manualRedactionEntry: ManualRedactionEntry) { + if (this._appStateService.isActiveProjectOwner) { + return this._makeRedaction(manualRedactionEntry); + } else { + return this._makeRedactionRequest(manualRedactionEntry); + } + } + + public acceptSuggestion(annotationWrapper: AnnotationWrapper) { + // for only here - approve the request + return this._manualRedactionControllerService + .approveRequest( + this._appStateService.activeProjectId, + this._appStateService.activeFile.fileId, + annotationWrapper.uuid + ) + .pipe( + tap( + () => { + // if approved - and not only here, make a dictionary entry + if (annotationWrapper.id.indexOf(':only_here:') < 0) { + this.getManualAnnotations().subscribe((annotations) => { + annotations.entriesToAdd.forEach((a) => { + // found the annotation + if (annotationWrapper.id.indexOf(a.id) >= 0) { + console.log(a); + this._makeDictionaryEntry(a).subscribe(() => {}); + } + }); + }); + } else { + this._notify('manual-annotation.reject-request.success'); + } + }, + () => { + this._notify('manual-annotation.reject-request.error'); + } + ) + ); + } + + public rejectSuggestion(annotationWrapper: AnnotationWrapper) { + return this._manualRedactionControllerService + .undo( + this._appStateService.activeProjectId, + this._appStateService.activeFile.fileId, + annotationWrapper.uuid + ) + .pipe( + tap( + () => { + this._notify('manual-annotation.reject-request.success'); + }, + () => { + this._notify('manual-annotation.reject-request.error'); + } + ) + ); + } + + public removeRedaction(annotationWrapper: AnnotationWrapper) {} + + private _makeDictionaryRequest(manualRedactionEntry: ManualRedactionEntry) { + return this._makeRedactionRequest(manualRedactionEntry); + } + + private _makeDictionaryEntry(manualRedactionEntry: ManualRedactionEntry) { + return this._dictionaryControllerService + .addEntry([manualRedactionEntry.value], manualRedactionEntry.type) + .pipe( + tap( + () => this._notify('manual-annotation.dictionary-add.success'), + () => { + this._notify( + 'manual-annotation.dictionary-add.error', + NotificationType.ERROR + ); + } + ) + ); + } + + private _makeRedactionRequest(manualRedactionEntry: ManualRedactionEntry) { + return this._manualRedactionControllerService + .requestAddRedaction( + manualRedactionEntry, + this._appStateService.activeProject.project.projectId, + this._appStateService.activeFile.fileId + ) + .pipe( + tap( + () => this._notify('manual-annotation.redaction-request.success'), + () => { + this._notify( + 'manual-annotation.redaction-request.error', + NotificationType.ERROR + ); + } + ) + ); + } + + private _makeRedaction(manualRedactionEntry: ManualRedactionEntry) { + return this._manualRedactionControllerService + .addRedaction( + manualRedactionEntry, + this._appStateService.activeProject.project.projectId, + this._appStateService.activeFile.fileId + ) + .pipe( + tap( + () => this._notify('manual-annotation.redaction-add.success'), + () => { + this._notify( + 'manual-annotation.redaction-add.error', + NotificationType.ERROR + ); + } + ) + ); + } + + private _notify(key: string, type: NotificationType = NotificationType.SUCCESS) { + this._notificationService.showToastNotification( + this._translateService.instant(key), + null, + type + ); + } + + getTitle(type: 'DICTIONARY' | 'REDACTION') { + if (this._appStateService.isActiveProjectOwner) { + if (type === 'DICTIONARY') { + return 'manual-redaction.dialog.header.dictionary.label'; + } else { + return 'manual-redaction.dialog.header.redaction.label'; + } + } else { + if (type === 'DICTIONARY') { + return 'manual-redaction.dialog.header.request-dictionary.label'; + } else { + return 'manual-redaction.dialog.header.request-redaction.label'; + } + } + } + + getManualAnnotations() { + return this._manualRedactionControllerService.getManualRedaction( + this._appStateService.activeProject.project.projectId, + this._appStateService.activeFile.fileId + ); + } +} diff --git a/apps/red-ui/src/assets/i18n/en.json b/apps/red-ui/src/assets/i18n/en.json index b1def7be3..12b4cb8dd 100644 --- a/apps/red-ui/src/assets/i18n/en.json +++ b/apps/red-ui/src/assets/i18n/en.json @@ -35,14 +35,14 @@ }, "dialog": { "header": { - "hint": { - "label": "Add Hint" + "dictionary": { + "label": "Add to dictionary" }, "redaction": { "label": "Add Redaction" }, - "request-hint": { - "label": "Request Hint" + "request-dictionary": { + "label": "Request add to dictionary" }, "request-redaction": { "label": "Request Redaction" diff --git a/apps/red-ui/src/assets/icons/general/add-dictionary.svg b/apps/red-ui/src/assets/icons/general/add-dictionary.svg new file mode 100644 index 000000000..c8dfcb7b3 --- /dev/null +++ b/apps/red-ui/src/assets/icons/general/add-dictionary.svg @@ -0,0 +1,68 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/libs/red-ui-http/src/lib/model/manualRedactionEntry.ts b/libs/red-ui-http/src/lib/model/manualRedactionEntry.ts index 9ceebfdca..2f700d8d3 100644 --- a/libs/red-ui-http/src/lib/model/manualRedactionEntry.ts +++ b/libs/red-ui-http/src/lib/model/manualRedactionEntry.ts @@ -9,22 +9,19 @@ * https://github.com/swagger-api/swagger-codegen.git * Do not edit the class manually. */ -import { Comment } from './comment'; import { Rectangle } from './rectangle'; export interface ManualRedactionEntry { addToDictionary?: boolean; - comments?: Array; id?: string; + legalBasis?: string; positions?: Array; reason?: string; status?: ManualRedactionEntry.StatusEnum; type?: string; user?: string; value?: string; - [key: string]: any; } - export namespace ManualRedactionEntry { export type StatusEnum = 'REQUESTED' | 'APPROVED' | 'DECLINED'; export const StatusEnum = { diff --git a/libs/red-ui-http/src/lib/model/manualRedactions.ts b/libs/red-ui-http/src/lib/model/manualRedactions.ts index edc96ca2c..c42f1e644 100644 --- a/libs/red-ui-http/src/lib/model/manualRedactions.ts +++ b/libs/red-ui-http/src/lib/model/manualRedactions.ts @@ -9,10 +9,12 @@ * https://github.com/swagger-api/swagger-codegen.git * Do not edit the class manually. */ +import { Comment } from './comment'; import { IdRemoval } from './idRemoval'; import { ManualRedactionEntry } from './manualRedactionEntry'; export interface ManualRedactions { + comments?: { [key: string]: Array }; entriesToAdd?: Array; idsToRemove?: Array; }