RED-6829: remove other observables
This commit is contained in:
parent
58976f24d8
commit
e89d317fa0
@ -1,5 +1,4 @@
|
|||||||
export interface ListItem<T> {
|
export interface ListItem<T> {
|
||||||
item: T;
|
item: T;
|
||||||
isSelected: boolean;
|
isSelected: boolean;
|
||||||
multiSelectActive: boolean;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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"
|
||||||
|
|||||||
@ -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[] {
|
||||||
|
|||||||
@ -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>
|
||||||
|
|||||||
@ -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;
|
||||||
|
|||||||
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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>
|
||||||
|
|||||||
@ -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);
|
||||||
});
|
});
|
||||||
|
|||||||
@ -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>
|
||||||
|
|||||||
@ -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);
|
||||||
|
|||||||
@ -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)"
|
||||||
|
|||||||
@ -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;
|
||||||
|
|||||||
@ -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>
|
||||||
|
|||||||
@ -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;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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>
|
||||||
|
|||||||
@ -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[]) {
|
||||||
|
|||||||
@ -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();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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) {
|
||||||
|
|||||||
@ -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());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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 {
|
||||||
|
|||||||
@ -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());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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 {
|
||||||
|
|||||||
@ -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() {
|
||||||
|
|||||||
@ -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');
|
||||||
|
|||||||
@ -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);
|
||||||
|
|||||||
@ -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:
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user