From c9dfac6ece99726a5dc977574fec37b7e4c1f8bc Mon Sep 17 00:00:00 2001 From: Timo Bejan Date: Sun, 8 Nov 2020 19:17:14 +0200 Subject: [PATCH] annotation actions WIP --- apps/red-ui/src/app/app.module.ts | 3 + .../annotation-actions.component.html | 42 +++++++ .../annotation-actions.component.scss | 22 ++++ .../annotation-actions.component.ts | 60 ++++++++++ .../file-preview-screen.component.html | 76 +----------- .../file-preview-screen.component.scss | 29 +---- .../file-preview-screen.component.ts | 13 --- .../screens/file/model/annotation.wrapper.ts | 2 + .../file/service/manual-annotation.service.ts | 109 ++++++++++-------- 9 files changed, 194 insertions(+), 162 deletions(-) create mode 100644 apps/red-ui/src/app/screens/file/annotation-actions/annotation-actions.component.html create mode 100644 apps/red-ui/src/app/screens/file/annotation-actions/annotation-actions.component.scss create mode 100644 apps/red-ui/src/app/screens/file/annotation-actions/annotation-actions.component.ts diff --git a/apps/red-ui/src/app/app.module.ts b/apps/red-ui/src/app/app.module.ts index e7c40a6f6..82e59f754 100644 --- a/apps/red-ui/src/app/app.module.ts +++ b/apps/red-ui/src/app/app.module.ts @@ -73,6 +73,7 @@ import { PageIndicatorComponent } from './screens/file/page-indicator/page-indic import { NeedsWorkBadgeComponent } from './screens/common/needs-work-badge/needs-work-badge.component'; import { ProjectOverviewEmptyComponent } from './screens/empty-states/project-overview-empty/project-overview-empty.component'; import { ProjectListingEmptyComponent } from './screens/empty-states/project-listing-empty/project-listing-empty.component'; +import { AnnotationActionsComponent } from './screens/file/annotation-actions/annotation-actions.component'; import { ProjectListingDetailsComponent } from './screens/project-listing-screen/project-listing-details/project-listing-details.component'; export function HttpLoaderFactory(httpClient: HttpClient) { @@ -113,6 +114,8 @@ export function HttpLoaderFactory(httpClient: HttpClient) { NeedsWorkBadgeComponent, ProjectOverviewEmptyComponent, ProjectListingEmptyComponent, + AnnotationActionsComponent + ProjectListingEmptyComponent, ProjectListingDetailsComponent ], imports: [ diff --git a/apps/red-ui/src/app/screens/file/annotation-actions/annotation-actions.component.html b/apps/red-ui/src/app/screens/file/annotation-actions/annotation-actions.component.html new file mode 100644 index 000000000..278d669a3 --- /dev/null +++ b/apps/red-ui/src/app/screens/file/annotation-actions/annotation-actions.component.html @@ -0,0 +1,42 @@ +
+ + +
+ +
+
+
+ +
+
+
+ + +
diff --git a/apps/red-ui/src/app/screens/file/annotation-actions/annotation-actions.component.scss b/apps/red-ui/src/app/screens/file/annotation-actions/annotation-actions.component.scss new file mode 100644 index 000000000..e4537323f --- /dev/null +++ b/apps/red-ui/src/app/screens/file/annotation-actions/annotation-actions.component.scss @@ -0,0 +1,22 @@ +@import '../../../../assets/styles/red-variables'; + +.annotation-actions { + position: absolute; + right: 0; + top: 0; + height: 40px; + display: none; + align-items: center; + justify-content: flex-end; + width: 120px; + padding-right: 16px; + background: linear-gradient(to right, transparent 0%, #f9fafb, #f9fafb, #f9fafb); + + .confirm.active { + background-color: $grey-2; + } + + &.visible { + display: flex; + } +} diff --git a/apps/red-ui/src/app/screens/file/annotation-actions/annotation-actions.component.ts b/apps/red-ui/src/app/screens/file/annotation-actions/annotation-actions.component.ts new file mode 100644 index 000000000..93bf5d0dd --- /dev/null +++ b/apps/red-ui/src/app/screens/file/annotation-actions/annotation-actions.component.ts @@ -0,0 +1,60 @@ +import { Component, Input, OnInit } from '@angular/core'; +import { AnnotationWrapper } from '../model/annotation.wrapper'; +import { AppStateService } from '../../../state/app-state.service'; +import { TypeValue } from '@redaction/red-ui-http'; + +@Component({ + selector: 'redaction-annotation-actions', + templateUrl: './annotation-actions.component.html', + styleUrls: ['./annotation-actions.component.scss'] +}) +export class AnnotationActionsComponent implements OnInit { + @Input() annotation: AnnotationWrapper; + @Input() canPerformAnnotationActions: boolean; + + suggestionType: TypeValue; + menuOpen: boolean; + + constructor(public appStateService: AppStateService) {} + + ngOnInit(): void { + this.suggestionType = this.appStateService.getDictionaryTypeValue('suggestion'); + } + + public isAnnotationMenuOpen() { + return this.annotation.id === this.activeMenuAnnotation?.id; + } + + get canAcceptSuggestion() { + return ( + this.appStateService.isActiveProjectOwnerAndManager && + (this.annotation.superType === 'suggestion' || + this.annotation.superType === 'suggestion-remove') + ); + } + + onSuggestionMenuClose() {} + + acceptSuggestion($event: MouseEvent, annotation: AnnotationWrapper) { + $event.stopPropagation(); + } + + rejectSuggestion($event: MouseEvent, annotation: AnnotationWrapper) { + $event.stopPropagation(); + } + + suggestRemoveAnnotation($event: MouseEvent, annotation: AnnotationWrapper) { + $event.stopPropagation(); + } + + openAcceptSuggestionMenu($event: MouseEvent, annotation: AnnotationWrapper) {} + + public openAcceptSuggestionMenu($event: MouseEvent) { + $event.preventDefault(); + this.menuOpen = true; + } + + public onSuggestionMenuClose() { + this.menuOpen = false; + } +} 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 65180c0a9..48afed6cd 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 @@ -213,79 +213,11 @@ - - -
- - -
- - -
-
-
- - -
-
-
- - -
+ diff --git a/apps/red-ui/src/app/screens/file/file-preview-screen/file-preview-screen.component.scss b/apps/red-ui/src/app/screens/file/file-preview-screen/file-preview-screen.component.scss index d070af005..ec2d62c70 100644 --- a/apps/red-ui/src/app/screens/file/file-preview-screen/file-preview-screen.component.scss +++ b/apps/red-ui/src/app/screens/file/file-preview-screen/file-preview-screen.component.scss @@ -102,7 +102,7 @@ redaction-pdf-viewer { &:hover { background-color: #f9fafb; - .annotation-actions { + ::ng-deep .annotation-actions { display: flex; } } @@ -110,33 +110,6 @@ redaction-pdf-viewer { &.active { border-left: 2px solid $primary; } - - .annotation-actions { - position: absolute; - right: 0; - top: 0; - height: 40px; - display: none; - align-items: center; - justify-content: flex-end; - width: 120px; - padding-right: 16px; - background: linear-gradient( - to right, - transparent 0%, - #f9fafb, - #f9fafb, - #f9fafb - ); - - .confirm.active { - background-color: $grey-2; - } - - &.visible { - display: flex; - } - } } } } 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 687949123..d6f9cd197 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 @@ -282,10 +282,6 @@ export class FilePreviewScreenComponent implements OnInit { } } - public isAnnotationMenuOpen(annotation: AnnotationWrapper) { - return annotation.id === this._activeMenuAnnotation?.id; - } - public acceptSuggestion($event: MouseEvent, annotation: AnnotationWrapper) { this.ngZone.run(() => { this._dialogRef = this._dialogService.openAcceptSuggestionModal( @@ -298,15 +294,6 @@ export class FilePreviewScreenComponent implements OnInit { }); } - public openAcceptSuggestionMenu($event: MouseEvent, annotation: AnnotationWrapper) { - $event.preventDefault(); - this._activeMenuAnnotation = annotation; - } - - public onSuggestionMenuClose() { - this._activeMenuAnnotation = null; - } - public rejectSuggestion($event: MouseEvent, annotation: AnnotationWrapper) { this.ngZone.run(() => { this._dialogRef = this._dialogService.openRejectSuggestionModal( diff --git a/apps/red-ui/src/app/screens/file/model/annotation.wrapper.ts b/apps/red-ui/src/app/screens/file/model/annotation.wrapper.ts index bd7cf9ff2..aebf22a9a 100644 --- a/apps/red-ui/src/app/screens/file/model/annotation.wrapper.ts +++ b/apps/red-ui/src/app/screens/file/model/annotation.wrapper.ts @@ -25,6 +25,7 @@ export class AnnotationWrapper { id: string; content: string; manual: boolean; + userId: string; pageNumber; static fromRedactionLog( @@ -79,6 +80,7 @@ export class AnnotationWrapper { annotationWrapper.comments = comments[manualRedactionEntry.id] ? comments[manualRedactionEntry.id] : []; + annotationWrapper.userId = manualRedactionEntry.user; return annotationWrapper; } 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 index 86eac3305..2554915ee 100644 --- 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 @@ -8,7 +8,7 @@ import { import { AnnotationWrapper } from '../model/annotation.wrapper'; import { NotificationService, NotificationType } from '../../../notification/notification.service'; import { TranslateService } from '@ngx-translate/core'; -import { mergeMap, tap } from 'rxjs/operators'; +import { tap } from 'rxjs/operators'; import { UserService } from '../../../user/user.service'; @Injectable({ @@ -24,7 +24,9 @@ export class ManualAnnotationService { private readonly _dictionaryControllerService: DictionaryControllerService ) {} - public addComment(comment: string, annotationId: string) { + // Comments + // this wraps /manualRedaction/comment/add + addComment(comment: string, annotationId: string) { return this._manualRedactionControllerService.addComment( { text: comment }, this._appStateService.activeProjectId, @@ -33,6 +35,7 @@ export class ManualAnnotationService { ); } + // this wraps /manualRedaction/comment/undo deleteComment(commentId: string, annotationId: string) { return this._manualRedactionControllerService.undoComment( this._appStateService.activeProjectId, @@ -42,28 +45,20 @@ export class ManualAnnotationService { ); } - public makeDictionaryEntry(manualRedactionEntry: ManualRedactionEntry) { - manualRedactionEntry.addToDictionary = true; - return this.makeRedaction(manualRedactionEntry); - } - - public makeRedaction(manualRedactionEntry: ManualRedactionEntry) { + // this wraps + // /manualRedaction/redaction/add + // /manualRedaction/request/add + addAnnotation(manualRedactionEntry: ManualRedactionEntry) { if (this._appStateService.isActiveProjectOwnerAndManager) { - if (!manualRedactionEntry.addToDictionary) { - return this._makeRedaction(manualRedactionEntry); - } else { - return this._makeRedactionRequest(manualRedactionEntry).pipe( - mergeMap((response) => { - return this.acceptSuggestion(response.annotationId); - }) - ); - } + return this._makeRedaction(manualRedactionEntry); } else { return this._makeRedactionRequest(manualRedactionEntry); } } - public acceptSuggestion(annotationId: string) { + // this wraps + // /manualRedaction/approve + public approveRequest(annotationId: string) { // for only here - approve the request return this._manualRedactionControllerService .approveRequest( @@ -74,46 +69,62 @@ export class ManualAnnotationService { .pipe( tap( () => { - this._notify('manual-annotation.reject-request.success'); + this._notify('manual-annotation.approve-request.success'); }, () => { - this._notify('manual-annotation.reject-request.error'); + this._notify('manual-annotation.approve-request.error'); } ) ); } - public rejectSuggestion(annotationWrapper: AnnotationWrapper) { - // if you're the owner, you undo, otherwise you reject - const observable = - annotationWrapper.manualRedactionOwner === this._userService.userId - ? this._manualRedactionControllerService.undo( - this._appStateService.activeProjectId, - this._appStateService.activeFile.fileId, - annotationWrapper.id - ) - : this._manualRedactionControllerService.declineRequest( - this._appStateService.activeProjectId, - this._appStateService.activeFile.fileId, - annotationWrapper.id - ); - - return observable.pipe( - tap( - () => { - this._notify('manual-annotation.reject-request.success'); - }, - () => { - this._notify('manual-annotation.reject-request.error'); - } - ) - ); + // this wraps + // /manualRedaction/decline/remove + // /manualRedaction/undo + declineOrRemoveRequest(annotationWrapper: AnnotationWrapper) { + if (this._appStateService.isActiveProjectOwnerAndManager) { + return this._manualRedactionControllerService + .declineRequest( + this._appStateService.activeProjectId, + this._appStateService.activeFileId, + annotationWrapper.id + ) + .pipe( + tap( + () => this._notify('manual-annotation.undo-request.success'), + () => { + this._notify( + 'manual-annotation.undo-request.error', + NotificationType.ERROR + ); + } + ) + ); + } else { + return this._manualRedactionControllerService + .undo( + this._appStateService.activeProjectId, + this._appStateService.activeFileId, + annotationWrapper.id + ) + .pipe( + tap( + () => this._notify('manual-annotation.undo-request.success'), + () => { + this._notify( + 'manual-annotation.undo-request.error', + NotificationType.ERROR + ); + } + ) + ); + } } - public removeAnnotation( - annotationWrapper: AnnotationWrapper, - removeFromDictionary: boolean = false - ) { + // this wraps + // /manualRedaction/redaction/remove/ + // /manualRedaction/request/remove/ + removeAnnotation(annotationWrapper: AnnotationWrapper, removeFromDictionary: boolean = false) { if (this._appStateService.isActiveProjectOwnerAndManager) { return this._manualRedactionControllerService .removeRedaction(