annotation actions WIP
This commit is contained in:
parent
8cf51e6014
commit
c9dfac6ece
@ -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: [
|
||||
|
||||
@ -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>
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -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>
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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(
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
|
||||
@ -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(
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user