RED-10034: File workload improvementes - WIP

This commit is contained in:
Adina Țeudan 2024-09-30 18:30:30 +03:00
parent 8e9f6e19bf
commit 671c44438d
2 changed files with 72 additions and 59 deletions

View File

@ -32,11 +32,11 @@
</ng-template>
</ng-container>
@if (displayedAnnotations$ | async; as annotations) {
@if (filteredAnnotations$ | async; as annotations) {
<div class="right-content">
<ng-container *ngIf="!isDocumine">
<redaction-readonly-banner
*ngIf="showAnalysisDisabledBanner; else readOnlyBanner"
*ngIf="showAnalysisDisabledBanner(); else readOnlyBanner"
[customTranslation]="translations.analysisDisabled"
></redaction-readonly-banner>
<ng-template #readOnlyBanner>
@ -112,7 +112,7 @@
></iqser-circle-button>
<span
[translateParams]="{ page: pdf.currentPage(), count: activeAnnotations.length }"
[translateParams]="{ page: pdf.currentPage(), count: activeAnnotations().length }"
[translate]="'page'"
class="all-caps-label"
></span>
@ -200,7 +200,7 @@
<redaction-annotations-list
(pagesPanelActive)="pagesPanelActive = $event"
[annotations]="annotations.get(pdf.currentPage())"
[annotations]="annotationsList$ | async"
></redaction-annotations-list>
</div>
}
@ -224,7 +224,7 @@
</ng-template>
<ng-template #documineHeader>
<span [translate]="'annotations'" [attr.help-mode-key]="'annotations_list'"></span>
<span [attr.help-mode-key]="'annotations_list'" [translate]="'annotations'"></span>
<ng-container *ngTemplateOutlet="annotationsFilter"></ng-container>
</ng-template>

View File

@ -9,6 +9,7 @@ import {
OnDestroy,
OnInit,
TemplateRef,
untracked,
viewChild,
} from '@angular/core';
import { MatDialog } from '@angular/material/dialog';
@ -89,23 +90,30 @@ const ALL_HOTKEY_ARRAY = ['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown'];
],
})
export class FileWorkloadComponent extends AutoUnsubscribe implements OnInit, OnDestroy {
private readonly _annotationsElement = viewChild<ElementRef>('annotationsElement');
private readonly _quickNavigationElement = viewChild<ElementRef>('quickNavigation');
readonly multiSelectTemplate = viewChild<TemplateRef<any>>('multiSelect');
readonly #isIqserDevMode = this._userPreferenceService.isIqserDevMode;
readonly annotationsList$: Observable<ListItem<AnnotationWrapper>[]>;
readonly allPages = computed(() => Array.from({ length: this.state.file()?.numberOfPages }, (_x, i) => i + 1));
protected readonly iconButtonTypes = IconButtonTypes;
protected readonly circleButtonTypes = CircleButtonTypes;
protected readonly displayedAnnotations$: Observable<Map<number, ListItem<AnnotationWrapper>[]>>;
protected readonly filteredAnnotations$: Observable<Map<number, ListItem<AnnotationWrapper>[]>>;
protected readonly title = computed(() =>
this.viewModeService.isEarmarks() ? _('file-preview.tabs.highlights.label') : _('file-preview.tabs.annotations.label'),
);
protected readonly currentPageIsExcluded = computed(() => this.state.file().excludedPages.includes(this.pdf.currentPage()));
protected readonly translations = workloadTranslations;
protected readonly isDocumine = getConfig().IS_DOCUMINE;
readonly showAnalysisDisabledBanner = computed(() => {
const file = this.state.file();
return this.isDocumine && file.excludedFromAutomaticAnalysis && file.workflowStatus !== WorkflowFileStatuses.APPROVED;
});
protected displayedAnnotations = new Map<number, AnnotationWrapper[]>();
readonly activeAnnotations = computed(() => this.displayedAnnotations.get(this.pdf.currentPage()) || []);
protected displayedPages: number[] = [];
protected pagesPanelActive = true;
protected enabledFilters = [];
private readonly _annotationsElement = viewChild<ElementRef>('annotationsElement');
private readonly _quickNavigationElement = viewChild<ElementRef>('quickNavigation');
readonly #isIqserDevMode = this._userPreferenceService.isIqserDevMode;
#displayedPagesChanged = false;
constructor(
@ -129,10 +137,9 @@ export class FileWorkloadComponent extends AutoUnsubscribe implements OnInit, On
) {
super();
// TODO: ngOnDetach is not called here, so we need to unsubscribe manually
this.addActiveScreenSubscription = this.pdf.currentPage$.subscribe(pageNumber => {
effect(() => {
this._scrollViews();
this.scrollAnnotationsToPage(pageNumber, 'always');
this.scrollAnnotationsToPage(this.pdf.currentPage(), 'always');
});
this.addActiveScreenSubscription = this.listingService.selected$.subscribe(annotationIds => {
@ -146,7 +153,11 @@ export class FileWorkloadComponent extends AutoUnsubscribe implements OnInit, On
this.handleKeyEvent($event);
});
this.displayedAnnotations$ = this._displayedAnnotations$;
this.filteredAnnotations$ = this._filteredAnnotations$;
this.annotationsList$ = combineLatest([this.filteredAnnotations$, this.pdf.currentPage$]).pipe(
map(([annotations, page]) => annotations.get(page)),
);
effect(() => {
if (this.multiSelectService.inactive()) {
@ -169,20 +180,11 @@ export class FileWorkloadComponent extends AutoUnsubscribe implements OnInit, On
);
}
get activeAnnotations(): AnnotationWrapper[] {
return this.displayedAnnotations.get(this.pdf.currentPage()) || [];
}
get showAnalysisDisabledBanner() {
const file = this.state.file();
return this.isDocumine && file.excludedFromAutomaticAnalysis && file.workflowStatus !== WorkflowFileStatuses.APPROVED;
}
private get _firstSelectedAnnotation() {
return this.listingService.selected.length ? this.listingService.selected[0] : null;
}
private get _displayedAnnotations$(): Observable<Map<number, ListItem<AnnotationWrapper>[]>> {
private get _filteredAnnotations$(): Observable<Map<number, ListItem<AnnotationWrapper>[]>> {
const primary$ = this.filterService.getFilterModels$('primaryFilters');
const secondary$ = this.filterService.getFilterModels$('secondaryFilters');
@ -205,10 +207,6 @@ export class FileWorkloadComponent extends AutoUnsubscribe implements OnInit, On
);
}
get #allPages() {
return Array.from({ length: this.state.file()?.numberOfPages }, (_x, i) => i + 1);
}
private static _scrollToFirstElement(elements: HTMLElement[], mode: 'always' | 'if-needed' = 'if-needed') {
if (elements.length > 0) {
scrollIntoView(elements[0], {
@ -222,12 +220,13 @@ export class FileWorkloadComponent extends AutoUnsubscribe implements OnInit, On
ngOnInit(): void {
setTimeout(() => {
const showExcludePages = getLocalStorageDataByFileId(this.state.file()?.id, 'show-exclude-pages') ?? false;
const file = untracked(this.state.file);
const showExcludePages = getLocalStorageDataByFileId(file?.id, 'show-exclude-pages') ?? false;
if (showExcludePages) {
this.excludedPagesService.show();
}
const showDocumentInfo = getLocalStorageDataByFileId(this.state.file()?.id, 'show-document-info') ?? false;
const showDocumentInfo = getLocalStorageDataByFileId(file?.id, 'show-document-info') ?? false;
if (showDocumentInfo) {
this.documentInfoService.show();
}
@ -235,16 +234,20 @@ export class FileWorkloadComponent extends AutoUnsubscribe implements OnInit, On
}
selectAllOnActivePage() {
this.listingService.selectAnnotations(this.activeAnnotations);
const activeAnnotations = untracked(this.activeAnnotations);
this.listingService.selectAnnotations(activeAnnotations);
}
deselectAllOnActivePage(): void {
this.listingService.deselect(this.activeAnnotations);
this.annotationManager.deselect(this.activeAnnotations);
const activeAnnotations = untracked(this.activeAnnotations);
this.listingService.deselect(activeAnnotations);
this.annotationManager.deselect(activeAnnotations);
}
@HostListener('window:keyup', ['$event'])
handleKeyEvent($event: KeyboardEvent): void {
const multiSelectServiceInactive = untracked(this.multiSelectService.inactive);
if (
!ALL_HOTKEY_ARRAY.includes($event.key) ||
this._dialog.openDialogs.length ||
@ -264,7 +267,7 @@ export class FileWorkloadComponent extends AutoUnsubscribe implements OnInit, On
// if we activated annotationsPanel -
// select first annotation from this page in case there is no
// selected annotation on this page and not in multi select mode
if (!this.pagesPanelActive && this.multiSelectService.inactive()) {
if (!this.pagesPanelActive && multiSelectServiceInactive) {
this._documentViewer.clearSelection();
this.#selectFirstAnnotationOnCurrentPageIfNecessary();
}
@ -275,7 +278,7 @@ export class FileWorkloadComponent extends AutoUnsubscribe implements OnInit, On
if (!this.pagesPanelActive) {
// Disable annotation navigation in multi select mode
// => TODO: maybe implement selection on enter?
if (this.multiSelectService.inactive()) {
if (multiSelectServiceInactive) {
this.navigateAnnotations($event);
}
} else {
@ -286,7 +289,7 @@ export class FileWorkloadComponent extends AutoUnsubscribe implements OnInit, On
}
scrollAnnotations(): void {
const currentPage = this.pdf.currentPage();
const currentPage = untracked(this.pdf.currentPage);
if (this._firstSelectedAnnotation?.pageNumber === currentPage) {
return;
}
@ -294,27 +297,27 @@ export class FileWorkloadComponent extends AutoUnsubscribe implements OnInit, On
}
scrollAnnotationsToPage(page: number, mode: 'always' | 'if-needed' = 'if-needed'): void {
if (this._annotationsElement()) {
const elements: HTMLElement[] = this._annotationsElement().nativeElement.querySelectorAll(
`div[anotation-page-header="${page}"]`,
);
const annotationsElement = untracked(this._annotationsElement);
if (annotationsElement) {
const elements: HTMLElement[] = annotationsElement.nativeElement.querySelectorAll(`div[anotation-page-header="${page}"]`);
FileWorkloadComponent._scrollToFirstElement(elements, mode);
}
}
@Debounce()
scrollToSelectedAnnotation(): void {
if (this.listingService.selected.length === 0 || !this._annotationsElement()) {
const annotationsElement = untracked(this._annotationsElement);
if (this.listingService.selected.length === 0 || annotationsElement) {
return;
}
const elements: HTMLElement[] = this._annotationsElement().nativeElement.querySelectorAll(
const elements: HTMLElement[] = annotationsElement.nativeElement.querySelectorAll(
`[annotation-id="${this._firstSelectedAnnotation?.id}"]`,
);
FileWorkloadComponent._scrollToFirstElement(elements);
}
scrollQuickNavigation(): void {
const currentPage = this.pdf.currentPage();
const currentPage = untracked(this.pdf.currentPage);
let quickNavPageIndex = this.displayedPages.findIndex(p => p >= currentPage);
if (quickNavPageIndex === -1 || this.displayedPages[quickNavPageIndex] !== currentPage) {
quickNavPageIndex = Math.max(0, quickNavPageIndex - 1);
@ -327,7 +330,8 @@ export class FileWorkloadComponent extends AutoUnsubscribe implements OnInit, On
}
scrollQuickNavLast() {
this.pdf.navigateTo(this.state.file().numberOfPages);
const file = untracked(this.state.file);
this.pdf.navigateTo(file.numberOfPages);
}
preventKeyDefault($event: KeyboardEvent): void {
@ -345,11 +349,12 @@ export class FileWorkloadComponent extends AutoUnsubscribe implements OnInit, On
}
navigateAnnotations($event: KeyboardEvent) {
const currentPage = this.pdf.currentPage();
const currentPage = untracked(this.pdf.currentPage);
const activeAnnotations = untracked(this.activeAnnotations);
if (!this._firstSelectedAnnotation || currentPage !== this._firstSelectedAnnotation.pageNumber) {
if (this.displayedPages.indexOf(currentPage) !== -1) {
// Displayed page has annotations
return this.listingService.selectAnnotations(this.activeAnnotations ? this.activeAnnotations[0] : null);
return this.listingService.selectAnnotations(activeAnnotations ? activeAnnotations[0] : null);
}
// Displayed page doesn't have annotations
if ($event.key === 'ArrowDown') {
@ -422,14 +427,16 @@ export class FileWorkloadComponent extends AutoUnsubscribe implements OnInit, On
secondary: INestedFilter[] = [],
componentReferenceIds: string[],
): Map<number, AnnotationWrapper[]> {
const onlyPageWithAnnotations = this.viewModeService.onlyPagesWithAnnotations();
const onlyPageWithAnnotations = untracked(this.viewModeService.onlyPagesWithAnnotations);
const isRedacted = untracked(this.viewModeService.isRedacted);
const allPages = untracked(this.allPages);
if (!primary || primary.length === 0) {
const pages = onlyPageWithAnnotations ? [] : this.#allPages;
const pages = onlyPageWithAnnotations ? [] : allPages;
this.#setDisplayedPages(pages);
return;
}
if (this.viewModeService.isRedacted()) {
if (isRedacted) {
annotations = annotations.filter(a => !a.isRemoved);
}
@ -447,7 +454,7 @@ export class FileWorkloadComponent extends AutoUnsubscribe implements OnInit, On
this.enabledFilters = this.filterService.enabledFlatFilters;
if (this.enabledFilters.some(f => f.id === 'pages-without-annotations')) {
if (this.enabledFilters.length === 1 && !onlyPageWithAnnotations) {
const pages = this.#allPages.filter(page => !pagesThatDisplayAnnotations.includes(page));
const pages = allPages.filter(page => !pagesThatDisplayAnnotations.includes(page));
this.#setDisplayedPages(pages);
} else {
this.#setDisplayedPages([]);
@ -456,7 +463,7 @@ export class FileWorkloadComponent extends AutoUnsubscribe implements OnInit, On
} else if (this.enabledFilters.length || onlyPageWithAnnotations || componentReferenceIds) {
this.#setDisplayedPages(pagesThatDisplayAnnotations);
} else {
this.#setDisplayedPages(this.#allPages);
this.#setDisplayedPages(allPages);
}
this.displayedPages.sort((a, b) => a - b);
@ -464,18 +471,20 @@ export class FileWorkloadComponent extends AutoUnsubscribe implements OnInit, On
}
#selectFirstAnnotationOnCurrentPageIfNecessary() {
const currentPage = this.pdf.currentPage();
const currentPage = untracked(this.pdf.currentPage);
const activeAnnotations = untracked(this.activeAnnotations);
if (
(!this._firstSelectedAnnotation || currentPage !== this._firstSelectedAnnotation.pageNumber) &&
this.displayedPages.indexOf(currentPage) >= 0 &&
this.activeAnnotations.length > 0
activeAnnotations.length > 0
) {
this.listingService.selectAnnotations(this.activeAnnotations[0]);
this.listingService.selectAnnotations(activeAnnotations[0]);
}
}
#navigatePages($event: KeyboardEvent) {
const pageIdx = this.displayedPages.indexOf(this.pdf.currentPage());
const currentPage = untracked(this.pdf.currentPage);
const pageIdx = this.displayedPages.indexOf(currentPage);
if ($event.key !== 'ArrowDown') {
if (pageIdx === -1) {
@ -507,9 +516,10 @@ export class FileWorkloadComponent extends AutoUnsubscribe implements OnInit, On
}
#nextPageWithAnnotations() {
const currentPage = untracked(this.pdf.currentPage);
let idx = 0;
for (const page of this.displayedPages) {
if (page > this.pdf.currentPage() && this.displayedAnnotations.get(page)) {
if (page > currentPage && this.displayedAnnotations.get(page)) {
break;
}
++idx;
@ -518,10 +528,11 @@ export class FileWorkloadComponent extends AutoUnsubscribe implements OnInit, On
}
#prevPageWithAnnotations() {
const currentPage = untracked(this.pdf.currentPage);
let idx = this.displayedPages.length - 1;
const reverseDisplayedPages = [...this.displayedPages].reverse();
for (const page of reverseDisplayedPages) {
if (page < this.pdf.currentPage() && this.displayedAnnotations.get(page)) {
if (page < currentPage && this.displayedAnnotations.get(page)) {
break;
}
--idx;
@ -530,8 +541,9 @@ export class FileWorkloadComponent extends AutoUnsubscribe implements OnInit, On
}
#scrollQuickNavigationToPage(page: number) {
if (this._quickNavigationElement()) {
const elements: HTMLElement[] = this._quickNavigationElement().nativeElement.querySelectorAll(`#quick-nav-page-${page}`);
const quickNavigationElement = untracked(this._quickNavigationElement);
if (quickNavigationElement) {
const elements: HTMLElement[] = quickNavigationElement.nativeElement.querySelectorAll(`#quick-nav-page-${page}`);
FileWorkloadComponent._scrollToFirstElement(elements);
}
}
@ -552,7 +564,8 @@ export class FileWorkloadComponent extends AutoUnsubscribe implements OnInit, On
}
#scrollToFirstAnnotationPage(annotations: Map<number, ListItem<AnnotationWrapper>[]>) {
if (this.isDocumine && annotations.size && this.#displayedPagesChanged && !this.displayedPages.includes(this.pdf.currentPage())) {
const currentPage = untracked(this.pdf.currentPage);
if (this.isDocumine && annotations.size && this.#displayedPagesChanged && !this.displayedPages.includes(currentPage)) {
const page = annotations.keys().next().value;
this.pdf.navigateTo(page);
}