move annotations list to a separate component

This commit is contained in:
Dan Percic 2021-09-23 15:20:17 +03:00
parent 162d900b9c
commit ff4cf841e3
9 changed files with 221 additions and 197 deletions

View File

@ -60,8 +60,6 @@ export class AnnotationWrapper {
private _origin: RedactionLogEntryWrapper;
constructor() {}
get isChangeLogRemoved() {
return this.changeLogType === 'REMOVED';
}

View File

@ -0,0 +1,60 @@
<div
(click)="annotationClicked(annotation, $event)"
*ngFor="let annotation of annotations"
[attr.annotation-id]="annotation.id"
[attr.annotation-page]="activeViewerPage"
[class.active]="isSelected(annotation.annotationId)"
[class.multi-select-active]="multiSelectActive"
class="annotation-wrapper"
>
<div class="active-bar-marker"></div>
<div [class.removed]="annotation.isChangeLogRemoved" class="annotation">
<div [matTooltip]="annotation.content" class="details" matTooltipPosition="above">
<redaction-type-annotation-icon [annotation]="annotation"></redaction-type-annotation-icon>
<div class="flex-1">
<div>
<strong>{{ annotation.typeLabel | translate }}</strong>
</div>
<div *ngIf="annotation?.type !== 'manual'">
<strong>
<span>{{ annotation.descriptor | translate }}</span
>: </strong
>{{ annotation.type | humanize: false }}
</div>
<div *ngIf="annotation.shortContent && !annotation.isHint">
<strong><span translate="content"></span>: </strong>{{ annotation.shortContent }}
</div>
</div>
<div class="active-icon-marker-container">
<iqser-round-checkbox
*ngIf="multiSelectActive && isSelected(annotation.annotationId)"
[active]="true"
></iqser-round-checkbox>
</div>
</div>
<div class="actions-wrapper">
<div
(click)="comments.toggleExpandComments($event)"
[matTooltip]="'comments.comments' | translate: { count: annotation.comments?.length }"
class="comments-counter"
matTooltipPosition="above"
>
<mat-icon svgIcon="red:comment"></mat-icon>
{{ annotation.comments.length }}
</div>
<div *ngIf="!multiSelectActive" class="actions">
<ng-container
[ngTemplateOutletContext]="{ annotation: annotation }"
[ngTemplateOutlet]="annotationActionsTemplate"
></ng-container>
</div>
</div>
<redaction-comments #comments [annotation]="annotation"></redaction-comments>
</div>
</div>

View File

@ -0,0 +1,89 @@
@use 'variables';
:host {
width: 100%;
}
.annotation-wrapper {
display: flex;
width: 100%;
border-bottom: 1px solid variables.$separator;
.active-bar-marker {
min-width: 4px;
min-height: 100%;
}
.active-icon-marker-container {
min-width: 20px;
}
&.active {
&:not(.lower-height) .active-bar-marker {
background-color: variables.$primary;
}
}
.annotation {
padding: 10px 16px 8px 10px;
font-size: 11px;
line-height: 14px;
cursor: pointer;
display: flex;
flex-direction: column;
width: 100%;
&.removed {
text-decoration: line-through;
color: variables.$grey-7;
}
.details {
display: flex;
position: relative;
}
.actions-wrapper {
display: flex;
justify-content: space-between;
align-items: center;
margin-top: 8px;
min-height: 34px;
padding-left: 18px;
.comments-counter {
cursor: pointer;
display: flex;
align-items: center;
padding: 0 8px;
transition: background-color 0.2s;
line-height: 13px;
height: 24px;
border-radius: 12px;
mat-icon {
width: 10px;
height: 10px;
margin-right: 4px;
}
&:hover {
background-color: variables.$grey-4;
}
}
}
redaction-type-annotation-icon {
margin-top: 6px;
margin-right: 10px;
}
}
&:hover {
background-color: variables.$grey-8;
::ng-deep .annotation-actions {
display: flex;
}
}
}

View File

@ -0,0 +1,48 @@
import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output, TemplateRef } from '@angular/core';
import { AnnotationWrapper } from '@models/file/annotation.wrapper';
import { IqserEventTarget } from '@iqser/common-ui';
@Component({
selector: 'redaction-annotations-list',
templateUrl: './annotations-list.component.html',
styleUrls: ['./annotations-list.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class AnnotationsListComponent {
@Input() annotations: AnnotationWrapper[];
@Input() selectedAnnotations: AnnotationWrapper[];
@Input() annotationActionsTemplate: TemplateRef<unknown>;
@Input() multiSelectActive = false;
@Input() activeViewerPage: number;
@Input() canMultiSelect = true;
@Output() readonly multiSelectActiveChange = new EventEmitter<boolean>();
@Output() readonly pagesPanelActive = new EventEmitter<boolean>();
@Output() readonly selectAnnotations = new EventEmitter<
AnnotationWrapper[] | { annotations: AnnotationWrapper[]; multiSelect: boolean }
>();
@Output() readonly deselectAnnotations = new EventEmitter<AnnotationWrapper[]>();
annotationClicked(annotation: AnnotationWrapper, $event: MouseEvent): void {
if (($event.target as IqserEventTarget).localName === 'input') {
return;
}
this.pagesPanelActive.emit(false);
if (this.isSelected(annotation.annotationId)) {
this.deselectAnnotations.emit([annotation]);
} else {
if (this.canMultiSelect && ($event.ctrlKey || $event.metaKey) && this.selectedAnnotations.length > 0) {
this.multiSelectActive = true;
this.multiSelectActiveChange.emit(true);
}
this.selectAnnotations.emit({
annotations: [annotation],
multiSelect: this.multiSelectActive
});
}
}
isSelected(annotationId: string): boolean {
return !!this.selectedAnnotations?.find(a => a?.annotationId === annotationId);
}
}

View File

@ -1,6 +1,3 @@
<!-- This is a hack to subscribe to an observable using async pipe instead of component class -->
<ng-container *ngIf="displayedAnnotations$ | async"></ng-container>
<div *ngIf="!excludePages" class="right-title heading" translate="file-preview.tabs.annotations.label">
<div>
<div
@ -150,8 +147,7 @@
(click)="actionPerformed.emit('view-exclude-pages')"
class="with-underline"
translate="file-preview.excluded-from-redaction"
>
</a
></a
>.
</ng-container>
</iqser-empty-state>
@ -174,62 +170,17 @@
</div>
</ng-container>
<div
(click)="annotationClicked(annotation, $event)"
*ngFor="let annotation of displayedAnnotations.get(activeViewerPage)"
[attr.annotation-id]="annotation.id"
[attr.annotation-page]="activeViewerPage"
[class.active]="isSelected(annotation)"
[class.multi-select-active]="multiSelectActive"
class="annotation-wrapper"
>
<div class="active-bar-marker"></div>
<div [class.removed]="annotation.isChangeLogRemoved" class="annotation">
<div [matTooltip]="annotation.content" class="details" matTooltipPosition="above">
<redaction-type-annotation-icon [annotation]="annotation"></redaction-type-annotation-icon>
<div class="flex-1">
<div>
<strong>{{ annotation.typeLabel | translate }}</strong>
</div>
<div *ngIf="annotation?.type !== 'manual'">
<strong>
<span>{{ annotation.descriptor | translate }}</span
>: </strong
>{{ annotation.type | humanize: false }}
</div>
<div *ngIf="annotation.shortContent && !annotation.isHint">
<strong><span translate="content"></span>: </strong>{{ annotation.shortContent }}
</div>
</div>
<div class="active-icon-marker-container">
<iqser-round-checkbox
*ngIf="multiSelectActive && isSelected(annotation)"
[active]="true"
></iqser-round-checkbox>
</div>
</div>
<div class="actions-wrapper">
<div
(click)="toggleExpandComments(annotation, $event)"
[matTooltip]="'comments.comments' | translate: { count: annotation.comments?.length }"
class="comments-counter"
matTooltipPosition="above"
>
<mat-icon svgIcon="red:comment"></mat-icon>
{{ annotation.comments.length }}
</div>
<div *ngIf="!multiSelectActive" class="actions">
<ng-container
[ngTemplateOutletContext]="{ annotation: annotation }"
[ngTemplateOutlet]="annotationActionsTemplate"
></ng-container>
</div>
</div>
<redaction-comments [annotation]="annotation"></redaction-comments>
</div>
</div>
<redaction-annotations-list
[canMultiSelect]="!isReadOnly"
[annotations]="(displayedAnnotations$ | async)?.get(activeViewerPage)"
[selectedAnnotations]="selectedAnnotations"
[annotationActionsTemplate]="annotationActionsTemplate"
[(multiSelectActive)]="multiSelectActive"
[activeViewerPage]="activeViewerPage"
(pagesPanelActive)="pagesPanelActive = $event"
(selectAnnotations)="selectAnnotations.emit($event)"
(deselectAnnotations)="deselectAnnotations.emit($event)"
></redaction-annotations-list>
</div>
</ng-container>

View File

@ -157,96 +157,12 @@
align-items: center;
flex-direction: column;
.annotation-wrapper {
display: flex;
width: 100%;
border-bottom: 1px solid variables.$separator;
.active-bar-marker {
min-width: 4px;
min-height: 100%;
}
.active-icon-marker-container {
min-width: 20px;
}
&.active {
&:not(.lower-height) .active-bar-marker {
background-color: variables.$primary;
}
}
.annotation {
padding: 10px 16px 8px 10px;
font-size: 11px;
line-height: 14px;
cursor: pointer;
display: flex;
flex-direction: column;
width: 100%;
&.removed {
text-decoration: line-through;
color: variables.$grey-7;
}
.details {
display: flex;
position: relative;
}
.actions-wrapper {
display: flex;
justify-content: space-between;
align-items: center;
margin-top: 8px;
min-height: 34px;
padding-left: 18px;
.comments-counter {
cursor: pointer;
display: flex;
align-items: center;
padding: 0 8px;
transition: background-color 0.2s;
line-height: 13px;
height: 24px;
border-radius: 12px;
mat-icon {
width: 10px;
height: 10px;
margin-right: 4px;
}
&:hover {
background-color: variables.$grey-4;
}
}
}
redaction-type-annotation-icon {
margin-top: 6px;
margin-right: 10px;
}
}
&:hover {
background-color: variables.$grey-8;
::ng-deep .annotation-actions {
display: flex;
}
}
}
&:hover {
overflow-y: auto;
@include common-mixins.scroll-bar;
}
&.has-scrollbar:hover .annotation-wrapper .annotation {
&.has-scrollbar:hover::ng-deep .annotation-wrapper .annotation {
padding-right: 5px;
}
}

View File

@ -1,23 +1,10 @@
import {
ChangeDetectorRef,
Component,
ElementRef,
EventEmitter,
HostListener,
Input,
Output,
QueryList,
TemplateRef,
ViewChild,
ViewChildren
} from '@angular/core';
import { ChangeDetectorRef, Component, ElementRef, EventEmitter, HostListener, Input, Output, TemplateRef, ViewChild } from '@angular/core';
import { AnnotationWrapper } from '@models/file/annotation.wrapper';
import { AnnotationProcessingService } from '../../services/annotation-processing.service';
import { MatDialogRef, MatDialogState } from '@angular/material/dialog';
import scrollIntoView from 'scroll-into-view-if-needed';
import { CircleButtonTypes, Debounce, FilterService, IconButtonTypes, IqserEventTarget, NestedFilter } from '@iqser/common-ui';
import { FileDataModel } from '@models/file/file-data.model';
import { CommentsComponent } from '../comments/comments.component';
import { PermissionsService } from '@services/permissions.service';
import { WebViewerInstance } from '@pdftron/webviewer';
import { BehaviorSubject, combineLatest, Observable } from 'rxjs';
@ -56,7 +43,6 @@ export class FileWorkloadComponent {
@Output() readonly actionPerformed = new EventEmitter<string>();
displayedPages: number[] = [];
pagesPanelActive = true;
@ViewChildren(CommentsComponent) readonly annotationCommentsComponents: QueryList<CommentsComponent>;
@ViewChild('annotationsElement') private readonly _annotationsElement: ElementRef;
@ViewChild('quickNavigation') private readonly _quickNavigationElement: ElementRef;
@ -118,15 +104,6 @@ export class FileWorkloadComponent {
}
}
isSelected(annotation: AnnotationWrapper) {
return this.selectedAnnotations?.find(a => a?.id === annotation.id);
}
toggleExpandComments(annotation: AnnotationWrapper, $event: MouseEvent) {
$event.stopPropagation();
this.annotationCommentsComponents.find(c => c.annotation === annotation).toggleExpandComments();
}
logAnnotation(annotation: AnnotationWrapper) {
console.log(annotation);
}
@ -156,25 +133,6 @@ export class FileWorkloadComponent {
return this.displayedAnnotations;
}
annotationClicked(annotation: AnnotationWrapper, $event: MouseEvent): void {
if (($event.target as IqserEventTarget).localName === 'input') {
return;
}
this.pagesPanelActive = false;
this.logAnnotation(annotation);
if (this.isSelected(annotation)) {
this.deselectAnnotations.emit([annotation]);
} else {
if (($event.ctrlKey || $event.metaKey) && this.selectedAnnotations.length > 0) {
this.multiSelectActive = true;
}
this.selectAnnotations.emit({
annotations: [annotation],
multiSelect: this.multiSelectActive
});
}
}
@HostListener('window:keyup', ['$event'])
handleKeyEvent($event: KeyboardEvent): void {
if (

View File

@ -49,6 +49,7 @@ import { DossiersService } from './services/dossiers.service';
import { DossierDetailsStatsComponent } from './components/dossier-details-stats/dossier-details-stats.component';
import { SearchScreenComponent } from './screens/search-screen/search-screen.component';
import { EditDossierDeletedDocumentsComponent } from './dialogs/edit-dossier-dialog/deleted-documents/edit-dossier-deleted-documents.component';
import { AnnotationsListComponent } from './components/file-workload/components/annotations-list/annotations-list.component';
const screens = [DossierListingScreenComponent, DossierOverviewScreenComponent, FilePreviewScreenComponent, SearchScreenComponent];
@ -89,6 +90,7 @@ const components = [
PageExclusionComponent,
DossierDetailsStatsComponent,
EditDossierDeletedDocumentsComponent,
AnnotationsListComponent,
...screens,
...dialogs
@ -111,4 +113,5 @@ const services = [
providers: [...services],
imports: [CommonModule, SharedModule, FileUploadDownloadModule, DossiersRoutingModule]
})
export class DossiersModule {}
export class DossiersModule {
}

View File

@ -16,11 +16,12 @@ export class AnnotationDrawService {
) {}
drawAnnotations(activeViewer: WebViewerInstance, annotationWrappers: AnnotationWrapper[], hideSkipped = false, compareMode = false) {
const annotations = [];
annotationWrappers.forEach(annotation => {
annotations.push(this.computeAnnotation(activeViewer, annotation, hideSkipped, compareMode));
});
if (!activeViewer) {
return;
}
const annotations = annotationWrappers.map(annotation =>
this.computeAnnotation(activeViewer, annotation, hideSkipped, compareMode)
);
const annotationManager = activeViewer.Core.annotationManager;
annotationManager.addAnnotations(annotations, { imported: true });
annotationManager.drawAnnotationsFromList(annotations);