RED-6829: remove other observables

This commit is contained in:
Dan Percic 2023-06-11 01:11:22 +03:00
parent 58976f24d8
commit e89d317fa0
28 changed files with 202 additions and 264 deletions

View File

@ -1,5 +1,4 @@
export interface ListItem<T> { export interface ListItem<T> {
item: T; item: T;
isSelected: boolean; isSelected: boolean;
multiSelectActive: boolean;
} }

View File

@ -62,7 +62,7 @@
<iqser-circle-button <iqser-circle-button
(action)="annotationActionsService.convertHighlights(annotations)" (action)="annotationActionsService.convertHighlights(annotations)"
*ngIf="(viewModeService.viewMode$ | async) === 'TEXT_HIGHLIGHTS'" *ngIf="viewModeService.isEarmarks()"
[tooltipPosition]="tooltipPosition" [tooltipPosition]="tooltipPosition"
[tooltip]="'annotation-actions.convert-highlights.label' | translate" [tooltip]="'annotation-actions.convert-highlights.label' | translate"
[type]="buttonType" [type]="buttonType"
@ -71,7 +71,7 @@
<iqser-circle-button <iqser-circle-button
(action)="annotationActionsService.removeHighlights(annotations)" (action)="annotationActionsService.removeHighlights(annotations)"
*ngIf="(viewModeService.viewMode$ | async) === 'TEXT_HIGHLIGHTS'" *ngIf="viewModeService.isEarmarks()"
[tooltipPosition]="tooltipPosition" [tooltipPosition]="tooltipPosition"
[tooltip]="'annotation-actions.remove-highlights.label' | translate" [tooltip]="'annotation-actions.remove-highlights.label' | translate"
[type]="buttonType" [type]="buttonType"

View File

