From 5636656f767a6a0bde70e77ae0853247665627e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adina=20=C8=9Aeudan?= Date: Thu, 25 Mar 2021 01:22:34 +0200 Subject: [PATCH] File workload separate component --- apps/red-ui/src/app/app.module.ts | 4 +- .../document-info.component.html | 2 +- .../document-info.component.scss | 5 +- .../file-workload.component.html | 133 +++++ .../file-workload.component.scss | 134 +++++ .../file-workload/file-workload.component.ts | 296 ++++++++++ .../file-preview-screen.component.html | 147 +---- .../file-preview-screen.component.scss | 263 ++++----- .../file-preview-screen.component.ts | 556 +++++------------- 9 files changed, 873 insertions(+), 667 deletions(-) create mode 100644 apps/red-ui/src/app/components/file-workload/file-workload.component.html create mode 100644 apps/red-ui/src/app/components/file-workload/file-workload.component.scss create mode 100644 apps/red-ui/src/app/components/file-workload/file-workload.component.ts diff --git a/apps/red-ui/src/app/app.module.ts b/apps/red-ui/src/app/app.module.ts index afdd131e0..f56c0f802 100644 --- a/apps/red-ui/src/app/app.module.ts +++ b/apps/red-ui/src/app/app.module.ts @@ -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, diff --git a/apps/red-ui/src/app/components/document-info/document-info.component.html b/apps/red-ui/src/app/components/document-info/document-info.component.html index 95cbe235a..69608ee31 100644 --- a/apps/red-ui/src/app/components/document-info/document-info.component.html +++ b/apps/red-ui/src/app/components/document-info/document-info.component.html @@ -15,7 +15,7 @@ -
+
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
diff --git a/apps/red-ui/src/app/components/document-info/document-info.component.scss b/apps/red-ui/src/app/components/document-info/document-info.component.scss index 5f677f05a..ee9825e39 100644 --- a/apps/red-ui/src/app/components/document-info/document-info.component.scss +++ b/apps/red-ui/src/app/components/document-info/document-info.component.scss @@ -11,8 +11,9 @@ @include inset-shadow; } -.content { - max-height: calc(100% - 71px); +.right-content { + flex-direction: column; + @include scroll-bar; overflow: hidden; diff --git a/apps/red-ui/src/app/components/file-workload/file-workload.component.html b/apps/red-ui/src/app/components/file-workload/file-workload.component.html new file mode 100644 index 000000000..54f1c8512 --- /dev/null +++ b/apps/red-ui/src/app/components/file-workload/file-workload.component.html @@ -0,0 +1,133 @@ +
+
+ +
+
+
+
+
+ +
+
+ + +
+
+ +
+
+ +
+
+ {{ activeViewerPage }} - {{ displayedAnnotations[activeViewerPage]?.annotations?.length || 0 }} + +
+ +
+
+ {{ 'file-preview.no-annotations-for-page' | translate }} +
+ +
+
+
+ +
+ +
+
+ {{ annotation.typeLabel | translate }} +
+
+ {{ annotation.descriptor | translate }}: {{ annotation.dictionary | humanize: false }} +
+
+ : {{ annotation.content }} +
+
+ + + + + + + + +
+
+ +
+
+
+
+
+ + + + + + {{ filter.key | humanize: false }} + + + + + + + + + diff --git a/apps/red-ui/src/app/components/file-workload/file-workload.component.scss b/apps/red-ui/src/app/components/file-workload/file-workload.component.scss new file mode 100644 index 000000000..149a4fb93 --- /dev/null +++ b/apps/red-ui/src/app/components/file-workload/file-workload.component.scss @@ -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; + } + } + } +} diff --git a/apps/red-ui/src/app/components/file-workload/file-workload.component.ts b/apps/red-ui/src/app/components/file-workload/file-workload.component.ts new file mode 100644 index 000000000..6d261bdfa --- /dev/null +++ b/apps/red-ui/src/app/components/file-workload/file-workload.component.ts @@ -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; + @Input() annotationFilters: FilterModel[]; + @Input() fileData: FileDataModel; + @Input() hideSkipped: boolean; + @Input() annotationActionsTemplate: TemplateRef; + + @Output() selectAnnotation = new EventEmitter(); + @Output() selectPage = new EventEmitter(); + @Output() toggleSkipped = new EventEmitter(); + + 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); + } +} diff --git a/apps/red-ui/src/app/screens/file/file-preview-screen/file-preview-screen.component.html b/apps/red-ui/src/app/screens/file/file-preview-screen/file-preview-screen.component.html index 243552329..2599fb9dd 100644 --- a/apps/red-ui/src/app/screens/file/file-preview-screen/file-preview-screen.component.html +++ b/apps/red-ui/src/app/screens/file/file-preview-screen/file-preview-screen.component.html @@ -207,118 +207,21 @@ -
-
- -
-
-
-
-
- -
-
- - -
-
- -
-
- -
-
- {{ activeViewerPage }} - {{ displayedAnnotations[activeViewerPage]?.annotations?.length || 0 }} - -
- -
-
- {{ 'file-preview.no-annotations-for-page' | translate }} -
- -
-
-
- -
- -
-
- {{ annotation.typeLabel | translate }} -
-
- {{ annotation.descriptor | translate }}: {{ annotation.dictionary | humanize: false }} -
-
- : {{ annotation.content }} -
-
- -
-
- -
-
-
-
-
+
@@ -330,17 +233,11 @@ - - - - - {{ filter.key | humanize: false }} - - - - - - - - + + diff --git a/apps/red-ui/src/app/screens/file/file-preview-screen/file-preview-screen.component.scss b/apps/red-ui/src/app/screens/file/file-preview-screen/file-preview-screen.component.scss index 3e6be62b9..00691a2d4 100644 --- a/apps/red-ui/src/app/screens/file/file-preview-screen/file-preview-screen.component.scss +++ b/apps/red-ui/src/app/screens/file/file-preview-screen/file-preview-screen.component.scss @@ -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; diff --git a/apps/red-ui/src/app/screens/file/file-preview-screen/file-preview-screen.component.ts b/apps/red-ui/src/app/screens/file/file-preview-screen/file-preview-screen.component.ts index a792fe1c7..4eb298fc9 100644 --- a/apps/red-ui/src/app/screens/file/file-preview-screen/file-preview-screen.component.ts +++ b/apps/red-ui/src/app/screens/file/file-preview-screen/file-preview-screen.component.ts @@ -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; + 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; - - 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 () 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 () 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() {