File workload separate component

This commit is contained in:
Adina Țeudan 2021-03-25 01:22:34 +02:00
parent f268726402
commit 5636656f76
9 changed files with 873 additions and 667 deletions

View File

@ -120,6 +120,7 @@ import { AddEditFileAttributeDialogComponent } from './dialogs/add-edit-file-att
import { ConfirmDeleteFileAttributeDialogComponent } from './dialogs/confirm-delete-file-attribute-dialog/confirm-delete-file-attribute-dialog.component';
import { DocumentInfoDialogComponent } from './dialogs/document-info-dialog/document-info-dialog.component';
import { DocumentInfoComponent } from './components/document-info/document-info.component';
import { FileWorkloadComponent } from './components/file-workload/file-workload.component';
export function HttpLoaderFactory(httpClient: HttpClient) {
return new TranslateHttpLoader(httpClient, '/assets/i18n/', '.json');
@ -226,7 +227,8 @@ const matImports = [
AddEditFileAttributeDialogComponent,
ConfirmDeleteFileAttributeDialogComponent,
DocumentInfoDialogComponent,
DocumentInfoComponent
DocumentInfoComponent,
FileWorkloadComponent
],
imports: [
BrowserModule,

View File

@ -15,7 +15,7 @@
</div>
</div>
<div class="content" redactionHasScrollbar>
<div class="right-content" redactionHasScrollbar>
<div class="section">
1 2 3 4 1 2 3 4 1 2 3 4 1 2 3 4 1 2 3 4 1 2 3 4 1 2 3 4 1 2 3 4
</div>

View File

@ -11,8 +11,9 @@
@include inset-shadow;
}
.content {
max-height: calc(100% - 71px);
.right-content {
flex-direction: column;
@include scroll-bar;
overflow: hidden;

View File

@ -0,0 +1,133 @@
<div class="right-title heading" translate="file-preview.tabs.annotations.label">
<div>
<redaction-filter
(filtersChanged)="filtersChanged($event)"
[chevron]="true"
[filterTemplate]="annotationFilterTemplate"
[actionsTemplate]="annotationFilterActionTemplate"
[filters]="annotationFilters"
></redaction-filter>
</div>
</div>
<div class="right-content">
<div
#quickNavigation
(keydown)="preventKeyDefault($event)"
(keyup)="preventKeyDefault($event)"
[class.active-panel]="pagesPanelActive"
class="quick-navigation"
tabindex="0"
>
<div
class="jump"
[class.disabled]="!quickScrollFirstEnabled"
[matTooltip]="'file-preview.quick-nav.jump-first' | translate"
matTooltipPosition="above"
(click)="quickScrollFirstEnabled && scrollQuickNavFirst()"
>
<mat-icon svgIcon="red:nav-first"></mat-icon>
</div>
<div class="pages" (scroll)="computeQuickNavButtonsState()" id="pages">
<redaction-page-indicator
(pageSelected)="pageSelectedByClick($event)"
*ngFor="let pageNumber of displayedPages"
[active]="pageNumber === activeViewerPage"
[number]="pageNumber"
[viewedPages]="fileData.viewedPages"
>
</redaction-page-indicator>
</div>
<div
class="jump"
[class.disabled]="!quickScrollLastEnabled"
[matTooltip]="'file-preview.quick-nav.jump-last' | translate"
matTooltipPosition="above"
(click)="scrollQuickNavLast()"
>
<mat-icon svgIcon="red:nav-last"></mat-icon>
</div>
</div>
<div style="overflow: hidden; width: 100%;">
<div attr.anotation-page-header="{{ activeViewerPage }}" class="page-separator">
<span *ngIf="!!activeViewerPage" class="all-caps-label"
><span translate="page"></span> {{ activeViewerPage }} - {{ displayedAnnotations[activeViewerPage]?.annotations?.length || 0 }}
<span [translate]="displayedAnnotations[activeViewerPage]?.annotations?.length === 1 ? 'annotation' : 'annotations'"></span
></span>
</div>
<div
#annotationsElement
(keydown)="preventKeyDefault($event)"
(keyup)="preventKeyDefault($event)"
[class.active-panel]="!pagesPanelActive"
redactionHasScrollbar
class="annotations"
tabindex="1"
>
<div *ngIf="!displayedAnnotations[activeViewerPage]" class="heading-l no-annotations">
{{ 'file-preview.no-annotations-for-page' | translate }}
</div>
<div
(click)="annotationClicked(annotation)"
class="annotation-wrapper"
*ngFor="let annotation of displayedAnnotations[activeViewerPage]?.annotations"
attr.annotation-id="{{ annotation.id }}"
attr.annotation-page="{{ activeViewerPage }}"
[class.active]="annotationIsSelected(annotation)"
>
<div class="active-marker"></div>
<div class="annotation" [class.removed]="annotation.isChangeLogRemoved">
<redaction-hidden-action (action)="logAnnotation(annotation)" [requiredClicks]="2">
<div class="details">
<redaction-type-annotation-icon [annotation]="annotation"></redaction-type-annotation-icon>
<div class="flex-1">
<div>
<strong>{{ annotation.typeLabel | translate }}</strong>
</div>
<div *ngIf="annotation.dictionary && annotation.dictionary !== 'manual'">
<strong
><span>{{ annotation.descriptor | translate }}</span
>: </strong
>{{ annotation.dictionary | humanize: false }}
</div>
<div *ngIf="annotation.content && !annotation.isHint">
<strong><span translate="content"></span>: </strong>{{ annotation.content }}
</div>
</div>
<ng-container [ngTemplateOutlet]="annotationActionsTemplate" [ngTemplateOutletContext]="{ annotation: annotation }"> </ng-container>
<!-- <redaction-annotation-actions-->
<!-- (annotationsChanged)="annotationsChangedByReviewAction($event)"-->
<!-- [annotation]="annotation"-->
<!-- [canPerformAnnotationActions]="canPerformAnnotationActions"-->
<!-- [viewer]="activeViewer"-->
<!-- ></redaction-annotation-actions>-->
</div>
</redaction-hidden-action>
<redaction-comments [annotation]="annotation"></redaction-comments>
</div>
</div>
</div>
</div>
</div>
<ng-template #annotationFilterTemplate let-filter="filter">
<redaction-type-filter *ngIf="filter.topLevelFilter" [filter]="filter"></redaction-type-filter>
<ng-container *ngIf="!filter.topLevelFilter">
<redaction-dictionary-annotation-icon [dictionaryKey]="filter.key"></redaction-dictionary-annotation-icon>
{{ filter.key | humanize: false }}
</ng-container>
</ng-template>
<ng-template #annotationFilterActionTemplate let-filter="filter">
<ng-container *ngIf="filter.key === 'skipped'">
<redaction-circle-button
[icon]="hideSkipped ? 'red:visibility-off' : 'red:visibility'"
(action)="toggleSkipped.emit($event)"
class="skipped-toggle-button"
>
</redaction-circle-button>
</ng-container>
</ng-template>

View File

@ -0,0 +1,134 @@
@import '../../../assets/styles/red-variables';
@import '../../../assets/styles/red-mixins';
.right-content {
.no-annotations {
padding: 24px;
text-align: center;
}
.quick-navigation,
.annotations {
overflow-y: scroll;
outline: none;
&.active-panel {
background-color: #fafafa;
}
}
.quick-navigation {
border-right: 1px solid $separator;
min-width: 61px;
overflow: hidden;
display: flex;
flex-direction: column;
.jump {
min-height: 32px;
display: flex;
justify-content: center;
align-items: center;
cursor: pointer;
transition: background-color 0.25s;
&:not(.disabled):hover {
background-color: $grey-6;
}
mat-icon {
width: 16px;
height: 16px;
}
&.disabled {
cursor: default;
mat-icon {
opacity: 0.3;
}
}
}
.pages {
@include no-scroll-bar();
overflow: auto;
flex: 1;
}
}
.page-separator {
border-bottom: 1px solid $separator;
height: 32px;
box-sizing: border-box;
padding: 0 10px;
display: flex;
align-items: center;
background-color: $grey-6;
}
.annotations {
overflow: hidden;
width: 100%;
height: calc(100% - 32px);
.annotation-wrapper {
display: flex;
border-bottom: 1px solid $separator;
.active-marker {
min-width: 4px;
min-height: 100%;
}
&.active {
.active-marker {
background-color: $primary;
}
}
.annotation {
padding: 10px 21px 10px 6px;
font-size: 11px;
line-height: 14px;
cursor: pointer;
display: flex;
flex-direction: column;
&.removed {
text-decoration: line-through;
color: $grey-7;
}
.details {
display: flex;
position: relative;
}
redaction-type-annotation-icon {
margin-top: 6px;
margin-right: 10px;
}
}
&:hover {
background-color: #f9fafb;
::ng-deep .annotation-actions {
display: flex;
}
}
}
&:hover {
overflow-y: auto;
@include scroll-bar;
}
&.has-scrollbar:hover {
.annotation {
padding-right: 10px;
}
}
}
}

View File

@ -0,0 +1,296 @@
import { ChangeDetectorRef, Component, ElementRef, EventEmitter, HostListener, Input, Output, TemplateRef, ViewChild } from '@angular/core';
import { FilterModel } from '../filter/model/filter.model';
import { AnnotationWrapper } from '../../screens/file/model/annotation.wrapper';
import { AnnotationProcessingService } from '../../screens/file/service/annotation-processing.service';
import { MatDialogRef, MatDialogState } from '@angular/material/dialog';
import scrollIntoView from 'scroll-into-view-if-needed';
import { debounce } from '../../utils/debounce';
import { FileDataModel } from '../../screens/file/model/file-data.model';
const COMMAND_KEY_ARRAY = ['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown', 'Escape'];
const ALL_HOTKEY_ARRAY = ['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown'];
@Component({
selector: 'redaction-file-workload',
templateUrl: './file-workload.component.html',
styleUrls: ['./file-workload.component.scss']
})
export class FileWorkloadComponent {
public displayedAnnotations: { [key: number]: { annotations: AnnotationWrapper[] } } = {};
private _annotations: AnnotationWrapper[];
@Input()
set annotations(value: AnnotationWrapper[]) {
this._annotations = value;
// this.computeQuickNavButtonsState();
}
@Input() selectedAnnotations: AnnotationWrapper[];
@Input() activeViewerPage: number;
@Input() shouldDeselectAnnotationsOnPageChange: boolean;
@Input() dialogRef: MatDialogRef<any>;
@Input() annotationFilters: FilterModel[];
@Input() fileData: FileDataModel;
@Input() hideSkipped: boolean;
@Input() annotationActionsTemplate: TemplateRef<any>;
@Output() selectAnnotation = new EventEmitter<AnnotationWrapper>();
@Output() selectPage = new EventEmitter<number>();
@Output() toggleSkipped = new EventEmitter<any>();
public quickScrollFirstEnabled = false;
public quickScrollLastEnabled = false;
public displayedPages: number[] = [];
public pagesPanelActive = true;
@ViewChild('annotationsElement') private _annotationsElement: ElementRef;
@ViewChild('quickNavigation') private _quickNavigationElement: ElementRef;
constructor(private _changeDetectorRef: ChangeDetectorRef, private _annotationProcessingService: AnnotationProcessingService) {}
private get firstSelectedAnnotation() {
return this.selectedAnnotations?.length ? this.selectedAnnotations[0] : null;
}
private static _scrollToFirstElement(elements: HTMLElement[], mode: 'always' | 'if-needed' = 'if-needed') {
if (elements.length > 0) {
scrollIntoView(elements[0], {
behavior: 'smooth',
scrollMode: mode,
block: 'start',
inline: 'start'
});
}
}
public annotationIsSelected(annotation: AnnotationWrapper) {
return this.selectedAnnotations?.find((a) => a.id === annotation.id);
}
public logAnnotation(annotation: AnnotationWrapper) {
console.log(annotation);
}
@debounce(0)
public filtersChanged(filters: FilterModel[]) {
this.displayedAnnotations = this._annotationProcessingService.filterAndGroupAnnotations(this._annotations, filters);
this.displayedPages = Object.keys(this.displayedAnnotations).map((key) => Number(key));
this.computeQuickNavButtonsState();
this._changeDetectorRef.markForCheck();
}
public computeQuickNavButtonsState() {
setTimeout(() => {
const element: HTMLElement = this._quickNavigationElement.nativeElement.querySelector(`#pages`);
const { scrollTop, scrollHeight, clientHeight } = element;
this.quickScrollFirstEnabled = scrollTop !== 0;
this.quickScrollLastEnabled = scrollHeight !== scrollTop + clientHeight;
}, 0);
}
public annotationClicked(annotation: AnnotationWrapper) {
this.pagesPanelActive = false;
this.selectAnnotation.emit(annotation);
}
@HostListener('window:keyup', ['$event'])
handleKeyEvent($event: KeyboardEvent) {
if (!ALL_HOTKEY_ARRAY.includes($event.key) || this.dialogRef?.getState() === MatDialogState.OPEN) {
return;
}
if ($event.key === 'ArrowLeft') {
this.pagesPanelActive = true;
return;
}
if ($event.key === 'ArrowRight') {
this.pagesPanelActive = false;
// if we activated annotationsPanel - select first annotation from this page in case there is no
// selected annotation on this page
if (!this.pagesPanelActive) {
this._selectFirstAnnotationOnCurrentPageIfNecessary();
}
return;
}
if (!this.pagesPanelActive) {
this._navigateAnnotations($event);
} else {
this._navigatePages($event);
}
this._changeDetectorRef.detectChanges();
}
public scrollAnnotations() {
if (this.firstSelectedAnnotation?.pageNumber === this.activeViewerPage) {
return;
}
this.scrollAnnotationsToPage(this.activeViewerPage, 'always');
}
public scrollAnnotationsToPage(page: number, mode: 'always' | 'if-needed' = 'if-needed') {
const elements: any[] = this._annotationsElement.nativeElement.querySelectorAll(`div[anotation-page-header="${page}"]`);
FileWorkloadComponent._scrollToFirstElement(elements, mode);
}
@debounce()
public scrollToSelectedAnnotation() {
if (!this.selectedAnnotations || this.selectedAnnotations.length === 0) {
return;
}
const elements: any[] = this._annotationsElement.nativeElement.querySelectorAll(`div[annotation-id="${this.firstSelectedAnnotation.id}"].active`);
FileWorkloadComponent._scrollToFirstElement(elements);
}
public scrollQuickNavigation() {
let quickNavPageIndex = this.displayedPages.findIndex((p) => p >= this.activeViewerPage);
if (quickNavPageIndex === -1 || this.displayedPages[quickNavPageIndex] !== this.activeViewerPage) {
quickNavPageIndex = Math.max(0, quickNavPageIndex - 1);
}
this._scrollQuickNavigationToPage(this.displayedPages[quickNavPageIndex]);
}
public scrollQuickNavFirst() {
if (this.displayedPages.length > 0) {
this._scrollQuickNavigationToPage(this.displayedPages[0]);
}
}
public scrollQuickNavLast() {
if (this.displayedPages.length > 0) {
this._scrollQuickNavigationToPage(this.displayedPages[this.displayedPages.length - 1]);
}
}
public pageSelectedByClick($event: number) {
this.pagesPanelActive = true;
this.selectPage.emit($event);
}
public preventKeyDefault($event: KeyboardEvent) {
if (COMMAND_KEY_ARRAY.includes($event.key)) {
$event.preventDefault();
}
}
private _selectFirstAnnotationOnCurrentPageIfNecessary() {
if (
(!this.firstSelectedAnnotation || this.activeViewerPage !== this.firstSelectedAnnotation.pageNumber) &&
this.displayedPages.indexOf(this.activeViewerPage) >= 0
) {
this.selectAnnotation.emit(this.displayedAnnotations[this.activeViewerPage].annotations[0]);
}
}
private _navigateAnnotations($event: KeyboardEvent) {
if (!this.firstSelectedAnnotation || this.activeViewerPage !== this.firstSelectedAnnotation.pageNumber) {
const pageIdx = this.displayedPages.indexOf(this.activeViewerPage);
if (pageIdx !== -1) {
// Displayed page has annotations
this.selectAnnotation.emit(this.displayedAnnotations[this.activeViewerPage].annotations[0]);
} else {
// Displayed page doesn't have annotations
if ($event.key === 'ArrowDown') {
const nextPage = this._nextPageWithAnnotations();
this.shouldDeselectAnnotationsOnPageChange = false;
this.selectAnnotation.emit(this.displayedAnnotations[nextPage].annotations[0]);
} else {
const prevPage = this._prevPageWithAnnotations();
this.shouldDeselectAnnotationsOnPageChange = false;
const prevPageAnnotations = this.displayedAnnotations[prevPage].annotations;
this.selectAnnotation.emit(prevPageAnnotations[prevPageAnnotations.length - 1]);
}
}
} else {
const page = this.firstSelectedAnnotation.pageNumber;
const pageIdx = this.displayedPages.indexOf(page);
const annotationsOnPage = this.displayedAnnotations[page].annotations;
const idx = annotationsOnPage.findIndex((a) => a.id === this.firstSelectedAnnotation.id);
if ($event.key === 'ArrowDown') {
if (idx + 1 !== annotationsOnPage.length) {
// If not last item in page
this.selectAnnotation.emit(annotationsOnPage[idx + 1]);
} else if (pageIdx + 1 < this.displayedPages.length) {
// If not last page
const nextPageAnnotations = this.displayedAnnotations[this.displayedPages[pageIdx + 1]].annotations;
this.shouldDeselectAnnotationsOnPageChange = false;
this.selectAnnotation.emit(nextPageAnnotations[0]);
}
} else {
if (idx !== 0) {
// If not first item in page
this.selectAnnotation.emit(annotationsOnPage[idx - 1]);
} else if (pageIdx) {
// If not first page
const prevPageAnnotations = this.displayedAnnotations[this.displayedPages[pageIdx - 1]].annotations;
this.shouldDeselectAnnotationsOnPageChange = false;
this.selectAnnotation.emit(prevPageAnnotations[prevPageAnnotations.length - 1]);
}
}
}
}
private _navigatePages($event: KeyboardEvent) {
const pageIdx = this.displayedPages.indexOf(this.activeViewerPage);
if ($event.key === 'ArrowDown') {
if (pageIdx !== -1) {
// If active page has annotations
if (pageIdx !== this.displayedPages.length - 1) {
this.selectPage.emit(this.displayedPages[pageIdx + 1]);
}
} else {
// If active page doesn't have annotations
const nextPage = this._nextPageWithAnnotations();
if (nextPage) {
this.selectPage.emit(nextPage);
}
}
} else {
if (pageIdx !== -1) {
// If active page has annotations
if (pageIdx !== 0) {
this.selectPage.emit(this.displayedPages[pageIdx - 1]);
}
} else {
// If active page doesn't have annotations
const prevPage = this._prevPageWithAnnotations();
if (prevPage) {
this.selectPage.emit(prevPage);
}
}
}
}
private _nextPageWithAnnotations() {
let idx = 0;
for (const page of this.displayedPages) {
if (page > this.activeViewerPage) {
break;
}
++idx;
}
return idx < this.displayedPages.length ? this.displayedPages[idx] : null;
}
private _prevPageWithAnnotations() {
let idx = this.displayedPages.length - 1;
for (const page of this.displayedPages.reverse()) {
if (page < this.activeViewerPage) {
this.selectPage.emit(this.displayedPages[idx]);
this.scrollAnnotations();
break;
}
--idx;
}
return idx >= 0 ? this.displayedPages[idx] : null;
}
private _scrollQuickNavigationToPage(page: number) {
const elements: any[] = this._quickNavigationElement.nativeElement.querySelectorAll(`#quick-nav-page-${page}`);
FileWorkloadComponent._scrollToFirstElement(elements);
}
}

View File

@ -207,118 +207,21 @@
<redaction-document-info *ngIf="viewReady && viewDocumentInfo" [file]="fileData.fileStatus" (closeDocumentInfoView)="viewDocumentInfo = false">
</redaction-document-info>
<div class="right-title heading" translate="file-preview.tabs.annotations.label">
<div>
<redaction-filter
(filtersChanged)="filtersChanged($event)"
[chevron]="true"
[filterTemplate]="annotationFilterTemplate"
[actionsTemplate]="annotationFilterActionTemplate"
[filters]="annotationFilters"
></redaction-filter>
</div>
</div>
<div class="right-content">
<div
#quickNavigation
(keydown)="preventKeyDefault($event)"
(keyup)="preventKeyDefault($event)"
[class.active-panel]="pagesPanelActive"
class="quick-navigation"
tabindex="0"
>
<div
class="jump"
[class.disabled]="!quickScrollFirstEnabled"
[matTooltip]="'file-preview.quick-nav.jump-first' | translate"
matTooltipPosition="above"
(click)="quickScrollFirstEnabled && scrollQuickNavFirst()"
>
<mat-icon svgIcon="red:nav-first"></mat-icon>
</div>
<div class="pages" (scroll)="computeQuickNavButtonsState()" id="pages">
<redaction-page-indicator
(pageSelected)="pageSelectedByClick($event)"
*ngFor="let pageNumber of displayedPages"
[active]="pageNumber === activeViewerPage"
[number]="pageNumber"
[viewedPages]="fileData.viewedPages"
>
</redaction-page-indicator>
</div>
<div
class="jump"
[class.disabled]="!quickScrollLastEnabled"
[matTooltip]="'file-preview.quick-nav.jump-last' | translate"
matTooltipPosition="above"
(click)="scrollQuickNavLast()"
>
<mat-icon svgIcon="red:nav-last"></mat-icon>
</div>
</div>
<div style="overflow: hidden; width: 100%;">
<div attr.anotation-page-header="{{ activeViewerPage }}" class="page-separator">
<span *ngIf="!!activeViewerPage" class="all-caps-label"
><span translate="page"></span> {{ activeViewerPage }} - {{ displayedAnnotations[activeViewerPage]?.annotations?.length || 0 }}
<span [translate]="displayedAnnotations[activeViewerPage]?.annotations?.length === 1 ? 'annotation' : 'annotations'"></span
></span>
</div>
<div
#annotationsElement
(keydown)="preventKeyDefault($event)"
(keyup)="preventKeyDefault($event)"
[class.active-panel]="!pagesPanelActive"
redactionHasScrollbar
class="annotations"
tabindex="1"
>
<div *ngIf="!displayedAnnotations[activeViewerPage]" class="heading-l no-annotations">
{{ 'file-preview.no-annotations-for-page' | translate }}
</div>
<div
(click)="annotationClicked(annotation)"
class="annotation-wrapper"
*ngFor="let annotation of displayedAnnotations[activeViewerPage]?.annotations"
attr.annotation-id="{{ annotation.id }}"
attr.annotation-page="{{ activeViewerPage }}"
[class.active]="annotationIsSelected(annotation)"
>
<div class="active-marker"></div>
<div class="annotation" [class.removed]="annotation.isChangeLogRemoved">
<redaction-hidden-action (action)="logAnnotation(annotation)" [requiredClicks]="2">
<div class="details">
<redaction-type-annotation-icon [annotation]="annotation"></redaction-type-annotation-icon>
<div class="flex-1">
<div>
<strong>{{ annotation.typeLabel | translate }}</strong>
</div>
<div *ngIf="annotation.dictionary && annotation.dictionary !== 'manual'">
<strong
><span>{{ annotation.descriptor | translate }}</span
>: </strong
>{{ annotation.dictionary | humanize: false }}
</div>
<div *ngIf="annotation.content && !annotation.isHint">
<strong><span translate="content"></span>: </strong>{{ annotation.content }}
</div>
</div>
<redaction-annotation-actions
(annotationsChanged)="annotationsChangedByReviewAction($event)"
[annotation]="annotation"
[canPerformAnnotationActions]="canPerformAnnotationActions"
[viewer]="activeViewer"
></redaction-annotation-actions>
</div>
</redaction-hidden-action>
<redaction-comments [annotation]="annotation"></redaction-comments>
</div>
</div>
</div>
</div>
</div>
<redaction-file-workload
#fileWorkloadComponent
[annotations]="annotations"
[selectedAnnotations]="selectedAnnotations"
[activeViewerPage]="activeViewerPage"
[shouldDeselectAnnotationsOnPageChange]="shouldDeselectAnnotationsOnPageChange"
[dialogRef]="dialogRef"
[annotationFilters]="annotationFilters"
[fileData]="fileData"
[hideSkipped]="hideSkipped"
[annotationActionsTemplate]="annotationActionsTemplate"
(selectAnnotation)="selectAnnotation($event)"
(selectPage)="selectPage($event)"
(toggleSkipped)="toggleSkipped($event)"
></redaction-file-workload>
</div>
</div>
</section>
@ -330,17 +233,11 @@
</div>
</redaction-full-page-loading-indicator>
<ng-template #annotationFilterTemplate let-filter="filter">
<redaction-type-filter *ngIf="filter.topLevelFilter" [filter]="filter"></redaction-type-filter>
<ng-container *ngIf="!filter.topLevelFilter">
<redaction-dictionary-annotation-icon [dictionaryKey]="filter.key"></redaction-dictionary-annotation-icon>
{{ filter.key | humanize: false }}
</ng-container>
</ng-template>
<ng-template #annotationFilterActionTemplate let-filter="filter">
<ng-container *ngIf="filter.key === 'skipped'">
<redaction-circle-button [icon]="hideSkipped ? 'red:visibility-off' : 'red:visibility'" (action)="toggleSkipped($event)" class="skipped-toggle-button">
</redaction-circle-button>
</ng-container>
<ng-template #annotationActionsTemplate let-annotation="annotation">
<redaction-annotation-actions
(annotationsChanged)="annotationsChangedByReviewAction($event)"
[annotation]="annotation"
[canPerformAnnotationActions]="canPerformAnnotationActions"
[viewer]="activeViewer"
></redaction-annotation-actions>
</ng-template>

View File

@ -31,7 +31,7 @@
min-width: 350px;
position: relative;
::ng-deep.right-title {
::ng-deep .right-title {
height: 70px;
display: flex;
border-bottom: 1px solid $separator;
@ -40,142 +40,143 @@
padding: 0 24px;
}
.right-content {
::ng-deep .right-content {
height: calc(100% - 72px);
box-sizing: border-box;
display: flex;
.quick-navigation,
.annotations {
overflow-y: scroll;
outline: none;
&.active-panel {
background-color: #fafafa;
}
}
.quick-navigation {
border-right: 1px solid $separator;
min-width: 61px;
overflow: hidden;
display: flex;
flex-direction: column;
.jump {
min-height: 32px;
display: flex;
justify-content: center;
align-items: center;
cursor: pointer;
transition: background-color 0.25s;
&:not(.disabled):hover {
background-color: $grey-6;
}
mat-icon {
width: 16px;
height: 16px;
}
&.disabled {
cursor: default;
mat-icon {
opacity: 0.3;
}
}
}
.pages {
@include no-scroll-bar();
overflow: auto;
flex: 1;
}
}
.page-separator {
border-bottom: 1px solid $separator;
height: 32px;
box-sizing: border-box;
padding: 0 10px;
display: flex;
align-items: center;
background-color: $grey-6;
}
.annotations {
overflow: hidden;
width: 100%;
height: calc(100% - 32px);
.annotation-wrapper {
display: flex;
border-bottom: 1px solid $separator;
.active-marker {
min-width: 4px;
min-height: 100%;
}
&.active {
.active-marker {
background-color: $primary;
}
}
.annotation {
padding: 10px 21px 10px 6px;
font-size: 11px;
line-height: 14px;
cursor: pointer;
display: flex;
flex-direction: column;
&.removed {
text-decoration: line-through;
color: $grey-7;
}
.details {
display: flex;
position: relative;
}
redaction-type-annotation-icon {
margin-top: 6px;
margin-right: 10px;
}
}
&:hover {
background-color: #f9fafb;
::ng-deep .annotation-actions {
display: flex;
}
}
}
&:hover {
overflow-y: auto;
@include scroll-bar;
}
&.has-scrollbar:hover {
.annotation {
padding-right: 10px;
}
}
}
}
//
// .quick-navigation,
// .annotations {
// overflow-y: scroll;
// outline: none;
//
// &.active-panel {
// background-color: #fafafa;
// }
// }
//
// .quick-navigation {
// border-right: 1px solid $separator;
// min-width: 61px;
// overflow: hidden;
// display: flex;
// flex-direction: column;
//
// .jump {
// min-height: 32px;
// display: flex;
// justify-content: center;
// align-items: center;
// cursor: pointer;
// transition: background-color 0.25s;
//
// &:not(.disabled):hover {
// background-color: $grey-6;
// }
//
// mat-icon {
// width: 16px;
// height: 16px;
// }
//
// &.disabled {
// cursor: default;
//
// mat-icon {
// opacity: 0.3;
// }
// }
// }
//
// .pages {
// @include no-scroll-bar();
// overflow: auto;
// flex: 1;
// }
// }
//
// .page-separator {
// border-bottom: 1px solid $separator;
// height: 32px;
// box-sizing: border-box;
// padding: 0 10px;
// display: flex;
// align-items: center;
// background-color: $grey-6;
// }
//
// .annotations {
// overflow: hidden;
// width: 100%;
// height: calc(100% - 32px);
//
// .annotation-wrapper {
// display: flex;
// border-bottom: 1px solid $separator;
//
// .active-marker {
// min-width: 4px;
// min-height: 100%;
// }
//
// &.active {
// .active-marker {
// background-color: $primary;
// }
// }
//
// .annotation {
// padding: 10px 21px 10px 6px;
// font-size: 11px;
// line-height: 14px;
// cursor: pointer;
// display: flex;
// flex-direction: column;
//
// &.removed {
// text-decoration: line-through;
// color: $grey-7;
// }
//
// .details {
// display: flex;
// position: relative;
// }
//
// redaction-type-annotation-icon {
// margin-top: 6px;
// margin-right: 10px;
// }
// }
//
// &:hover {
// background-color: #f9fafb;
//
// ::ng-deep .annotation-actions {
// display: flex;
// }
// }
// }
//
// &:hover {
// overflow-y: auto;
// @include scroll-bar;
// }
//
// &.has-scrollbar:hover {
// .annotation {
// padding-right: 10px;
// }
// }
// }
//}
}
.no-annotations {
padding: 24px;
text-align: center;
}
//.no-annotations {
// padding: 24px;
// text-align: center;
//}
.assign-actions-wrapper {
display: flex;

View File

@ -30,9 +30,9 @@ import { FileManagementControllerService, StatusControllerService } from '@redac
import { PdfViewerDataService } from '../service/pdf-viewer-data.service';
import { download } from '../../../utils/file-download-utils';
import { ViewMode } from '../model/view-mode';
import { FileWorkloadComponent } from '../../../components/file-workload/file-workload.component';
const COMMAND_KEY_ARRAY = ['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown', 'Escape'];
const ALL_HOTKEY_ARRAY = ['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown', 'Escape', 'F', 'f'];
const ALL_HOTKEY_ARRAY = ['Escape', 'F', 'f'];
@Component({
selector: 'redaction-file-preview-screen',
@ -40,6 +40,33 @@ const ALL_HOTKEY_ARRAY = ['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown', 'Es
styleUrls: ['./file-preview-screen.component.scss']
})
export class FilePreviewScreenComponent implements OnInit, OnDestroy {
public dialogRef: MatDialogRef<any>;
public viewMode: ViewMode = 'STANDARD';
public fullScreen = false;
public editingReviewer = false;
public reviewerForm: FormGroup;
public shouldDeselectAnnotationsOnPageChange = true;
public analysisProgressInSeconds = 0;
public analysisProgress: number;
public analysisInterval: number;
fileData: FileDataModel;
fileId: string;
annotationData: AnnotationData;
selectedAnnotations: AnnotationWrapper[];
viewReady = false;
annotationFilters: FilterModel[];
loadingMessage: string;
canPerformAnnotationActions: boolean;
filesAutoUpdateTimer: Subscription;
fileReanalysedSubscription: Subscription;
hideSkipped = false;
public viewDocumentInfo = false;
private projectId: string;
private _instance: WebViewerInstance;
@ViewChild('fileWorkloadComponent') private _workloadComponent: FileWorkloadComponent;
@ViewChild(PdfViewerComponent) private _viewerComponent: PdfViewerComponent;
constructor(
public readonly appStateService: AppStateService,
public readonly permissionsService: PermissionsService,
@ -72,7 +99,7 @@ export class FilePreviewScreenComponent implements OnInit, OnDestroy {
});
}
get annotations() {
get annotations(): AnnotationWrapper[] {
return this.annotationData ? this.annotationData.visibleAnnotations : [];
}
@ -92,62 +119,16 @@ export class FilePreviewScreenComponent implements OnInit, OnDestroy {
return this.fileData?.redactionChangeLog?.redactionLogEntry?.length > 0;
}
get ignoreColor() {
return this.appStateService.getDictionaryColor('skipped');
}
get displayData() {
return this.fileData?.fileData;
}
private projectId: string;
private _instance: WebViewerInstance;
private _dialogRef: MatDialogRef<any>;
public viewMode: ViewMode = 'STANDARD';
public fullScreen = false;
public editingReviewer = false;
public reviewerForm: FormGroup;
public shouldDeselectAnnotationsOnPageChange = true;
public analysisProgressInSeconds = 0;
public analysisProgress: number;
public analysisInterval: number;
public quickScrollFirstEnabled = false;
public quickScrollLastEnabled = false;
public displayedPages: number[] = [];
get indeterminateMode() {
return (
this.analysisProgress > 100 || this.appStateService.activeFile.analysisDuration < 3 * 1000 // it takes longer than usual - switch to indeterminate
); // on less than 3 seconds show indeterminate
}
@ViewChild(PdfViewerComponent) private _viewerComponent: PdfViewerComponent;
@ViewChild('annotationsElement') private _annotationsElement: ElementRef;
@ViewChild('quickNavigation') private _quickNavigationElement: ElementRef;
fileData: FileDataModel;
fileId: string;
annotationData: AnnotationData;
displayedAnnotations: { [key: number]: { annotations: AnnotationWrapper[] } } = {};
selectedAnnotations: AnnotationWrapper[];
pagesPanelActive = true;
viewReady = false;
annotationFilters: FilterModel[];
loadingMessage: string;
canPerformAnnotationActions: boolean;
filesAutoUpdateTimer: Subscription;
fileReanalysedSubscription: Subscription;
hideSkipped = false;
public viewDocumentInfo = false;
updateViewMode() {
const allAnnotations = this._instance.annotManager.getAnnotationsList();
@ -183,15 +164,11 @@ export class FilePreviewScreenComponent implements OnInit, OnDestroy {
break;
}
this._rebuildFilters();
this.rebuildFilters();
this._updateCanPerformActions();
}
private _updateCanPerformActions() {
this.canPerformAnnotationActions = this.permissionsService.canPerformAnnotationActions() && this.viewMode === 'STANDARD';
}
ngOnInit(): void {
document.documentElement.addEventListener('fullscreenchange', (event) => {
if (!document.fullscreenElement) {
@ -227,32 +204,7 @@ export class FilePreviewScreenComponent implements OnInit, OnDestroy {
this.fileReanalysedSubscription.unsubscribe();
}
private _loadFileData(performUpdate: boolean = false) {
return this._fileDownloadService.loadActiveFileData().pipe(
tap((fileDataModel) => {
if (fileDataModel.fileStatus.isWorkable) {
if (performUpdate) {
this.fileData.redactionLog = fileDataModel.redactionLog;
this.fileData.redactionChangeLog = fileDataModel.redactionChangeLog;
this.fileData.fileStatus = fileDataModel.fileStatus;
this.fileData.manualRedactions = fileDataModel.manualRedactions;
this._rebuildFilters(true);
} else {
this.fileData = fileDataModel;
this._rebuildFilters();
}
} else {
if (fileDataModel.fileStatus.isError) {
this._router.navigate(['/ui/projects/' + this.appStateService.activeProjectId]);
} else {
this.loadingMessage = 'file-preview.reanalyse-file';
}
}
})
);
}
private _rebuildFilters(deletePreviousAnnotations: boolean = false) {
public rebuildFilters(deletePreviousAnnotations: boolean = false) {
const startTime = new Date().getTime();
if (deletePreviousAnnotations) {
this.activeViewer.annotManager.deleteAnnotations(this.activeViewer.annotManager.getAnnotationsList(), {
@ -270,7 +222,7 @@ export class FilePreviewScreenComponent implements OnInit, OnDestroy {
);
const annotationFilters = this._annotationProcessingService.getAnnotationFilter(this.annotations);
this.annotationFilters = processFilters(this.annotationFilters, annotationFilters);
this.filtersChanged(this.annotationFilters);
this._workloadComponent.filtersChanged(this.annotationFilters);
console.log('[REDACTION] Process time: ' + (new Date().getTime() - processStartTime) + 'ms');
console.log(
'[REDACTION] Annotation Redraw and filter rebuild time: ' +
@ -283,27 +235,22 @@ export class FilePreviewScreenComponent implements OnInit, OnDestroy {
handleAnnotationSelected(annotationIds: string[]) {
this.selectedAnnotations = annotationIds.map((annotationId) => this.annotations.find((annotationWrapper) => annotationWrapper.id === annotationId));
this.scrollToSelectedAnnotation();
this._workloadComponent.scrollToSelectedAnnotation();
this._changeDetectorRef.detectChanges();
}
annotationClicked(annotation: AnnotationWrapper) {
this.pagesPanelActive = false;
this.selectAnnotation(annotation);
}
selectAnnotation(annotation: AnnotationWrapper) {
this._viewerComponent.selectAnnotation(annotation);
}
selectPage(pageNumber: number) {
this._viewerComponent.navigateToPage(pageNumber);
this._scrollAnnotationsToPage(pageNumber, 'always');
this._workloadComponent.scrollAnnotationsToPage(pageNumber, 'always');
}
openManualAnnotationDialog($event: ManualRedactionEntryWrapper) {
this.ngZone.run(() => {
this._dialogRef = this._dialogService.openManualAnnotationDialog($event, async (response: ManualAnnotationResponse) => {
this.dialogRef = this._dialogService.openManualAnnotationDialog($event, async (response: ManualAnnotationResponse) => {
if (response?.annotationId) {
const annotation = this.activeViewer.annotManager.getAnnotationById(response.manualRedactionEntryWrapper.rectId);
this.activeViewer.annotManager.deleteAnnotation(annotation);
@ -317,81 +264,6 @@ export class FilePreviewScreenComponent implements OnInit, OnDestroy {
});
}
annotationIsSelected(annotation: AnnotationWrapper) {
return this.selectedAnnotations?.find((a) => a.id === annotation.id);
}
private get firstSelectedAnnotation() {
return this.selectedAnnotations?.length ? this.selectedAnnotations[0] : null;
}
private get lastSelectedAnnotation() {
return this.selectedAnnotations?.length ? this.selectedAnnotations[this.selectedAnnotations.length - 1] : null;
}
@debounce()
private _scrollViews() {
this._scrollQuickNavigation();
this._scrollAnnotations();
}
@debounce()
private scrollToSelectedAnnotation() {
if (!this.selectedAnnotations || this.selectedAnnotations.length === 0) {
return;
}
const elements: any[] = this._annotationsElement.nativeElement.querySelectorAll(`div[annotation-id="${this.firstSelectedAnnotation.id}"].active`);
this._scrollToFirstElement(elements);
}
private _scrollQuickNavigationToPage(page: number) {
const elements: any[] = this._quickNavigationElement.nativeElement.querySelectorAll(`#quick-nav-page-${page}`);
this._scrollToFirstElement(elements);
}
private _scrollQuickNavigation() {
let quickNavPageIndex = this.displayedPages.findIndex((p) => p >= this.activeViewerPage);
if (quickNavPageIndex === -1 || this.displayedPages[quickNavPageIndex] !== this.activeViewerPage) {
quickNavPageIndex = Math.max(0, quickNavPageIndex - 1);
}
this._scrollQuickNavigationToPage(this.displayedPages[quickNavPageIndex]);
}
public scrollQuickNavFirst() {
if (this.displayedPages.length > 0) {
this._scrollQuickNavigationToPage(this.displayedPages[0]);
}
}
public scrollQuickNavLast() {
if (this.displayedPages.length > 0) {
this._scrollQuickNavigationToPage(this.displayedPages[this.displayedPages.length - 1]);
}
}
private _scrollAnnotations() {
if (this.firstSelectedAnnotation?.pageNumber === this.activeViewerPage) {
return;
}
this._scrollAnnotationsToPage(this.activeViewerPage, 'always');
}
private _scrollAnnotationsToPage(page: number, mode: 'always' | 'if-needed' = 'if-needed') {
const elements: any[] = this._annotationsElement.nativeElement.querySelectorAll(`div[anotation-page-header="${page}"]`);
this._scrollToFirstElement(elements, mode);
}
private _scrollToFirstElement(elements: HTMLElement[], mode: 'always' | 'if-needed' = 'if-needed') {
if (elements.length > 0) {
scrollIntoView(elements[0], {
behavior: 'smooth',
scrollMode: mode,
block: 'start',
inline: 'start'
});
}
}
public toggleFullScreen() {
this.fullScreen = !this.fullScreen;
if (this.fullScreen) {
@ -403,7 +275,7 @@ export class FilePreviewScreenComponent implements OnInit, OnDestroy {
@HostListener('window:keyup', ['$event'])
handleKeyEvent($event: KeyboardEvent) {
if (!ALL_HOTKEY_ARRAY.includes($event.key) || this._dialogRef?.getState() === MatDialogState.OPEN) {
if (!ALL_HOTKEY_ARRAY.includes($event.key) || this.dialogRef?.getState() === MatDialogState.OPEN) {
return;
}
@ -421,145 +293,9 @@ export class FilePreviewScreenComponent implements OnInit, OnDestroy {
return;
}
if ($event.key === 'ArrowLeft') {
this.pagesPanelActive = true;
}
if ($event.key === 'ArrowRight') {
this.pagesPanelActive = false;
// if we activated annotationsPanel - select first annotation from this page in case there is no
// selected annotation on this page
if (!this.pagesPanelActive) {
this._selectFirstAnnotationOnCurrentPageIfNecessary();
}
}
if ($event.key === 'ArrowLeft' || $event.key === 'ArrowRight') {
return;
}
if (!this.pagesPanelActive) {
this._navigateAnnotations($event);
} else {
this._navigatePages($event);
}
this._changeDetectorRef.detectChanges();
}
private _selectFirstAnnotationOnCurrentPageIfNecessary() {
if (
(!this.firstSelectedAnnotation || this.activeViewerPage !== this.firstSelectedAnnotation.pageNumber) &&
this.displayedPages.indexOf(this.activeViewerPage) >= 0
) {
this.selectAnnotation(this.displayedAnnotations[this.activeViewerPage].annotations[0]);
}
}
private _navigateAnnotations($event: KeyboardEvent) {
if (!this.firstSelectedAnnotation || this.activeViewerPage !== this.firstSelectedAnnotation.pageNumber) {
const pageIdx = this.displayedPages.indexOf(this.activeViewerPage);
if (pageIdx !== -1) {
// Displayed page has annotations
this.selectAnnotation(this.displayedAnnotations[this.activeViewerPage].annotations[0]);
} else {
// Displayed page doesn't have annotations
if ($event.key === 'ArrowDown') {
const nextPage = this._nextPageWithAnnotations();
this.shouldDeselectAnnotationsOnPageChange = false;
this.selectAnnotation(this.displayedAnnotations[nextPage].annotations[0]);
} else {
const prevPage = this._prevPageWithAnnotations();
this.shouldDeselectAnnotationsOnPageChange = false;
const prevPageAnnotations = this.displayedAnnotations[prevPage].annotations;
this.selectAnnotation(prevPageAnnotations[prevPageAnnotations.length - 1]);
}
}
} else {
const page = this.firstSelectedAnnotation.pageNumber;
const pageIdx = this.displayedPages.indexOf(page);
const annotationsOnPage = this.displayedAnnotations[page].annotations;
const idx = annotationsOnPage.findIndex((a) => a.id === this.firstSelectedAnnotation.id);
if ($event.key === 'ArrowDown') {
if (idx + 1 !== annotationsOnPage.length) {
// If not last item in page
this.selectAnnotation(annotationsOnPage[idx + 1]);
} else if (pageIdx + 1 < this.displayedPages.length) {
// If not last page
const nextPageAnnotations = this.displayedAnnotations[this.displayedPages[pageIdx + 1]].annotations;
this.shouldDeselectAnnotationsOnPageChange = false;
this.selectAnnotation(nextPageAnnotations[0]);
}
} else {
if (idx !== 0) {
// If not first item in page
this.selectAnnotation(annotationsOnPage[idx - 1]);
} else if (pageIdx) {
// If not first page
const prevPageAnnotations = this.displayedAnnotations[this.displayedPages[pageIdx - 1]].annotations;
this.shouldDeselectAnnotationsOnPageChange = false;
this.selectAnnotation(prevPageAnnotations[prevPageAnnotations.length - 1]);
}
}
}
}
private _navigatePages($event: KeyboardEvent) {
const pageIdx = this.displayedPages.indexOf(this.activeViewerPage);
if ($event.key === 'ArrowDown') {
if (pageIdx !== -1) {
// If active page has annotations
if (pageIdx !== this.displayedPages.length - 1) {
this.selectPage(this.displayedPages[pageIdx + 1]);
}
} else {
// If active page doesn't have annotations
const nextPage = this._nextPageWithAnnotations();
if (nextPage) {
this.selectPage(nextPage);
}
}
} else {
if (pageIdx !== -1) {
// If active page has annotations
if (pageIdx !== 0) {
this.selectPage(this.displayedPages[pageIdx - 1]);
}
} else {
// If active page doesn't have annotations
const prevPage = this._prevPageWithAnnotations();
if (prevPage) {
this.selectPage(prevPage);
}
}
}
}
private _nextPageWithAnnotations() {
let idx = 0;
for (const page of this.displayedPages) {
if (page > this.activeViewerPage) {
break;
}
++idx;
}
return idx < this.displayedPages.length ? this.displayedPages[idx] : null;
}
private _prevPageWithAnnotations() {
let idx = this.displayedPages.length - 1;
for (const page of this.displayedPages.reverse()) {
if (page < this.activeViewerPage) {
this.selectPage(this.displayedPages[idx]);
this._scrollAnnotations();
break;
}
--idx;
}
return idx >= 0 ? this.displayedPages[idx] : null;
}
viewerPageChanged($event: any) {
if (typeof $event === 'number') {
this._scrollViews();
@ -590,74 +326,10 @@ export class FilePreviewScreenComponent implements OnInit, OnDestroy {
}
}
filtersChanged(filters: FilterModel[]) {
this.displayedAnnotations = this._annotationProcessingService.filterAndGroupAnnotations(this.annotations, filters);
this.displayedPages = Object.keys(this.displayedAnnotations).map((key) => Number(key));
this.computeQuickNavButtonsState();
this._changeDetectorRef.markForCheck();
}
preventKeyDefault($event: KeyboardEvent) {
if (COMMAND_KEY_ARRAY.includes($event.key)) {
$event.preventDefault();
}
}
public computeQuickNavButtonsState() {
const element: HTMLElement = this._quickNavigationElement.nativeElement.querySelector(`#pages`);
const { scrollTop, scrollHeight, clientHeight } = element;
this.quickScrollFirstEnabled = scrollTop !== 0;
this.quickScrollLastEnabled = scrollHeight !== scrollTop + clientHeight;
}
private _cleanupAndRedrawManualAnnotations() {
this._fileDownloadService.loadActiveFileManualAnnotations().subscribe((manualRedactions) => {
this.fileData.manualRedactions = manualRedactions;
this._rebuildFilters();
this._annotationDrawService.drawAnnotations(this._instance, this.annotationData.allAnnotations, this.hideSkipped);
});
}
private async _cleanupAndRedrawManualAnnotationsForEntirePage(page: number) {
const currentPageAnnotations = this.annotations.filter((a) => a.pageNumber === page);
const currentPageAnnotationIds = currentPageAnnotations.map((a) => a.id);
this.fileData.fileStatus = await this.appStateService.reloadActiveFile();
this._fileDownloadService.loadActiveFileManualAnnotations().subscribe((manualRedactions) => {
this.fileData.manualRedactions = manualRedactions;
this._rebuildFilters();
if (this.viewMode === 'STANDARD') {
currentPageAnnotationIds.forEach((id) => {
this._findAndDeleteAnnotation(id);
});
const newPageAnnotations = this.annotations.filter((item) => item.pageNumber === page);
this._handleDeltaAnnotationFilters(currentPageAnnotations, newPageAnnotations);
this._annotationDrawService.drawAnnotations(this._instance, newPageAnnotations, this.hideSkipped);
}
});
}
private _handleDeltaAnnotationFilters(currentPageAnnotations: AnnotationWrapper[], newPageAnnotations: AnnotationWrapper[]) {
const hasAnyFilterSet = this.annotationFilters.find((f) => f.checked || f.indeterminate);
if (hasAnyFilterSet) {
const oldPageSpecificFilters = this._annotationProcessingService.getAnnotationFilter(currentPageAnnotations);
const newPageSpecificFilters = this._annotationProcessingService.getAnnotationFilter(newPageAnnotations);
handleFilterDelta(oldPageSpecificFilters, newPageSpecificFilters, this.annotationFilters);
this.filtersChanged(this.annotationFilters);
}
}
async annotationsChangedByReviewAction(annotation: AnnotationWrapper) {
await this._cleanupAndRedrawManualAnnotationsForEntirePage(annotation.pageNumber);
}
private _findAndDeleteAnnotation(id: string) {
const viewerAnnotation = this.activeViewer.annotManager.getAnnotationById(id);
if (viewerAnnotation) {
this.activeViewer.annotManager.deleteAnnotation(viewerAnnotation, { imported: true, force: true });
}
}
async fileActionPerformed(action: string) {
switch (action) {
case 'delete':
@ -682,31 +354,6 @@ export class FilePreviewScreenComponent implements OnInit, OnDestroy {
}
}
private _startAnalysisTimer() {
this._stopAnalysisTimer();
if (this.appStateService.activeFile.analysisDuration > 0) {
this.analysisProgress = 0;
this.analysisProgressInSeconds = 0;
this.analysisInterval = setInterval(() => {
this.analysisProgressInSeconds += 1;
this.analysisProgress = (this.analysisProgressInSeconds * 100) / (this.appStateService.activeFile.analysisDuration / 1000);
}, 1000);
} else {
this.analysisInterval = 0;
this.analysisProgress = 0;
this.analysisProgressInSeconds = 0;
}
}
private _stopAnalysisTimer() {
if (this.analysisInterval) {
clearInterval(this.analysisInterval);
this.analysisInterval = 0;
}
}
public async assignToMe() {
await this._fileActionService.assignToMe(this.fileData.fileStatus, async () => {
await this.appStateService.reloadActiveFile();
@ -732,21 +379,6 @@ export class FilePreviewScreenComponent implements OnInit, OnDestroy {
this.reviewerForm.setValue({ reviewer: this.appStateService.activeFile.currentReviewer });
}
pageSelectedByClick($event: number) {
this.pagesPanelActive = true;
this.selectPage($event);
}
/* Get the documentElement (<html>) to display the page in fullscreen */
/* View in fullscreen */
private _openFullScreen() {
const documentElement = document.documentElement;
if (documentElement.requestFullscreen) {
documentElement.requestFullscreen();
}
}
/* Close fullscreen */
closeFullScreen() {
if (document.exitFullscreen) {
@ -787,8 +419,118 @@ export class FilePreviewScreenComponent implements OnInit, OnDestroy {
window.open(`/html-debug/${this.projectId}/${this.fileId}`, '_blank');
}
logAnnotation(annotation: AnnotationWrapper) {
console.log(annotation);
private _updateCanPerformActions() {
this.canPerformAnnotationActions = this.permissionsService.canPerformAnnotationActions() && this.viewMode === 'STANDARD';
}
private _loadFileData(performUpdate: boolean = false) {
return this._fileDownloadService.loadActiveFileData().pipe(
tap((fileDataModel) => {
if (fileDataModel.fileStatus.isWorkable) {
if (performUpdate) {
this.fileData.redactionLog = fileDataModel.redactionLog;
this.fileData.redactionChangeLog = fileDataModel.redactionChangeLog;
this.fileData.fileStatus = fileDataModel.fileStatus;
this.fileData.manualRedactions = fileDataModel.manualRedactions;
this.rebuildFilters(true);
} else {
this.fileData = fileDataModel;
this.rebuildFilters();
}
} else {
if (fileDataModel.fileStatus.isError) {
this._router.navigate(['/ui/projects/' + this.appStateService.activeProjectId]);
} else {
this.loadingMessage = 'file-preview.reanalyse-file';
}
}
})
);
}
@debounce()
private _scrollViews() {
this._workloadComponent.scrollQuickNavigation();
this._workloadComponent.scrollAnnotations();
}
/* Get the documentElement (<html>) to display the page in fullscreen */
private _cleanupAndRedrawManualAnnotations() {
this._fileDownloadService.loadActiveFileManualAnnotations().subscribe((manualRedactions) => {
this.fileData.manualRedactions = manualRedactions;
this.rebuildFilters();
this._annotationDrawService.drawAnnotations(this._instance, this.annotationData.allAnnotations, this.hideSkipped);
});
}
private async _cleanupAndRedrawManualAnnotationsForEntirePage(page: number) {
const currentPageAnnotations = this.annotations.filter((a) => a.pageNumber === page);
const currentPageAnnotationIds = currentPageAnnotations.map((a) => a.id);
this.fileData.fileStatus = await this.appStateService.reloadActiveFile();
this._fileDownloadService.loadActiveFileManualAnnotations().subscribe((manualRedactions) => {
this.fileData.manualRedactions = manualRedactions;
this.rebuildFilters();
if (this.viewMode === 'STANDARD') {
currentPageAnnotationIds.forEach((id) => {
this._findAndDeleteAnnotation(id);
});
const newPageAnnotations = this.annotations.filter((item) => item.pageNumber === page);
this._handleDeltaAnnotationFilters(currentPageAnnotations, newPageAnnotations);
this._annotationDrawService.drawAnnotations(this._instance, newPageAnnotations, this.hideSkipped);
}
});
}
private _handleDeltaAnnotationFilters(currentPageAnnotations: AnnotationWrapper[], newPageAnnotations: AnnotationWrapper[]) {
const hasAnyFilterSet = this.annotationFilters.find((f) => f.checked || f.indeterminate);
if (hasAnyFilterSet) {
const oldPageSpecificFilters = this._annotationProcessingService.getAnnotationFilter(currentPageAnnotations);
const newPageSpecificFilters = this._annotationProcessingService.getAnnotationFilter(newPageAnnotations);
handleFilterDelta(oldPageSpecificFilters, newPageSpecificFilters, this.annotationFilters);
this._workloadComponent.filtersChanged(this.annotationFilters);
}
}
private _findAndDeleteAnnotation(id: string) {
const viewerAnnotation = this.activeViewer.annotManager.getAnnotationById(id);
if (viewerAnnotation) {
this.activeViewer.annotManager.deleteAnnotation(viewerAnnotation, { imported: true, force: true });
}
}
private _startAnalysisTimer() {
this._stopAnalysisTimer();
if (this.appStateService.activeFile.analysisDuration > 0) {
this.analysisProgress = 0;
this.analysisProgressInSeconds = 0;
this.analysisInterval = setInterval(() => {
this.analysisProgressInSeconds += 1;
this.analysisProgress = (this.analysisProgressInSeconds * 100) / (this.appStateService.activeFile.analysisDuration / 1000);
}, 1000);
} else {
this.analysisInterval = 0;
this.analysisProgress = 0;
this.analysisProgressInSeconds = 0;
}
}
private _stopAnalysisTimer() {
if (this.analysisInterval) {
clearInterval(this.analysisInterval);
this.analysisInterval = 0;
}
}
/* View in fullscreen */
private _openFullScreen() {
const documentElement = document.documentElement;
if (documentElement.requestFullscreen) {
documentElement.requestFullscreen();
}
}
private _handleIgnoreAnnotationsDrawing() {