@ -1,11 +1,11 @@
import { Component, Input, OnChanges } from '@angular/core'; import { Component, inject, Input, OnChanges } from '@angular/core';
import { AnnotationWrapper } from '@models/file/annotation.wrapper'; import { AnnotationWrapper } from '@models/file/annotation.wrapper';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
import { annotationChangesTranslations } from '@translations/annotation-changes-translations'; import { annotationChangesTranslations } from '@translations/annotation-changes-translations';
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker'; import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
import { KeysOf } from '@iqser/common-ui'; import { KeysOf } from '@iqser/common-ui';
import { AnnotationsListingService } from '../../services/annotations-listing.service';
import { ListItem } from '@models/file/list-item'; import { ListItem } from '@models/file/list-item';
import { MultiSelectService } from '../../services/multi-select.service';
interface Engine { interface Engine {
readonly icon: string; readonly icon: string;
@ -48,7 +48,8 @@ export class AnnotationDetailsComponent implements OnChanges {
changesTooltip: string; changesTooltip: string;
noSelection: boolean; noSelection: boolean;
constructor(private readonly _translateService: TranslateService, private readonly _listingService: AnnotationsListingService) {} private readonly _translateService = inject(TranslateService);
private readonly _multiSelectService = inject(MultiSelectService);
getChangesTooltip(): string | undefined { getChangesTooltip(): string | undefined {
const changes = changesProperties.filter(key => this.annotation.item[key]); const changes = changesProperties.filter(key => this.annotation.item[key]);
@ -65,7 +66,7 @@ export class AnnotationDetailsComponent implements OnChanges {
ngOnChanges() { ngOnChanges() {
this.engines = this.#extractEngines(this.annotation.item).filter(engine => engine.show); this.engines = this.#extractEngines(this.annotation.item).filter(engine => engine.show);
this.changesTooltip = this.getChangesTooltip(); this.changesTooltip = this.getChangesTooltip();
this.noSelection = !this.annotation.isSelected || !this.annotation.multiSelectActive; this.noSelection = !this.annotation.isSelected || this._multiSelectService.inactive();
} }
#extractEngines(annotation: AnnotationWrapper): Engine[] { #extractEngines(annotation: AnnotationWrapper): Engine[] {

View File

@ -20,11 +20,11 @@
{{ annotation.item.comments.length }} {{ annotation.item.comments.length }}
</div> </div>
<div *ngIf="!annotation.multiSelectActive" class="actions"> <div *ngIf="_multiSelectService.inactive()" class="actions">
<redaction-annotation-actions <redaction-annotation-actions
[annotations]="[annotation.item]" [annotations]="[annotation.item]"
[attr.help-mode-key]="actionsHelpModeKey" [attr.help-mode-key]="actionsHelpModeKey"
[canPerformAnnotationActions]="pdfProxyService.canPerformActions()" [canPerformAnnotationActions]="_pdfProxyService.canPerformActions()"
></redaction-annotation-actions> ></redaction-annotation-actions>
</div> </div>
</div> </div>

View File

@ -1,8 +1,9 @@
import { Component, HostBinding, Input, OnChanges } from '@angular/core'; import { Component, HostBinding, inject, Input, OnChanges } from '@angular/core';
import { AnnotationWrapper } from '@models/file/annotation.wrapper'; import { AnnotationWrapper } from '@models/file/annotation.wrapper';
import { PdfProxyService } from '../../services/pdf-proxy.service'; import { PdfProxyService } from '../../services/pdf-proxy.service';
import { ActionsHelpModeKeys } from '../../utils/constants'; import { ActionsHelpModeKeys } from '../../utils/constants';
import { ListItem } from '@models/file/list-item'; import { ListItem } from '@models/file/list-item';
import { MultiSelectService } from '../../services/multi-select.service';
@Component({ @Component({
selector: 'redaction-annotation-wrapper', selector: 'redaction-annotation-wrapper',
@ -15,8 +16,8 @@ export class AnnotationWrapperComponent implements OnChanges {
@HostBinding('attr.annotation-id') annotationId: string; @HostBinding('attr.annotation-id') annotationId: string;
@HostBinding('class.active') active = false; @HostBinding('class.active') active = false;
actionsHelpModeKey?: string; actionsHelpModeKey?: string;
protected readonly _pdfProxyService = inject(PdfProxyService);
constructor(readonly pdfProxyService: PdfProxyService) {} protected readonly _multiSelectService = inject(MultiSelectService);
ngOnChanges() { ngOnChanges() {
this.annotationId = this.annotation.item.id; this.annotationId = this.annotation.item.id;

View File

@ -1,11 +1,10 @@
import { ChangeDetectorRef, Component, ElementRef, EventEmitter, Input, OnChanges, Output } from '@angular/core'; import { ChangeDetectorRef, Component, computed, ElementRef, EventEmitter, Input, OnChanges, Output } from '@angular/core';
import { AnnotationWrapper } from '@models/file/annotation.wrapper'; import { AnnotationWrapper } from '@models/file/annotation.wrapper';
import { FilterService, HasScrollbarDirective, IqserEventTarget } from '@iqser/common-ui'; import { FilterService, HasScrollbarDirective, IqserEventTarget } from '@iqser/common-ui';
import { MultiSelectService } from '../../services/multi-select.service'; import { MultiSelectService } from '../../services/multi-select.service';
import { AnnotationReferencesService } from '../../services/annotation-references.service'; import { AnnotationReferencesService } from '../../services/annotation-references.service';
import { UserPreferenceService } from '@users/user-preference.service'; import { UserPreferenceService } from '@users/user-preference.service';
import { ViewModeService } from '../../services/view-mode.service'; import { ViewModeService } from '../../services/view-mode.service';
import { BehaviorSubject } from 'rxjs';
import { EarmarkGroup } from '@red/domain'; import { EarmarkGroup } from '@red/domain';
import { REDAnnotationManager } from '../../../pdf-viewer/services/annotation-manager.service'; import { REDAnnotationManager } from '../../../pdf-viewer/services/annotation-manager.service';
import { AnnotationsListingService } from '../../services/annotations-listing.service'; import { AnnotationsListingService } from '../../services/annotations-listing.service';
@ -18,10 +17,14 @@ import { ListItem } from '@models/file/list-item';
}) })
export class AnnotationsListComponent extends HasScrollbarDirective implements OnChanges { export class AnnotationsListComponent extends HasScrollbarDirective implements OnChanges {
@Input() annotations: ListItem<AnnotationWrapper>[]; @Input() annotations: ListItem<AnnotationWrapper>[];
@Output() readonly pagesPanelActive = new EventEmitter<boolean>(); @Output() readonly pagesPanelActive = new EventEmitter<boolean>();
readonly earmarkGroups$ = new BehaviorSubject<EarmarkGroup[]>([]); readonly #earmarkGroups = computed(() => {
if (this._viewModeService.isEarmarks()) {
return this.#getEarmarksGroups();
}
return [] as EarmarkGroup[];
});
constructor( constructor(
protected readonly _elementRef: ElementRef, protected readonly _elementRef: ElementRef,
@ -37,16 +40,6 @@ export class AnnotationsListComponent extends HasScrollbarDirective implements O
super(_elementRef, _changeDetector); super(_elementRef, _changeDetector);
} }
ngOnChanges(): void {
if (this._viewModeService.isEarmarks()) {
this._updateEarmarksGroups();
}
setTimeout(() => {
super.ngOnChanges();
}, 0);
}
annotationClicked(annotation: AnnotationWrapper, $event: MouseEvent): void { annotationClicked(annotation: AnnotationWrapper, $event: MouseEvent): void {
if (this._userPreferenceService.areDevFeaturesEnabled) { if (this._userPreferenceService.areDevFeaturesEnabled) {
console.log('Selected Annotation:', annotation); console.log('Selected Annotation:', annotation);
@ -78,16 +71,18 @@ export class AnnotationsListComponent extends HasScrollbarDirective implements O
} }
showHighlightGroup(idx: number): EarmarkGroup { showHighlightGroup(idx: number): EarmarkGroup {
return this._viewModeService.isEarmarks() && this.earmarkGroups$.value.find(h => h.startIdx === idx); return this._viewModeService.isEarmarks() && this.#earmarkGroups().find(h => h.startIdx === idx);
} }
protected readonly _trackBy = (index: number, listItem: ListItem<AnnotationWrapper>) => listItem.item.id; protected readonly _trackBy = (index: number, listItem: ListItem<AnnotationWrapper>) => listItem.item.id;
private _updateEarmarksGroups(): void { #getEarmarksGroups() {
if (!this.annotations?.length) {
return;
}
const earmarksGroups: EarmarkGroup[] = []; const earmarksGroups: EarmarkGroup[] = [];
if (!this.annotations?.length) {
return earmarksGroups;
}
let lastGroup: EarmarkGroup; let lastGroup: EarmarkGroup;
for (let idx = 0; idx < this.annotations.length; ++idx) { for (let idx = 0; idx < this.annotations.length; ++idx) {
if (idx === 0 || this.annotations[idx].item.color !== this.annotations[idx - 1].item.color) { if (idx === 0 || this.annotations[idx].item.color !== this.annotations[idx - 1].item.color) {
@ -99,7 +94,8 @@ export class AnnotationsListComponent extends HasScrollbarDirective implements O
lastGroup.length += 1; lastGroup.length += 1;
} }
} }
earmarksGroups.push(lastGroup); earmarksGroups.push(lastGroup);
this.earmarkGroups$.next(earmarksGroups); return earmarksGroups;
} }
} }

View File

@ -1,4 +1,8 @@
<div *ngIf="showExcludedPages$ | async; else selectAndFilter" class="right-title heading" translate="file-preview.tabs.exclude-pages.label"> <div
*ngIf="excludedPagesService.shown(); else selectAndFilter"
class="right-title heading"
translate="file-preview.tabs.exclude-pages.label"
>
<div> <div>
<iqser-circle-button <iqser-circle-button
(action)="excludedPagesService.toggle()" (action)="excludedPagesService.toggle()"
@ -11,7 +15,7 @@
<ng-template #selectAndFilter> <ng-template #selectAndFilter>
<div class="right-title heading"> <div class="right-title heading">
{{ title$ | async | translate }} {{ title() | translate }}
<div> <div>
<div <div
(click)="multiSelectService.activate()" (click)="multiSelectService.activate()"
@ -32,11 +36,7 @@
</ng-template> </ng-template>
<div class="right-content"> <div class="right-content">
<redaction-readonly-banner <redaction-readonly-banner *ngIf="state.isReadonly()"></redaction-readonly-banner>
*ngIf="state.isReadonly()"
[file]="file"
[isDossierActive]="state.dossier().isActive"
></redaction-readonly-banner>
<div *ngIf="multiSelectService.active()" class="multi-select"> <div *ngIf="multiSelectService.active()" class="multi-select">
<div class="selected-wrapper"> <div class="selected-wrapper">
@ -76,7 +76,7 @@
> >
<div <div
(click)="scrollQuickNavFirst()" (click)="scrollQuickNavFirst()"
[class.disabled]="activeViewerPage === 1" [class.disabled]="pdf.currentPage() === 1"
[matTooltip]="'file-preview.quick-nav.jump-first' | translate" [matTooltip]="'file-preview.quick-nav.jump-first' | translate"
class="jump" class="jump"
matTooltipPosition="above" matTooltipPosition="above"
@ -86,14 +86,13 @@
<redaction-pages <redaction-pages
(click)="pagesPanelActive = true" (click)="pagesPanelActive = true"
[activePage]="activeViewerPage"
[displayedAnnotations]="displayedAnnotations" [displayedAnnotations]="displayedAnnotations"
[pages]="displayedPages" [pages]="displayedPages"
></redaction-pages> ></redaction-pages>
<div <div
(click)="scrollQuickNavLast()" (click)="scrollQuickNavLast()"
[class.disabled]="activeViewerPage === file?.numberOfPages" [class.disabled]="pdf.currentPage() === state.file()?.numberOfPages"
[matTooltip]="'file-preview.quick-nav.jump-last' | translate" [matTooltip]="'file-preview.quick-nav.jump-last' | translate"
class="jump" class="jump"
matTooltipPosition="above" matTooltipPosition="above"
@ -104,15 +103,15 @@
<div class="content"> <div class="content">
<div <div
*ngIf="(isEarmarks$ | async) === false" *ngIf="!viewModeService.isEarmarks()"
[attr.anotation-page-header]="activeViewerPage" [attr.anotation-page-header]="pdf.currentPage()"
[hidden]="excludedPagesService.shown$ | async" [hidden]="excludedPagesService.shown()"
class="workload-separator" class="workload-separator"
> >
<span *ngIf="!!activeViewerPage" class="flex-align-items-center"> <span *ngIf="!!pdf.currentPage()" class="flex-align-items-center">
<iqser-circle-button <iqser-circle-button
(action)="excludedPagesService.toggle()" (action)="excludedPagesService.toggle()"
*ngIf="currentPageIsExcluded" *ngIf="currentPageIsExcluded()"
[size]="14" [size]="14"
[tooltip]="'file-preview.excluded-from-redaction' | translate | capitalize" [tooltip]="'file-preview.excluded-from-redaction' | translate | capitalize"
class="mr-10 primary" class="mr-10 primary"
@ -121,7 +120,7 @@
></iqser-circle-button> ></iqser-circle-button>
<span <span
[translateParams]="{ page: activeViewerPage, count: activeAnnotations.length }" [translateParams]="{ page: pdf.currentPage(), count: activeAnnotations.length }"
[translate]="'page'" [translate]="'page'"
class="all-caps-label" class="all-caps-label"
></span> ></span>
@ -146,19 +145,19 @@
(keydown)="preventKeyDefault($event)" (keydown)="preventKeyDefault($event)"
(keyup)="preventKeyDefault($event)" (keyup)="preventKeyDefault($event)"
[class.active-panel]="!pagesPanelActive" [class.active-panel]="!pagesPanelActive"
[hidden]="excludedPagesService.shown$ | async" [hidden]="excludedPagesService.shown()"
class="annotations" class="annotations"
id="annotations-list" id="annotations-list"
tabindex="1" tabindex="1"
> >
<ng-container *ngIf="activeViewerPage && !displayedAnnotations.get(activeViewerPage)?.length"> <ng-container *ngIf="pdf.currentPage() && !displayedAnnotations.get(pdf.currentPage())?.length">
<iqser-empty-state <iqser-empty-state
[horizontalPadding]="24" [horizontalPadding]="24"
[text]="'file-preview.no-data.title' | translate" [text]="'file-preview.no-data.title' | translate"
[verticalPadding]="40" [verticalPadding]="40"
icon="iqser:document" icon="iqser:document"
> >
<ng-container *ngIf="currentPageIsExcluded && displayedPages.length"> <ng-container *ngIf="currentPageIsExcluded() && displayedPages.length">
{{ 'file-preview.tabs.annotations.page-is' | translate }} {{ 'file-preview.tabs.annotations.page-is' | translate }}
<a <a
(click)="excludedPagesService.toggle()" (click)="excludedPagesService.toggle()"
@ -182,7 +181,7 @@
<div *ngIf="displayedPages.length" class="no-annotations-buttons-container mt-32"> <div *ngIf="displayedPages.length" class="no-annotations-buttons-container mt-32">
<iqser-icon-button <iqser-icon-button
(action)="jumpToPreviousWithAnnotations()" (action)="jumpToPreviousWithAnnotations()"
[disabled]="activeViewerPage <= displayedPages[0]" [disabled]="pdf.currentPage() <= displayedPages[0]"
[label]="'file-preview.tabs.annotations.jump-to-previous' | translate" [label]="'file-preview.tabs.annotations.jump-to-previous' | translate"
[type]="iconButtonTypes.dark" [type]="iconButtonTypes.dark"
icon="red:nav-prev" icon="red:nav-prev"
@ -190,7 +189,7 @@
<iqser-icon-button <iqser-icon-button
(action)="jumpToNextWithAnnotations()" (action)="jumpToNextWithAnnotations()"
[disabled]="activeViewerPage >= displayedPages[displayedPages.length - 1]" [disabled]="pdf.currentPage() >= displayedPages[displayedPages.length - 1]"
[label]="'file-preview.tabs.annotations.jump-to-next' | translate" [label]="'file-preview.tabs.annotations.jump-to-next' | translate"
[type]="iconButtonTypes.dark" [type]="iconButtonTypes.dark"
class="mt-8" class="mt-8"
@ -201,11 +200,11 @@
<redaction-annotations-list <redaction-annotations-list
(pagesPanelActive)="pagesPanelActive = $event" (pagesPanelActive)="pagesPanelActive = $event"
[annotations]="(displayedAnnotations$ | async)?.get(activeViewerPage)" [annotations]="(displayedAnnotations$ | async)?.get(pdf.currentPage())"
></redaction-annotations-list> ></redaction-annotations-list>
</div> </div>
<redaction-page-exclusion *ngIf="showExcludedPages$ | async"></redaction-page-exclusion> <redaction-page-exclusion *ngIf="excludedPagesService.shown()"></redaction-page-exclusion>
</div> </div>
</div> </div>
</div> </div>
@ -214,7 +213,7 @@
<iqser-circle-button <iqser-circle-button
(action)="skippedService.toggleSkipped()" (action)="skippedService.toggleSkipped()"
*ngIf="filter.id === 'skipped'" *ngIf="filter.id === 'skipped'"
[icon]="(skippedService.hideSkipped$ | async) ? 'red:visibility-off' : 'red:visibility'" [icon]="skippedService.hideSkipped() ? 'red:visibility-off' : 'red:visibility'"
[type]="circleButtonTypes.dark" [type]="circleButtonTypes.dark"
preventDefault preventDefault
></iqser-circle-button> ></iqser-circle-button>

View File

@ -1,4 +1,4 @@
import { ChangeDetectorRef, Component, effect, ElementRef, HostListener, Input, OnDestroy, ViewChild } from '@angular/core'; import { ChangeDetectorRef, Component, computed, effect, ElementRef, HostListener, OnDestroy, ViewChild } from '@angular/core';
import { AnnotationWrapper } from '@models/file/annotation.wrapper'; import { AnnotationWrapper } from '@models/file/annotation.wrapper';
import { AnnotationProcessingService } from '../../services/annotation-processing.service'; import { AnnotationProcessingService } from '../../services/annotation-processing.service';
import { MatDialog } from '@angular/material/dialog'; import { MatDialog } from '@angular/material/dialog';
@ -12,11 +12,9 @@ import {
IconButtonTypes, IconButtonTypes,
INestedFilter, INestedFilter,
IqserEventTarget, IqserEventTarget,
shareLast,
} from '@iqser/common-ui'; } from '@iqser/common-ui';
import { combineLatest, delay, Observable } from 'rxjs'; import { combineLatest, delay, Observable } from 'rxjs';
import { map, tap } from 'rxjs/operators'; import { map, tap } from 'rxjs/operators';
import { File } from '@red/domain';
import { ExcludedPagesService } from '../../services/excluded-pages.service'; import { ExcludedPagesService } from '../../services/excluded-pages.service';
import { MultiSelectService } from '../../services/multi-select.service'; import { MultiSelectService } from '../../services/multi-select.service';
import { DocumentInfoService } from '../../services/document-info.service'; import { DocumentInfoService } from '../../services/document-info.service';
@ -32,7 +30,6 @@ import { REDDocumentViewer } from '../../../pdf-viewer/services/document-viewer.
import { SuggestionsService } from '../../services/suggestions.service'; import { SuggestionsService } from '../../services/suggestions.service';
import { ListItem } from '@models/file/list-item'; import { ListItem } from '@models/file/list-item';
import { PageRotationService } from '../../../pdf-viewer/services/page-rotation.service'; import { PageRotationService } from '../../../pdf-viewer/services/page-rotation.service';
import { toObservable } from '@angular/core/rxjs-interop';
const COMMAND_KEY_ARRAY = ['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown', 'Escape']; const COMMAND_KEY_ARRAY = ['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown', 'Escape'];
const ALL_HOTKEY_ARRAY = ['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown']; const ALL_HOTKEY_ARRAY = ['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown'];
@ -43,9 +40,6 @@ const ALL_HOTKEY_ARRAY = ['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown'];
styleUrls: ['./file-workload.component.scss'], styleUrls: ['./file-workload.component.scss'],
}) })
export class FileWorkloadComponent extends AutoUnsubscribe implements OnDestroy { export class FileWorkloadComponent extends AutoUnsubscribe implements OnDestroy {
@Input() activeViewerPage: number;
@Input({ required: true }) file!: File;
readonly iconButtonTypes = IconButtonTypes; readonly iconButtonTypes = IconButtonTypes;
readonly circleButtonTypes = CircleButtonTypes; readonly circleButtonTypes = CircleButtonTypes;
@ -53,9 +47,10 @@ export class FileWorkloadComponent extends AutoUnsubscribe implements OnDestroy
displayedPages: number[] = []; displayedPages: number[] = [];
pagesPanelActive = true; pagesPanelActive = true;
readonly displayedAnnotations$: Observable<Map<number, ListItem<AnnotationWrapper>[]>>; readonly displayedAnnotations$: Observable<Map<number, ListItem<AnnotationWrapper>[]>>;
readonly showExcludedPages$: Observable<boolean>; readonly title = computed(() =>
readonly title$: Observable<string>; this.viewModeService.isEarmarks() ? _('file-preview.tabs.highlights.label') : _('file-preview.tabs.annotations.label'),
readonly isEarmarks$: Observable<boolean>; );
readonly currentPageIsExcluded = computed(() => this.state.file().excludedPages.includes(this.pdf.currentPage()));
@ViewChild('annotationsElement') private readonly _annotationsElement: ElementRef; @ViewChild('annotationsElement') private readonly _annotationsElement: ElementRef;
@ViewChild('quickNavigation') private readonly _quickNavigationElement: ElementRef; @ViewChild('quickNavigation') private readonly _quickNavigationElement: ElementRef;
@ -75,7 +70,6 @@ export class FileWorkloadComponent extends AutoUnsubscribe implements OnDestroy
readonly excludedPagesService: ExcludedPagesService, readonly excludedPagesService: ExcludedPagesService,
private readonly _changeDetectorRef: ChangeDetectorRef, private readonly _changeDetectorRef: ChangeDetectorRef,
private readonly _annotationProcessingService: AnnotationProcessingService, private readonly _annotationProcessingService: AnnotationProcessingService,
private readonly _viewModeService: ViewModeService,
private readonly _suggestionsService: SuggestionsService, private readonly _suggestionsService: SuggestionsService,
private readonly _pageRotationService: PageRotationService, private readonly _pageRotationService: PageRotationService,
) { ) {
@ -99,47 +93,30 @@ export class FileWorkloadComponent extends AutoUnsubscribe implements OnDestroy
}); });
this.displayedAnnotations$ = this._displayedAnnotations$; this.displayedAnnotations$ = this._displayedAnnotations$;
this.showExcludedPages$ = this._showExcludedPages$;
this.isEarmarks$ = this._isEarmarks$;
this.title$ = this._title$;
effect(() => { effect(() => {
if (this.multiSelectService.inactive()) { if (this.multiSelectService.inactive()) {
this.annotationManager.deselect(); this.annotationManager.deselect();
} }
}); });
effect(() => {
const __ = this.viewModeService.viewMode();
this._scrollViews();
});
effect(
() => {
if (this.excludedPagesService.shown()) {
this._disableMultiSelectAndDocumentInfo();
}
},
{ allowSignalWrites: true },
);
} }
get activeAnnotations(): AnnotationWrapper[] { get activeAnnotations(): AnnotationWrapper[] {
return this.displayedAnnotations.get(this.activeViewerPage) || []; return this.displayedAnnotations.get(this.pdf.currentPage()) || [];
}
get currentPageIsExcluded(): boolean {
return this.file?.excludedPages?.includes(this.activeViewerPage);
}
private get _showExcludedPages$() {
return this.excludedPagesService.shown$.pipe(
tap(show => {
if (show) {
this._disableMultiSelectAndDocumentInfo();
}
}),
shareLast(),
);
}
private get _title$(): Observable<string> {
return this.isEarmarks$.pipe(
map(isHighlights => (isHighlights ? _('file-preview.tabs.highlights.label') : _('file-preview.tabs.annotations.label'))),
);
}
private get _isEarmarks$(): Observable<boolean> {
return this.viewModeService.viewMode$.pipe(
tap(() => this._scrollViews()),
map(() => this.viewModeService.isEarmarks()),
);
} }
private get _firstSelectedAnnotation() { private get _firstSelectedAnnotation() {
@ -155,7 +132,6 @@ export class FileWorkloadComponent extends AutoUnsubscribe implements OnDestroy
primary$, primary$,
secondary$, secondary$,
this.listingService.selected$, this.listingService.selected$,
toObservable(this.multiSelectService.active),
this._pageRotationService.rotations$, this._pageRotationService.rotations$,
]).pipe( ]).pipe(
delay(0), delay(0),
@ -166,7 +142,7 @@ export class FileWorkloadComponent extends AutoUnsubscribe implements OnDestroy
} }
get #allPages() { get #allPages() {
return Array.from({ length: this.file?.numberOfPages }, (_x, i) => i + 1); return Array.from({ length: this.state.file()?.numberOfPages }, (_x, i) => i + 1);
} }
private static _scrollToFirstElement(elements: HTMLElement[], mode: 'always' | 'if-needed' = 'if-needed') { private static _scrollToFirstElement(elements: HTMLElement[], mode: 'always' | 'if-needed' = 'if-needed') {
@ -232,10 +208,11 @@ export class FileWorkloadComponent extends AutoUnsubscribe implements OnDestroy
} }
scrollAnnotations(): void { scrollAnnotations(): void {
if (this._firstSelectedAnnotation?.pageNumber === this.activeViewerPage) { const currentPage = this.pdf.currentPage();
if (this._firstSelectedAnnotation?.pageNumber === currentPage) {
return; return;
} }
this.scrollAnnotationsToPage(this.activeViewerPage, 'always'); this.scrollAnnotationsToPage(currentPage, 'always');
} }
scrollAnnotationsToPage(page: number, mode: 'always' | 'if-needed' = 'if-needed'): void { scrollAnnotationsToPage(page: number, mode: 'always' | 'if-needed' = 'if-needed'): void {
@ -257,8 +234,9 @@ export class FileWorkloadComponent extends AutoUnsubscribe implements OnDestroy
} }
scrollQuickNavigation(): void { scrollQuickNavigation(): void {
let quickNavPageIndex = this.displayedPages.findIndex(p => p >= this.activeViewerPage); const currentPage = this.pdf.currentPage();
if (quickNavPageIndex === -1 || this.displayedPages[quickNavPageIndex] !== this.activeViewerPage) { let quickNavPageIndex = this.displayedPages.findIndex(p => p >= currentPage);
if (quickNavPageIndex === -1 || this.displayedPages[quickNavPageIndex] !== currentPage) {
quickNavPageIndex = Math.max(0, quickNavPageIndex - 1); quickNavPageIndex = Math.max(0, quickNavPageIndex - 1);
} }
this._scrollQuickNavigationToPage(this.displayedPages[quickNavPageIndex]); this._scrollQuickNavigationToPage(this.displayedPages[quickNavPageIndex]);
@ -287,8 +265,9 @@ export class FileWorkloadComponent extends AutoUnsubscribe implements OnDestroy
} }
navigateAnnotations($event: KeyboardEvent) { navigateAnnotations($event: KeyboardEvent) {
if (!this._firstSelectedAnnotation || this.activeViewerPage !== this._firstSelectedAnnotation.pageNumber) { const currentPage = this.pdf.currentPage();
if (this.displayedPages.indexOf(this.activeViewerPage) !== -1) { if (!this._firstSelectedAnnotation || currentPage !== this._firstSelectedAnnotation.pageNumber) {
if (this.displayedPages.indexOf(currentPage) !== -1) {
// Displayed page has annotations // Displayed page has annotations
return this.listingService.selectAnnotations(this.activeAnnotations ? this.activeAnnotations[0] : null); return this.listingService.selectAnnotations(this.activeAnnotations ? this.activeAnnotations[0] : null);
} }
@ -362,12 +341,13 @@ export class FileWorkloadComponent extends AutoUnsubscribe implements OnDestroy
primary: INestedFilter[], primary: INestedFilter[],
secondary: INestedFilter[] = [], secondary: INestedFilter[] = [],
): Map<number, AnnotationWrapper[]> { ): Map<number, AnnotationWrapper[]> {
const onlyPageWithAnnotations = this.viewModeService.onlyPagesWithAnnotations();
if (!primary || primary.length === 0) { if (!primary || primary.length === 0) {
this.displayedPages = this.viewModeService.onlyPagesWithAnnotations ? [] : this.#allPages; this.displayedPages = onlyPageWithAnnotations ? [] : this.#allPages;
return; return;
} }
if (this._viewModeService.isRedacted()) { if (this.viewModeService.isRedacted()) {
annotations = annotations.filter(a => !bool(a.isChangeLogRemoved)); annotations = annotations.filter(a => !bool(a.isChangeLogRemoved));
annotations = this._suggestionsService.filterWorkloadSuggestionsInPreview(annotations); annotations = this._suggestionsService.filterWorkloadSuggestionsInPreview(annotations);
} }
@ -376,13 +356,13 @@ export class FileWorkloadComponent extends AutoUnsubscribe implements OnDestroy
const pagesThatDisplayAnnotations = [...this.displayedAnnotations.keys()]; const pagesThatDisplayAnnotations = [...this.displayedAnnotations.keys()];
const enabledFilters = this.filterService.enabledFlatFilters; const enabledFilters = this.filterService.enabledFlatFilters;
if (enabledFilters.some(f => f.id === 'pages-without-annotations')) { if (enabledFilters.some(f => f.id === 'pages-without-annotations')) {
if (enabledFilters.length === 1 && !this.viewModeService.onlyPagesWithAnnotations) { if (enabledFilters.length === 1 && !onlyPageWithAnnotations) {
this.displayedPages = this.#allPages.filter(page => !pagesThatDisplayAnnotations.includes(page)); this.displayedPages = this.#allPages.filter(page => !pagesThatDisplayAnnotations.includes(page));
} else { } else {
this.displayedPages = []; this.displayedPages = [];
} }
this.displayedAnnotations.clear(); this.displayedAnnotations.clear();
} else if (enabledFilters.length || this.viewModeService.onlyPagesWithAnnotations) { } else if (enabledFilters.length || onlyPageWithAnnotations) {
this.displayedPages = pagesThatDisplayAnnotations; this.displayedPages = pagesThatDisplayAnnotations;
} else { } else {
this.displayedPages = this.#allPages; this.displayedPages = this.#allPages;
@ -393,9 +373,10 @@ export class FileWorkloadComponent extends AutoUnsubscribe implements OnDestroy
} }
private _selectFirstAnnotationOnCurrentPageIfNecessary() { private _selectFirstAnnotationOnCurrentPageIfNecessary() {
const currentPage = this.pdf.currentPage();
if ( if (
(!this._firstSelectedAnnotation || this.activeViewerPage !== this._firstSelectedAnnotation.pageNumber) && (!this._firstSelectedAnnotation || currentPage !== this._firstSelectedAnnotation.pageNumber) &&
this.displayedPages.indexOf(this.activeViewerPage) >= 0 && this.displayedPages.indexOf(currentPage) >= 0 &&
this.activeAnnotations.length > 0 this.activeAnnotations.length > 0
) { ) {
this.listingService.selectAnnotations(this.activeAnnotations[0]); this.listingService.selectAnnotations(this.activeAnnotations[0]);
@ -403,7 +384,7 @@ export class FileWorkloadComponent extends AutoUnsubscribe implements OnDestroy
} }
private _navigatePages($event: KeyboardEvent) { private _navigatePages($event: KeyboardEvent) {
const pageIdx = this.displayedPages.indexOf(this.activeViewerPage); const pageIdx = this.displayedPages.indexOf(this.pdf.currentPage());
if ($event.key !== 'ArrowDown') { if ($event.key !== 'ArrowDown') {
if (pageIdx === -1) { if (pageIdx === -1) {
@ -437,7 +418,7 @@ export class FileWorkloadComponent extends AutoUnsubscribe implements OnDestroy
private _nextPageWithAnnotations() { private _nextPageWithAnnotations() {
let idx = 0; let idx = 0;
for (const page of this.displayedPages) { for (const page of this.displayedPages) {
if (page > this.activeViewerPage && this.displayedAnnotations.get(page)) { if (page > this.pdf.currentPage() && this.displayedAnnotations.get(page)) {
break; break;
} }
++idx; ++idx;
@ -449,7 +430,7 @@ export class FileWorkloadComponent extends AutoUnsubscribe implements OnDestroy
let idx = this.displayedPages.length - 1; let idx = this.displayedPages.length - 1;
const reverseDisplayedPages = [...this.displayedPages].reverse(); const reverseDisplayedPages = [...this.displayedPages].reverse();
for (const page of reverseDisplayedPages) { for (const page of reverseDisplayedPages) {
if (page < this.activeViewerPage && this.displayedAnnotations.get(page)) { if (page < this.pdf.currentPage() && this.displayedAnnotations.get(page)) {
break; break;
} }
--idx; --idx;
@ -473,7 +454,6 @@ export class FileWorkloadComponent extends AutoUnsubscribe implements OnDestroy
const newValue = annotations.get(key).map(annotation => ({ const newValue = annotations.get(key).map(annotation => ({
item: annotation, item: annotation,
isSelected: this.listingService.isSelected(annotation), isSelected: this.listingService.isSelected(annotation),
multiSelectActive: this.multiSelectService.active(),
})); }));
listItemsMap.set(key, newValue); listItemsMap.set(key, newValue);
}); });

View File

@ -2,7 +2,7 @@
(click)="pageSelected.emit(number)" (click)="pageSelected.emit(number)"
(dblclick)="toggleReadState()" (dblclick)="toggleReadState()"
*ngIf="componentContext$ | async" *ngIf="componentContext$ | async"
[class.active]="active" [class.active]="isActive"
[class.read]="read" [class.read]="read"
[id]="'quick-nav-page-' + number" [id]="'quick-nav-page-' + number"
class="page-wrapper" class="page-wrapper"
@ -13,7 +13,7 @@
<div *ngIf="activeSelection" class="dot"></div> <div *ngIf="activeSelection" class="dot"></div>
<div *ngIf="isRotated" class="rotated"> <div *ngIf="pageRotationService.rotations()[number]" class="rotated">
<mat-icon svgIcon="red:rotation"></mat-icon> <mat-icon svgIcon="red:rotation"></mat-icon>
</div> </div>
</div> </div>

View File

@ -1,4 +1,4 @@
import { ChangeDetectorRef, Component, EventEmitter, Input, OnChanges, OnInit, Output } from '@angular/core'; import { Component, EventEmitter, Input, OnChanges, OnInit, Output } from '@angular/core';
import { PermissionsService } from '@services/permissions.service'; import { PermissionsService } from '@services/permissions.service';
import { ViewedPagesService } from '@services/files/viewed-pages.service'; import { ViewedPagesService } from '@services/files/viewed-pages.service';
import { FilePreviewStateService } from '../../services/file-preview-state.service'; import { FilePreviewStateService } from '../../services/file-preview-state.service';
@ -8,6 +8,7 @@ import { map, tap } from 'rxjs/operators';
import { AppConfig, ViewedPage } from '@red/domain'; import { AppConfig, ViewedPage } from '@red/domain';
import { ViewedPagesMapService } from '@services/files/viewed-pages-map.service'; import { ViewedPagesMapService } from '@services/files/viewed-pages-map.service';
import { pairwise } from 'rxjs'; import { pairwise } from 'rxjs';
import { PdfViewer } from '../../../pdf-viewer/services/pdf-viewer.service';
interface PageIndicatorContext { interface PageIndicatorContext {
isRotated: boolean; isRotated: boolean;
@ -15,46 +16,42 @@ interface PageIndicatorContext {
} }
@Component({ @Component({
selector: 'redaction-page-indicator [number] [read]', selector: 'redaction-page-indicator',
templateUrl: './page-indicator.component.html', templateUrl: './page-indicator.component.html',
styleUrls: ['./page-indicator.component.scss'], styleUrls: ['./page-indicator.component.scss'],
}) })
export class PageIndicatorComponent extends ContextComponent<PageIndicatorContext> implements OnChanges, OnInit { export class PageIndicatorComponent extends ContextComponent<PageIndicatorContext> implements OnChanges, OnInit {
@Input() active = false; @Input({ required: true }) number: number;
@Input() showDottedIcon = false; @Input() showDottedIcon = false;
@Input() number: number;
@Input() activeSelection = false; @Input() activeSelection = false;
@Input() read = false; @Input() read = false;
@Output() readonly pageSelected = new EventEmitter<number>(); @Output() readonly pageSelected = new EventEmitter<number>();
pageReadTimeout: number = null; pageReadTimeout: number = null;
isRotated = false;
readonly #config = getConfig<AppConfig>(); readonly #config = getConfig<AppConfig>();
constructor( constructor(
private readonly _viewedPagesService: ViewedPagesService, private readonly _viewedPagesService: ViewedPagesService,
private readonly _viewedPagesMapService: ViewedPagesMapService, private readonly _viewedPagesMapService: ViewedPagesMapService,
private readonly _changeDetectorRef: ChangeDetectorRef,
private readonly _permissionService: PermissionsService, private readonly _permissionService: PermissionsService,
private readonly _state: FilePreviewStateService, private readonly _state: FilePreviewStateService,
private readonly _pdf: PdfViewer,
readonly pageRotationService: PageRotationService, readonly pageRotationService: PageRotationService,
) { ) {
super(); super();
} }
get isActive() {
return this.number === this._pdf.currentPage();
}
ngOnInit() { ngOnInit() {
const isRotated$ = this.pageRotationService.isRotated$(this.number).pipe(
tap(value => {
this.isRotated = value;
this._changeDetectorRef.detectChanges();
}),
);
const assigneeChanged$ = this._state.file$.pipe( const assigneeChanged$ = this._state.file$.pipe(
pairwise(), pairwise(),
map(([prevFile, currFile]) => prevFile.assignee !== currFile.assignee), map(([prevFile, currFile]) => prevFile.assignee !== currFile.assignee),
tap(assigneeChanged => assigneeChanged && this.handlePageRead()), tap(assigneeChanged => assigneeChanged && this.handlePageRead()),
); );
super._initContext({ isRotated: isRotated$, assigneeChanged: assigneeChanged$ }); super._initContext({ assigneeChanged: assigneeChanged$ });
} }
ngOnChanges() { ngOnChanges() {
@ -80,9 +77,9 @@ export class PageIndicatorComponent extends ContextComponent<PageIndicatorContex
clearTimeout(this.pageReadTimeout); clearTimeout(this.pageReadTimeout);
} }
if (this.active && !this.read) { if (this.isActive && !this.read) {
this.pageReadTimeout = window.setTimeout(async () => { this.pageReadTimeout = window.setTimeout(async () => {
if (this.active && !this.read) { if (this.isActive && !this.read) {
await this.#markPageRead(); await this.#markPageRead();
} }
}, this.#config.AUTO_READ_TIME * 1000); }, this.#config.AUTO_READ_TIME * 1000);

View File

@ -3,7 +3,6 @@
(pageSelected)="pageSelectedByClick($event)" (pageSelected)="pageSelectedByClick($event)"
*ngFor="let pageNumber of pages; trackBy: trackBy" *ngFor="let pageNumber of pages; trackBy: trackBy"
[activeSelection]="pageHasSelection(pageNumber)" [activeSelection]="pageHasSelection(pageNumber)"
[active]="pageNumber === activePage"
[number]="pageNumber" [number]="pageNumber"
[read]="!!getViewedPage(viewedPages, pageNumber)" [read]="!!getViewedPage(viewedPages, pageNumber)"
[showDottedIcon]="hasOnlyManualRedactionsAndIsExcluded(pageNumber)" [showDottedIcon]="hasOnlyManualRedactionsAndIsExcluded(pageNumber)"

View File

@ -9,22 +9,21 @@ import { ViewedPagesMapService } from '@services/files/viewed-pages-map.service'
import { ViewedPage } from '@red/domain'; import { ViewedPage } from '@red/domain';
@Component({ @Component({
selector: 'redaction-pages [pages] [activePage] [displayedAnnotations]', selector: 'redaction-pages',
templateUrl: './pages.component.html', templateUrl: './pages.component.html',
styleUrls: ['./pages.component.scss'], styleUrls: ['./pages.component.scss'],
}) })
export class PagesComponent { export class PagesComponent {
@Input() pages: List<number>; @Input({ required: true }) pages: List<number>;
@Input() activePage: number; @Input({ required: true }) displayedAnnotations: Map<number, AnnotationWrapper[]>;
@Input() displayedAnnotations: Map<number, AnnotationWrapper[]>; protected readonly _pdf = inject(PdfViewer);
readonly #pdf = inject(PdfViewer);
readonly #state = inject(FilePreviewStateService); readonly #state = inject(FilePreviewStateService);
readonly viewedPages$ = inject(ViewedPagesMapService).get$(this.#state.fileId); readonly viewedPages$ = inject(ViewedPagesMapService).get$(this.#state.fileId);
readonly #multiSelectService = inject(MultiSelectService); readonly #multiSelectService = inject(MultiSelectService);
readonly #listingService = inject(AnnotationsListingService); readonly #listingService = inject(AnnotationsListingService);
pageSelectedByClick($event: number): void { pageSelectedByClick($event: number): void {
this.#pdf.navigateTo($event); this._pdf.navigateTo($event);
} }
readonly trackBy = (_index: number, item: number) => item; readonly trackBy = (_index: number, item: number) => item;

View File

@ -1,4 +1,4 @@
<div class="justify-center banner read-only flex"> <div *ngIf="_state.file() as file" class="justify-center banner read-only flex">
<div *ngIf="file.isFullProcessing" class="ocr-indicator"> <div *ngIf="file.isFullProcessing" class="ocr-indicator">
<ng-container *ngIf="file.isOcrProcessing; else defaultProcessing"> <ng-container *ngIf="file.isOcrProcessing; else defaultProcessing">
<redaction-ocr-progress-bar <redaction-ocr-progress-bar
@ -16,6 +16,6 @@
<div class="flex-center"> <div class="flex-center">
<mat-icon class="primary-white" svgIcon="red:read-only"></mat-icon> <mat-icon class="primary-white" svgIcon="red:read-only"></mat-icon>
<span [translate]="isDossierActive ? 'readonly' : 'readonly-archived'" class="read-only-text"></span> <span [translate]="_state.dossier().isActive ? 'readonly' : 'readonly-archived'" class="read-only-text"></span>
</div> </div>
</div> </div>

View File

@ -1,5 +1,5 @@
import { Component, Input } from '@angular/core'; import { Component, inject } from '@angular/core';
import { File } from '@red/domain'; import { FilePreviewStateService } from '../../services/file-preview-state.service';
@Component({ @Component({
selector: 'redaction-readonly-banner', selector: 'redaction-readonly-banner',
@ -7,6 +7,5 @@ import { File } from '@red/domain';
styleUrls: ['./readonly-banner.component.scss'], styleUrls: ['./readonly-banner.component.scss'],
}) })
export class ReadonlyBannerComponent { export class ReadonlyBannerComponent {
@Input() file: File; protected readonly _state = inject(FilePreviewStateService);
@Input() isDossierActive: boolean;
} }

View File

@ -1,12 +1,12 @@
<ng-container *ngIf="state.file$ | async as file"> <ng-container *ngIf="state.file() as file">
<iqser-empty-state <iqser-empty-state
*ngIf="file.excluded && (documentInfoService.hidden$ | async) && excludedPagesService.hidden$ | async" *ngIf="file.excluded && documentInfoService.hidden() && excludedPagesService.hidden()"
[horizontalPadding]="40" [horizontalPadding]="40"
[text]="'file-preview.tabs.is-excluded' | translate" [text]="'file-preview.tabs.is-excluded' | translate"
icon="red:needs-work" icon="red:needs-work"
></iqser-empty-state> ></iqser-empty-state>
<redaction-document-info *ngIf="documentInfoService.shown$ | async" id="document-info"></redaction-document-info> <redaction-document-info *ngIf="documentInfoService.shown()" id="document-info"></redaction-document-info>
<redaction-file-workload *ngIf="!file.excluded" [activeViewerPage]="pdf.currentPage$ | async" [file]="file"></redaction-file-workload> <redaction-file-workload *ngIf="!file.excluded"></redaction-file-workload>
</ng-container> </ng-container>

View File

@ -150,7 +150,7 @@ export class FilePreviewScreenComponent
} }
get changed() { get changed() {
return this._pageRotationService.hasRotations; return this._pageRotationService.hasRotations();
} }
get #textSelected$() { get #textSelected$() {
@ -162,7 +162,7 @@ export class FilePreviewScreenComponent
return textSelected$.pipe( return textSelected$.pipe(
tap(([selectedText, canPerformActions, file]) => { tap(([selectedText, canPerformActions, file]) => {
const isCurrentPageExcluded = file.isPageExcluded(this.pdf.currentPage); const isCurrentPageExcluded = file.isPageExcluded(this.pdf.currentPage());
if ((selectedText.length > 2 || this._isJapaneseString(selectedText)) && canPerformActions && !isCurrentPageExcluded) { if ((selectedText.length > 2 || this._isJapaneseString(selectedText)) && canPerformActions && !isCurrentPageExcluded) {
this.pdf.enable(textActions); this.pdf.enable(textActions);
@ -192,7 +192,7 @@ export class FilePreviewScreenComponent
); );
return currentPageEarmarks$.pipe( return currentPageEarmarks$.pipe(
map(earmarks => [earmarks, this._skippedService.hideSkipped, this.state.dossierTemplateId] as const), map(earmarks => [earmarks, this._skippedService.hideSkipped(), this.state.dossierTemplateId] as const),
tap(args => this._annotationDrawService.draw(...args)), tap(args => this._annotationDrawService.draw(...args)),
); );
} }
@ -204,7 +204,7 @@ export class FilePreviewScreenComponent
); );
return isChangingFromEarmarksViewMode$.pipe( return isChangingFromEarmarksViewMode$.pipe(
map(() => this._fileDataService.earmarks().get(this.pdf.currentPage) ?? []), map(() => this._fileDataService.earmarks().get(this.pdf.currentPage()) ?? []),
map(earmarks => this.deleteAnnotations(earmarks, [])), map(earmarks => this.deleteAnnotations(earmarks, [])),
); );
} }
@ -505,7 +505,7 @@ export class FilePreviewScreenComponent
} }
#getAnnotationsToDraw(oldAnnotations: AnnotationWrapper[], newAnnotations: AnnotationWrapper[]) { #getAnnotationsToDraw(oldAnnotations: AnnotationWrapper[], newAnnotations: AnnotationWrapper[]) {
const currentPage = this.pdf.currentPage; const currentPage = this.pdf.currentPage();
const currentPageAnnotations = this._annotationManager.get(a => a.getPageNumber() === currentPage); const currentPageAnnotations = this._annotationManager.get(a => a.getPageNumber() === currentPage);
const existingAnnotations = []; const existingAnnotations = [];
for (const annotation of currentPageAnnotations) { for (const annotation of currentPageAnnotations) {
@ -578,11 +578,11 @@ export class FilePreviewScreenComponent
}); });
} }
private _setExcludedPageStyles() { #setExcludedPageStyles() {
const file = this._filesMapService.get(this.dossierId, this.fileId); const file = this._filesMapService.get(this.dossierId, this.fileId);
setTimeout(() => { setTimeout(() => {
const iframeDoc = this.pdf.instance.UI.iframeWindow.document; const iframeDoc = this.pdf.instance.UI.iframeWindow.document;
const currentPage = this.pdf.currentPage; const currentPage = this.pdf.currentPage();
const elementId = `pageWidgetContainer${currentPage}`; const elementId = `pageWidgetContainer${currentPage}`;
const pageContainer = iframeDoc.getElementById(elementId); const pageContainer = iframeDoc.getElementById(elementId);
if (pageContainer) { if (pageContainer) {
@ -616,7 +616,7 @@ export class FilePreviewScreenComponent
.subscribe(); .subscribe();
this.addActiveScreenSubscription = this._documentViewer.pageComplete$.subscribe(() => { this.addActiveScreenSubscription = this._documentViewer.pageComplete$.subscribe(() => {
this._setExcludedPageStyles(); this.#setExcludedPageStyles();
}); });
this.addActiveScreenSubscription = this._documentViewer.keyUp$.subscribe($event => { this.addActiveScreenSubscription = this._documentViewer.keyUp$.subscribe($event => {
@ -749,7 +749,7 @@ export class FilePreviewScreenComponent
this.#handleDeltaAnnotationFilters(currentFilters); this.#handleDeltaAnnotationFilters(currentFilters);
} }
this._annotationDrawService.draw(newAnnotations, this._skippedService.hideSkipped, this.state.dossierTemplateId).then(); this._annotationDrawService.draw(newAnnotations, this._skippedService.hideSkipped(), this.state.dossierTemplateId).then();
} }
#handleDeltaAnnotationFilters(currentFilters: NestedFilter[]) { #handleDeltaAnnotationFilters(currentFilters: NestedFilter[]) {

View File

@ -224,7 +224,7 @@ export class AnnotationActionsService {
async cancelResize(annotationWrapper: AnnotationWrapper) { async cancelResize(annotationWrapper: AnnotationWrapper) {
this._annotationManager.resizingAnnotationId = undefined; this._annotationManager.resizingAnnotationId = undefined;
this._annotationManager.delete(annotationWrapper); this._annotationManager.delete(annotationWrapper);
await this._annotationDrawService.draw([annotationWrapper], this._skippedService.hideSkipped, this._state.dossierTemplateId); await this._annotationDrawService.draw([annotationWrapper], this._skippedService.hideSkipped(), this._state.dossierTemplateId);
this._annotationManager.deselect(); this._annotationManager.deselect();
await this._fileDataService.annotationsChanged(); await this._fileDataService.annotationsChanged();
} }

View File

@ -51,7 +51,7 @@ export class AnnotationsListingService extends ListingService<AnnotationWrapper>
this._annotationManager.deselect(); this._annotationManager.deselect();
} }
if (pageNumber === this._pdf.currentPage) { if (pageNumber === this._pdf.currentPage()) {
return this._annotationManager.jumpAndSelect(annotations); return this._annotationManager.jumpAndSelect(annotations);
} }

View File

@ -1,7 +1,6 @@
import { Injectable } from '@angular/core'; import { computed, effect, Injectable, Signal, signal } from '@angular/core';
import { BehaviorSubject, merge, Observable } from 'rxjs'; import { merge } from 'rxjs';
import { shareLast } from '@iqser/common-ui'; import { map, startWith, withLatestFrom } from 'rxjs/operators';
import { map, startWith, tap, withLatestFrom } from 'rxjs/operators';
import { DossierTemplatesService } from '@services/dossier-templates/dossier-templates.service'; import { DossierTemplatesService } from '@services/dossier-templates/dossier-templates.service';
import { FileAttributesService } from '@services/entity-services/file-attributes.service'; import { FileAttributesService } from '@services/entity-services/file-attributes.service';
import { FilesMapService } from '@services/files/files-map.service'; import { FilesMapService } from '@services/files/files-map.service';
@ -11,9 +10,9 @@ import { ExcludedPagesService } from './excluded-pages.service';
@Injectable() @Injectable()
export class DocumentInfoService { export class DocumentInfoService {
readonly shown$: Observable<boolean>; readonly shown: Signal<boolean>;
readonly hidden$: Observable<boolean>; readonly hidden: Signal<boolean>;
private readonly _show$ = new BehaviorSubject(false); readonly #show$ = signal(false);
constructor( constructor(
private readonly _dossierTemplatesService: DossierTemplatesService, private readonly _dossierTemplatesService: DossierTemplatesService,
@ -22,19 +21,14 @@ export class DocumentInfoService {
private readonly _multiSelectService: MultiSelectService, private readonly _multiSelectService: MultiSelectService,
private readonly _excludedPagesService: ExcludedPagesService, private readonly _excludedPagesService: ExcludedPagesService,
) { ) {
this.shown$ = this._show$.asObservable().pipe( this.hidden = computed(() => !this.#show$());
tap(show => { this.shown = this.#show$.asReadonly();
if (show) { effect(() => {
if (this.shown()) {
this._multiSelectService.deactivate(); this._multiSelectService.deactivate();
this._excludedPagesService.hide(); this._excludedPagesService.hide();
} }
}), });
shareLast(),
);
this.hidden$ = this.shown$.pipe(
map(value => !value),
shareLast(),
);
} }
fileAttributes$(fileId: string, dossierId: string, dossierTemplateId: string) { fileAttributes$(fileId: string, dossierId: string, dossierTemplateId: string) {
@ -50,15 +44,15 @@ export class DocumentInfoService {
} }
show() { show() {
this._show$.next(true); this.#show$.set(true);
} }
hide() { hide() {
this._show$.next(false); this.#show$.set(false);
} }
toggle() { toggle() {
this._show$.next(!this._show$.value); this.#show$.set(!this.#show$());
} }
private _toAttributes(attributes: IFileAttributeConfig[], file: File) { private _toAttributes(attributes: IFileAttributeConfig[], file: File) {

View File

@ -1,28 +1,25 @@
import { Injectable } from '@angular/core'; import { computed, Injectable, Signal, signal } from '@angular/core';
import { BehaviorSubject, Observable } from 'rxjs';
import { shareDistinctLast } from '@iqser/common-ui';
import { map } from 'rxjs/operators';
@Injectable() @Injectable()
export class ExcludedPagesService { export class ExcludedPagesService {
readonly shown$: Observable<boolean>; readonly shown: Signal<boolean>;
readonly hidden$: Observable<boolean>; readonly hidden: Signal<boolean>;
private readonly _show$ = new BehaviorSubject(false); readonly #show = signal(false);
constructor() { constructor() {
this.shown$ = this._show$.asObservable().pipe(shareDistinctLast()); this.shown = this.#show.asReadonly();
this.hidden$ = this.shown$.pipe(map(value => !value)); this.hidden = computed(() => !this.#show());
} }
show() { show() {
this._show$.next(true); this.#show.set(true);
} }
hide() { hide() {
this._show$.next(false); this.#show.set(false);
} }
toggle() { toggle() {
this._show$.next(!this._show$.value); this.#show.set(!this.#show());
} }
} }

View File

@ -191,7 +191,7 @@ export class PdfProxyService {
} }
private _handleCustomActions() { private _handleCustomActions() {
const isCurrentPageExcluded = this._state.file().isPageExcluded(this._pdf.currentPage); const isCurrentPageExcluded = this._state.file().isPageExcluded(this._pdf.currentPage());
if (this._viewModeService.isRedacted()) { if (this._viewModeService.isRedacted()) {
this._viewerHeaderService.enable([HeaderElements.TOGGLE_READABLE_REDACTIONS]); this._viewerHeaderService.enable([HeaderElements.TOGGLE_READABLE_REDACTIONS]);
@ -266,7 +266,7 @@ export class PdfProxyService {
// Remove deselected annotations from selected list // Remove deselected annotations from selected list
nextAnnotations = this._annotationManager.selected.filter(ann => !annotations.some(a => a.Id === ann.Id)); nextAnnotations = this._annotationManager.selected.filter(ann => !annotations.some(a => a.Id === ann.Id));
this._pdf.disable(TextPopups.ADD_RECTANGLE); this._pdf.disable(TextPopups.ADD_RECTANGLE);
const currentPage = this._pdf.currentPage; const currentPage = this._pdf.currentPage();
if (nextAnnotations.some(a => a.getPageNumber() === currentPage)) { if (nextAnnotations.some(a => a.getPageNumber() === currentPage)) {
this.#configureAnnotationSpecificActions(nextAnnotations); this.#configureAnnotationSpecificActions(nextAnnotations);
} else { } else {

View File

@ -1,36 +1,26 @@
import { Injectable } from '@angular/core'; import { effect, Injectable, Signal, signal } from '@angular/core';
import { BehaviorSubject, Observable } from 'rxjs'; import { bool } from '@iqser/common-ui';
import { skip, tap } from 'rxjs/operators';
import { bool, shareDistinctLast } from '@iqser/common-ui';
import { REDAnnotationManager } from '../../pdf-viewer/services/annotation-manager.service'; import { REDAnnotationManager } from '../../pdf-viewer/services/annotation-manager.service';
@Injectable() @Injectable()
export class SkippedService { export class SkippedService {
readonly hideSkipped$: Observable<boolean>; readonly hideSkipped: Signal<boolean>;
readonly #hideSkipped$ = new BehaviorSubject(false); readonly #hideSkipped = signal(false);
constructor(private readonly _annotationManager: REDAnnotationManager) { constructor(private readonly _annotationManager: REDAnnotationManager) {
this.hideSkipped$ = this.#hideSkipped$.pipe( this.hideSkipped = this.#hideSkipped.asReadonly();
tap(hideSkipped => this._handleIgnoreAnnotationsDrawing(hideSkipped)),
shareDistinctLast(),
skip(1),
);
}
get hideSkipped(): boolean { effect(() => {
return this.#hideSkipped$.value;
}
toggleSkipped(): void {
this.#hideSkipped$.next(!this.hideSkipped);
}
private _handleIgnoreAnnotationsDrawing(hideSkipped: boolean): void {
const ignored = this._annotationManager.get(a => bool(a.getCustomData('skipped'))); const ignored = this._annotationManager.get(a => bool(a.getCustomData('skipped')));
if (hideSkipped) { if (this.#hideSkipped()) {
this._annotationManager.hide(ignored); this._annotationManager.hide(ignored);
} else { } else {
this._annotationManager.show(ignored); this._annotationManager.show(ignored);
} }
});
}
toggleSkipped(): void {
this.#hideSkipped.set(!this.#hideSkipped());
} }
} }

View File

@ -12,16 +12,15 @@ export class ViewModeService {
readonly isStandard = computed(() => this.viewMode() === ViewModes.STANDARD); readonly isStandard = computed(() => this.viewMode() === ViewModes.STANDARD);
readonly isDelta = computed(() => this.viewMode() === ViewModes.DELTA); readonly isDelta = computed(() => this.viewMode() === ViewModes.DELTA);
readonly #viewMode = signal<ViewMode>(ViewModes.STANDARD); readonly #viewMode = signal<ViewMode>(ViewModes.STANDARD);
readonly onlyPagesWithAnnotations = computed(() =>
([ViewModes.DELTA, ViewModes.TEXT_HIGHLIGHTS] as ViewMode[]).includes(this.#viewMode()),
);
constructor() { constructor() {
this.viewMode$ = toObservable(this.#viewMode); this.viewMode$ = toObservable(this.#viewMode);
this.viewMode = this.#viewMode.asReadonly(); this.viewMode = this.#viewMode.asReadonly();
} }
get onlyPagesWithAnnotations() {
return ([ViewModes.DELTA, ViewModes.TEXT_HIGHLIGHTS] as ViewMode[]).includes(this.#viewMode());
}
is(viewMode: ViewMode) { is(viewMode: ViewMode) {
return this.viewMode() === viewMode; return this.viewMode() === viewMode;
} }

View File

@ -164,7 +164,7 @@ export class REDDocumentViewer {
pages.forEach(page => this.#document.setRotation(0, Number(page))); pages.forEach(page => this.#document.setRotation(0, Number(page)));
} }
rotate(rotation: RotationType, page = this.#pdf.currentPage) { rotate(rotation: RotationType, page = this.#pdf.currentPage()) {
if (rotation === RotationTypes.LEFT) { if (rotation === RotationTypes.LEFT) {
this.#document.rotateCounterClockwise(page); this.#document.rotateCounterClockwise(page);
} else { } else {

View File

@ -1,8 +1,8 @@
import { Injectable, Injector } from '@angular/core'; import { computed, Injectable, Injector, Signal, signal } from '@angular/core';
import { BehaviorSubject, firstValueFrom, Observable, of } from 'rxjs'; import { firstValueFrom, Observable, of } from 'rxjs';
import { RotationType } from '@red/domain'; import { RotationType } from '@red/domain';
import { FileManagementService } from '@services/files/file-management.service'; import { FileManagementService } from '@services/files/file-management.service';
import { distinctUntilChanged, map, tap } from 'rxjs/operators'; import { map, tap } from 'rxjs/operators';
import { import {
ConfirmationDialogComponent, ConfirmationDialogComponent,
ConfirmOption, ConfirmOption,
@ -18,11 +18,14 @@ import { PdfViewer } from './pdf-viewer.service';
import { FilesMapService } from '@services/files/files-map.service'; import { FilesMapService } from '@services/files/files-map.service';
import { NGXLogger } from 'ngx-logger'; import { NGXLogger } from 'ngx-logger';
import { REDDocumentViewer } from './document-viewer.service'; import { REDDocumentViewer } from './document-viewer.service';
import { toObservable } from '@angular/core/rxjs-interop';
@Injectable() @Injectable()
export class PageRotationService { export class PageRotationService {
readonly rotations$: Observable<Record<number, number>>; readonly rotations$: Observable<Record<number, number>>;
readonly #rotations$ = new BehaviorSubject<Record<number, number>>({}); readonly rotations: Signal<Record<number, number>>;
readonly hasRotations: Signal<boolean>;
readonly #rotations = signal<Record<number, number>>({});
constructor( constructor(
private readonly _pdf: PdfViewer, private readonly _pdf: PdfViewer,
@ -34,34 +37,21 @@ export class PageRotationService {
private readonly _filesMapService: FilesMapService, private readonly _filesMapService: FilesMapService,
private readonly _documentViewer: REDDocumentViewer, private readonly _documentViewer: REDDocumentViewer,
) { ) {
this.rotations$ = this.#rotations$.asObservable(); this.rotations$ = toObservable(this.#rotations);
} this.rotations = this.#rotations.asReadonly();
this.hasRotations = computed(() => Object.values(this.#rotations()).filter(v => !!v).length > 0);
get hasRotations() {
return Object.values(this.#rotations$.value).filter(v => !!v).length > 0;
} }
get currentPageRotation() { get currentPageRotation() {
const savedRotation = this._documentViewer.currentPageRotation; const savedRotation = this._documentViewer.currentPageRotation;
const currentRotation = this.#rotations[this._pdf.currentPage] ?? 0; const currentRotation = this.#rotations()[this._pdf.currentPage()] ?? 0;
const rotationsSum = savedRotation + currentRotation; const rotationsSum = savedRotation + currentRotation;
return Math.abs(rotationsSum < 360 ? rotationsSum : 360 - rotationsSum); return Math.abs(rotationsSum < 360 ? rotationsSum : 360 - rotationsSum);
} }
get #rotations() {
return this.#rotations$.value;
}
isRotated$(page: number) {
return this.#rotations$.pipe(
map(rotations => !!rotations[page]),
distinctUntilChanged(),
);
}
async applyRotation() { async applyRotation() {
this._loadingService.start(); this._loadingService.start();
const pages = this.#rotations$.value; const pages = this.#rotations();
const { dossierId, fileId } = this._pdf; const { dossierId, fileId } = this._pdf;
if (!dossierId || !fileId) { if (!dossierId || !fileId) {
@ -84,26 +74,26 @@ export class PageRotationService {
} }
discardRotation() { discardRotation() {
this._documentViewer.resetRotation(Object.keys(this.#rotations$.value)); this._documentViewer.resetRotation(Object.keys(this.#rotations()));
this.clearRotations(); this.clearRotations();
} }
addRotation(rotation: RotationType): void { addRotation(rotation: RotationType): void {
const pageNumber = this._pdf.currentPage; const pageNumber = this._pdf.currentPage();
const pageRotation = this.#rotations$.value[pageNumber]; const pageRotation = this.#rotations()[pageNumber];
const rotationValue = pageRotation ? (pageRotation + Number(rotation)) % 360 : rotation; const rotationValue = pageRotation ? (pageRotation + Number(rotation)) % 360 : rotation;
this.#rotations$.next({ ...this.#rotations$.value, [pageNumber]: rotationValue }); this.#rotations.update(value => ({ ...value, [pageNumber]: rotationValue }));
this._documentViewer.rotate(rotation); this._documentViewer.rotate(rotation);
} }
clearRotations() { clearRotations() {
this.#rotations$.next({}); this.#rotations.set({});
} }
showConfirmationDialogIfHasRotations() { showConfirmationDialogIfHasRotations() {
return this.hasRotations ? this.#showConfirmationDialog() : of(false); return this.hasRotations() ? this.#showConfirmationDialog() : of(false);
} }
#showConfirmationDialog() { #showConfirmationDialog() {

View File

@ -13,7 +13,7 @@ import { TranslateService } from '@ngx-translate/core';
import { LicenseService } from '@services/license.service'; import { LicenseService } from '@services/license.service';
import { UserPreferenceService } from '@users/user-preference.service'; import { UserPreferenceService } from '@users/user-preference.service';
import { environment } from '@environments/environment'; import { environment } from '@environments/environment';
import { takeUntilDestroyed, toObservable } from '@angular/core/rxjs-interop'; import { takeUntilDestroyed, toObservable, toSignal } from '@angular/core/rxjs-interop';
import TextTool = Core.Tools.TextTool; import TextTool = Core.Tools.TextTool;
import Annotation = Core.Annotations.Annotation; import Annotation = Core.Annotations.Annotation;
import TextHighlightAnnotation = Core.Annotations.TextHighlightAnnotation; import TextHighlightAnnotation = Core.Annotations.TextHighlightAnnotation;
@ -26,6 +26,7 @@ export class PdfViewer {
map(params => Number(params.get('page') ?? '1')), map(params => Number(params.get('page') ?? '1')),
shareDistinctLast(), shareDistinctLast(),
); );
readonly currentPage = toSignal(this.currentPage$);
documentViewer: DocumentViewer; documentViewer: DocumentViewer;
@ -81,10 +82,6 @@ export class PdfViewer {
} }
} }
get currentPage() {
return this.#adjustPage(this.#currentInternalPage);
}
get #totalPages$() { get #totalPages$() {
const layoutChanged$ = fromEvent(this.documentViewer, 'layoutChanged').pipe(startWith('')); const layoutChanged$ = fromEvent(this.documentViewer, 'layoutChanged').pipe(startWith(''));
const docLoaded$ = fromEvent(this.documentViewer, 'documentLoaded'); const docLoaded$ = fromEvent(this.documentViewer, 'documentLoaded');

View File

@ -347,7 +347,7 @@ export class ViewerHeaderService {
} }
#toggleRotationActionButtons() { #toggleRotationActionButtons() {
if (this._rotationService.hasRotations) { if (this._rotationService.hasRotations()) {
this.enable(ROTATION_ACTION_BUTTONS); this.enable(ROTATION_ACTION_BUTTONS);
} else { } else {
this.disable(ROTATION_ACTION_BUTTONS); this.disable(ROTATION_ACTION_BUTTONS);

View File

@ -31,6 +31,7 @@ import { ViewerHeaderService } from '../../../pdf-viewer/services/viewer-header.
import { ROTATION_ACTION_BUTTONS } from '../../../pdf-viewer/utils/constants'; import { ROTATION_ACTION_BUTTONS } from '../../../pdf-viewer/utils/constants';
import { Roles } from '@users/roles'; import { Roles } from '@users/roles';
import { FileAttributesService } from '@services/entity-services/file-attributes.service'; import { FileAttributesService } from '@services/entity-services/file-attributes.service';
import { toObservable } from '@angular/core/rxjs-interop';
@Component({ @Component({
selector: 'redaction-file-actions', selector: 'redaction-file-actions',
@ -161,7 +162,7 @@ export class FileActionsComponent implements OnChanges {
type: ActionTypes.circleBtn, type: ActionTypes.circleBtn,
action: () => this._documentInfoService.toggle(), action: () => this._documentInfoService.toggle(),
tooltip: _('file-preview.document-info'), tooltip: _('file-preview.document-info'),
ariaExpanded: this._documentInfoService?.shown$, ariaExpanded: toObservable(this._documentInfoService.shown, { injector: this._injector }),
icon: 'red:status-info', icon: 'red:status-info',
show: !!this._documentInfoService, show: !!this._documentInfoService,
}, },
@ -170,7 +171,7 @@ export class FileActionsComponent implements OnChanges {
type: ActionTypes.circleBtn, type: ActionTypes.circleBtn,
action: () => this._excludedPagesService.toggle(), action: () => this._excludedPagesService.toggle(),
tooltip: _('file-preview.exclude-pages'), tooltip: _('file-preview.exclude-pages'),
ariaExpanded: this._excludedPagesService?.shown$, ariaExpanded: toObservable(this._excludedPagesService.shown, { injector: this._injector }),
showDot: !!this.file.excludedPages?.length, showDot: !!this.file.excludedPages?.length,
icon: 'red:exclude-pages', icon: 'red:exclude-pages',
show: show: