annotation actions WIP

This commit is contained in:
Timo Bejan 2020-11-08 19:17:14 +02:00
parent 8cf51e6014
commit c9dfac6ece
9 changed files with 194 additions and 162 deletions

View File

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

View File

@ -0,0 +1,42 @@
<div
[class.visible]="isAnnotationMenuOpen()"
*ngIf="canPerformAnnotationActions"
class="annotation-actions"
>
<button
(click)="openAcceptSuggestionMenu($event)"
*ngIf="canAcceptSuggestion"
[class.active]="isAnnotationMenuOpen()"
[matMenuTriggerFor]="menu"
class="confirm"
mat-icon-button
>
<mat-icon svgIcon="red:check-alt"></mat-icon>
</button>
<mat-menu #menu="matMenu" (closed)="onSuggestionMenuClose()" xPosition="before">
<div (click)="acceptSuggestion($event, annotation)" mat-menu-item>
<redaction-annotation-icon [typeValue]="suggestionType"></redaction-annotation-icon>
<div translate="file-preview.tabs.annotations.accept-suggestion.add-to-dict"></div>
</div>
<div (click)="acceptSuggestion($event, annotation)" mat-menu-item>
<redaction-annotation-icon [typeValue]="suggestionType"></redaction-annotation-icon>
<div translate="file-preview.tabs.annotations.accept-suggestion.only-here"></div>
</div>
</mat-menu>
<button
(click)="rejectSuggestion($event, annotation)"
*ngIf="
annotation.superType === 'suggestion' || annotation.superType === 'suggestion-remove'
"
mat-icon-button
>
<mat-icon svgIcon="red:close"></mat-icon>
</button>
<button
(click)="suggestRemoveAnnotation($event, annotation)"
*ngIf="annotation.superType === 'redaction' || annotation.superType === 'hint'"
mat-icon-button
>
<mat-icon svgIcon="red:trash"></mat-icon>
</button>
</div>

View File

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

View File

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

View File

@ -213,79 +213,11 @@
</div>
</div>
</div>
<redaction-comments [annotation]="annotation"></redaction-comments>
<div
[class.visible]="isAnnotationMenuOpen(annotation)"
*ngIf="canPerformAnnotationActions"
class="annotation-actions"
>
<button
(click)="openAcceptSuggestionMenu($event, annotation)"
*ngIf="
appStateService.isActiveProjectOwnerAndManager &&
(annotation.superType === 'suggestion' ||
annotation.superType === 'suggestion-remove')
"
[class.active]="isAnnotationMenuOpen(annotation)"
[matMenuTriggerFor]="menu"
class="confirm"
mat-icon-button
>
<mat-icon svgIcon="red:check-alt"></mat-icon>
</button>
<mat-menu
#menu="matMenu"
(closed)="onSuggestionMenuClose()"
xPosition="before"
>
<div
(click)="acceptSuggestion($event, annotation)"
mat-menu-item
>
<!-- TODO -->
<redaction-annotation-icon
[typeValue]="{ hexColor: '#5CE594', type: 'S' }"
></redaction-annotation-icon>
<div
translate="file-preview.tabs.annotations.accept-suggestion.add-to-dict"
></div>
</div>
<div
(click)="acceptSuggestion($event, annotation)"
mat-menu-item
>
<!-- TODO -->
<redaction-annotation-icon
[typeValue]="{ hexColor: '#5B97DB', type: 'S' }"
></redaction-annotation-icon>
<div
translate="file-preview.tabs.annotations.accept-suggestion.only-here"
></div>
</div>
</mat-menu>
<button
(click)="rejectSuggestion($event, annotation)"
*ngIf="
annotation.superType === 'suggestion' ||
annotation.superType === 'suggestion-remove'
"
mat-icon-button
>
<mat-icon svgIcon="red:close"></mat-icon>
</button>
<button
(click)="suggestRemoveAnnotation($event, annotation)"
*ngIf="
annotation.superType === 'redaction' ||
annotation.superType === 'hint'
"
mat-icon-button
>
<mat-icon svgIcon="red:trash"></mat-icon>
</button>
</div>
<redaction-annotation-actions
[annotation]="annotation"
[canPerformAnnotationActions]="canPerformAnnotationActions"
></redaction-annotation-actions>
</div>
</div>
</div>

View File

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

View File

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

View File

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

View File

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