RED-6829: update pdf event listeners to run in ng zone
This commit is contained in:
parent
f3e7811523
commit
04cc33b18e
@ -47,7 +47,7 @@
|
|||||||
|
|
||||||
<router-outlet></router-outlet>
|
<router-outlet></router-outlet>
|
||||||
|
|
||||||
<redaction-pdf-viewer [style.visibility]="(documentViewer.loaded$ | async) ? 'visible' : 'hidden'"></redaction-pdf-viewer>
|
<redaction-pdf-viewer [style.visibility]="documentViewer.loaded() ? 'visible' : 'hidden'"></redaction-pdf-viewer>
|
||||||
|
|
||||||
<iqser-skeleton [templates]="{ dashboard: dashboardSkeleton, dossier: dossierSkeleton }"></iqser-skeleton>
|
<iqser-skeleton [templates]="{ dashboard: dashboardSkeleton, dossier: dossierSkeleton }"></iqser-skeleton>
|
||||||
|
|
||||||
|
|||||||
@ -1,62 +1,58 @@
|
|||||||
<ng-container *ngIf="componentContext$ | async as ctx">
|
<div class="right-title heading" translate="file-preview.tabs.document-info.label">
|
||||||
<div class="right-title heading" translate="file-preview.tabs.document-info.label">
|
<div>
|
||||||
|
<iqser-circle-button
|
||||||
|
(action)="edit()"
|
||||||
|
*ngIf="permissionsService.canEditFileAttributes(_state.file(), _state.dossier())"
|
||||||
|
[tooltip]="'file-preview.tabs.document-info.edit' | translate"
|
||||||
|
buttonId="edit-document-info-btn"
|
||||||
|
icon="iqser:edit"
|
||||||
|
tooltipPosition="before"
|
||||||
|
></iqser-circle-button>
|
||||||
|
|
||||||
|
<iqser-circle-button
|
||||||
|
(action)="documentInfoService.hide()"
|
||||||
|
[tooltip]="'file-preview.tabs.document-info.close' | translate"
|
||||||
|
buttonId="close-document-info-btn"
|
||||||
|
icon="iqser:close"
|
||||||
|
tooltipPosition="before"
|
||||||
|
></iqser-circle-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="right-content" iqserHasScrollbar>
|
||||||
|
<div class="section">
|
||||||
|
<div *ngFor="let attr of _fileAttributes()" class="attribute">
|
||||||
|
<div class="small-label">{{ attr.label }}:</div>
|
||||||
|
<div>{{ attr.value ? (isDate(attr) ? (attr.value | date : 'd MMM yyyy') : attr.value) : '-' }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="section small-label stats-subtitle">
|
||||||
<div>
|
<div>
|
||||||
<iqser-circle-button
|
<mat-icon svgIcon="red:folder"></mat-icon>
|
||||||
(action)="edit()"
|
<span
|
||||||
*ngIf="permissionsService.canEditFileAttributes(_state.file(), _state.dossier())"
|
[innerHTML]="'file-preview.tabs.document-info.details.dossier' | translate : { dossierName: _state.dossier().dossierName }"
|
||||||
[tooltip]="'file-preview.tabs.document-info.edit' | translate"
|
></span>
|
||||||
buttonId="edit-document-info-btn"
|
</div>
|
||||||
icon="iqser:edit"
|
|
||||||
tooltipPosition="before"
|
|
||||||
></iqser-circle-button>
|
|
||||||
|
|
||||||
<iqser-circle-button
|
<div>
|
||||||
(action)="documentInfoService.hide()"
|
<mat-icon svgIcon="iqser:document"></mat-icon>
|
||||||
[tooltip]="'file-preview.tabs.document-info.close' | translate"
|
<span>{{ 'file-preview.tabs.document-info.details.pages' | translate : { pages: _state.file().numberOfPages } }}</span>
|
||||||
buttonId="close-document-info-btn"
|
</div>
|
||||||
icon="iqser:close"
|
|
||||||
tooltipPosition="before"
|
<div *ngIf="_state.file().added | date : 'mediumDate' as added">
|
||||||
></iqser-circle-button>
|
<mat-icon svgIcon="red:calendar"></mat-icon>
|
||||||
|
<span [innerHTML]="'file-preview.tabs.document-info.details.created-on' | translate : { date: added }"></span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div *ngIf="_state.dossier().dueDate | date : 'mediumDate' as dueDate">
|
||||||
|
<mat-icon svgIcon="red:lightning"></mat-icon>
|
||||||
|
<span [innerHTML]="'file-preview.tabs.document-info.details.due' | translate : { date: dueDate }"></span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<mat-icon svgIcon="red:template"></mat-icon>
|
||||||
|
{{ _dossierTemplate().name }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
<div class="right-content" iqserHasScrollbar>
|
|
||||||
<div class="section">
|
|
||||||
<div *ngFor="let attr of ctx.fileAttributes" class="attribute">
|
|
||||||
<div class="small-label">{{ attr.label }}:</div>
|
|
||||||
<div>{{ attr.value ? (isDate(attr) ? (attr.value | date : 'd MMM yyyy') : attr.value) : '-' }}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="section small-label stats-subtitle">
|
|
||||||
<div>
|
|
||||||
<mat-icon svgIcon="red:folder"></mat-icon>
|
|
||||||
<span
|
|
||||||
[innerHTML]="
|
|
||||||
'file-preview.tabs.document-info.details.dossier' | translate : { dossierName: _state.dossier().dossierName }
|
|
||||||
"
|
|
||||||
></span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<mat-icon svgIcon="iqser:document"></mat-icon>
|
|
||||||
<span>{{ 'file-preview.tabs.document-info.details.pages' | translate : { pages: _state.file().numberOfPages } }}</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div *ngIf="_state.file().added | date : 'mediumDate' as added">
|
|
||||||
<mat-icon svgIcon="red:calendar"></mat-icon>
|
|
||||||
<span [innerHTML]="'file-preview.tabs.document-info.details.created-on' | translate : { date: added }"></span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div *ngIf="_state.dossier().dueDate | date : 'mediumDate' as dueDate">
|
|
||||||
<mat-icon svgIcon="red:lightning"></mat-icon>
|
|
||||||
<span [innerHTML]="'file-preview.tabs.document-info.details.due' | translate : { date: dueDate }"></span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<mat-icon svgIcon="red:template"></mat-icon>
|
|
||||||
{{ ctx.dossierTemplateName }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</ng-container>
|
|
||||||
|
|||||||
@ -1,14 +1,13 @@
|
|||||||
import { Component, inject } from '@angular/core';
|
import { Component, computed, inject } from '@angular/core';
|
||||||
import { DossierTemplatesService } from '@services/dossier-templates/dossier-templates.service';
|
import { DossierTemplatesService } from '@services/dossier-templates/dossier-templates.service';
|
||||||
import { DocumentInfoService } from '../../services/document-info.service';
|
import { DocumentInfoService } from '../../services/document-info.service';
|
||||||
import { combineLatest, switchMap } from 'rxjs';
|
|
||||||
import { PermissionsService } from '@services/permissions.service';
|
import { PermissionsService } from '@services/permissions.service';
|
||||||
import { FilePreviewStateService } from '../../services/file-preview-state.service';
|
import { FilePreviewStateService } from '../../services/file-preview-state.service';
|
||||||
import { map } from 'rxjs/operators';
|
|
||||||
import { type FileAttributeConfigType, FileAttributeConfigTypes } from '@red/domain';
|
import { type FileAttributeConfigType, FileAttributeConfigTypes } from '@red/domain';
|
||||||
import { FilePreviewDialogService } from '../../services/file-preview-dialog.service';
|
import { FilePreviewDialogService } from '../../services/file-preview-dialog.service';
|
||||||
import { FileAttributesService } from '@services/entity-services/file-attributes.service';
|
import { FileAttributesService } from '@services/entity-services/file-attributes.service';
|
||||||
import { ContextComponent } from '@iqser/common-ui';
|
import { ContextComponent } from '@iqser/common-ui';
|
||||||
|
import { toSignal } from '@angular/core/rxjs-interop';
|
||||||
|
|
||||||
interface FileAttribute {
|
interface FileAttribute {
|
||||||
label: string;
|
label: string;
|
||||||
@ -17,7 +16,6 @@ interface FileAttribute {
|
|||||||
}
|
}
|
||||||
|
|
||||||
interface Context {
|
interface Context {
|
||||||
readonly dossierTemplateName: string;
|
|
||||||
readonly fileAttributes: FileAttribute[];
|
readonly fileAttributes: FileAttribute[];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -28,28 +26,22 @@ interface Context {
|
|||||||
})
|
})
|
||||||
export class DocumentInfoComponent extends ContextComponent<Context> {
|
export class DocumentInfoComponent extends ContextComponent<Context> {
|
||||||
protected readonly _state = inject(FilePreviewStateService);
|
protected readonly _state = inject(FilePreviewStateService);
|
||||||
|
protected readonly _dossierTemplate = toSignal(inject(DossierTemplatesService).getEntityChanged$(this._state.dossierTemplateId));
|
||||||
|
protected readonly _fileAttributesConfig = toSignal(inject(FileAttributesService).fileAttributesConfig$);
|
||||||
|
protected readonly _attributes = toSignal(
|
||||||
|
this.documentInfoService.fileAttributes$(this._state.fileId, this._state.dossierId, this._state.dossierTemplateId),
|
||||||
|
);
|
||||||
|
protected readonly _fileAttributes = computed(() => {
|
||||||
|
this._fileAttributesConfig();
|
||||||
|
return this._attributes();
|
||||||
|
});
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
fileAttributesService: FileAttributesService,
|
|
||||||
readonly permissionsService: PermissionsService,
|
readonly permissionsService: PermissionsService,
|
||||||
readonly documentInfoService: DocumentInfoService,
|
readonly documentInfoService: DocumentInfoService,
|
||||||
private readonly _dialogService: FilePreviewDialogService,
|
private readonly _dialogService: FilePreviewDialogService,
|
||||||
private readonly _dossierTemplatesService: DossierTemplatesService,
|
|
||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
const fileAttributes$ = combineLatest([this._state.file$, this._state.dossier$, fileAttributesService.fileAttributesConfig$]).pipe(
|
|
||||||
switchMap(([file, dossier]) => this.documentInfoService.fileAttributes$(file.id, dossier.id, dossier.dossierTemplateId)),
|
|
||||||
);
|
|
||||||
|
|
||||||
const dossierTemplateName$ = this._state.dossier$.pipe(
|
|
||||||
switchMap(dossier => this._dossierTemplatesService.getEntityChanged$(dossier.dossierTemplateId)),
|
|
||||||
map(dossierTemplate => dossierTemplate.name),
|
|
||||||
);
|
|
||||||
|
|
||||||
super._initContext({
|
|
||||||
dossierTemplateName: dossierTemplateName$,
|
|
||||||
fileAttributes: fileAttributes$,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
edit() {
|
edit() {
|
||||||
|
|||||||
@ -26,6 +26,7 @@
|
|||||||
></div>
|
></div>
|
||||||
|
|
||||||
<iqser-popup-filter
|
<iqser-popup-filter
|
||||||
|
*ngIf="documentInfoService.hidden()"
|
||||||
[actionsTemplate]="annotationFilterActionTemplate"
|
[actionsTemplate]="annotationFilterActionTemplate"
|
||||||
[attr.help-mode-key]="'workload_in_editor'"
|
[attr.help-mode-key]="'workload_in_editor'"
|
||||||
[primaryFiltersSlug]="'primaryFilters'"
|
[primaryFiltersSlug]="'primaryFilters'"
|
||||||
|
|||||||
@ -75,7 +75,6 @@ import { ConfigService } from '@services/config.service';
|
|||||||
import { ReadableRedactionsService } from '../pdf-viewer/services/readable-redactions.service';
|
import { ReadableRedactionsService } from '../pdf-viewer/services/readable-redactions.service';
|
||||||
import { Roles } from '@users/roles';
|
import { Roles } from '@users/roles';
|
||||||
import { SuggestionsService } from './services/suggestions.service';
|
import { SuggestionsService } from './services/suggestions.service';
|
||||||
import { toObservable } from '@angular/core/rxjs-interop';
|
|
||||||
|
|
||||||
const textActions = [TextPopups.ADD_DICTIONARY, TextPopups.ADD_FALSE_POSITIVE];
|
const textActions = [TextPopups.ADD_DICTIONARY, TextPopups.ADD_FALSE_POSITIVE];
|
||||||
|
|
||||||
@ -147,32 +146,41 @@ export class FilePreviewScreenComponent
|
|||||||
const file = this.state.file();
|
const file = this.state.file();
|
||||||
this._fileDataService.loadAnnotations(file).then();
|
this._fileDataService.loadAnnotations(file).then();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
effect(
|
||||||
|
() => {
|
||||||
|
if (this._documentViewer.loaded()) {
|
||||||
|
this._pageRotationService.clearRotations();
|
||||||
|
this._viewerHeaderService.disable(ROTATION_ACTION_BUTTONS);
|
||||||
|
this.viewerReady().then();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ allowSignalWrites: true },
|
||||||
|
);
|
||||||
|
|
||||||
|
effect(() => {
|
||||||
|
if (this._documentViewer.pageComplete()) {
|
||||||
|
this.#setExcludedPageStyles();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
effect(() => {
|
||||||
|
const selectedText = this._documentViewer.selectedText();
|
||||||
|
const canPerformActions = this.pdfProxyService.canPerformActions();
|
||||||
|
const isCurrentPageExcluded = this.state.file().isPageExcluded(this.pdf.currentPage());
|
||||||
|
|
||||||
|
if ((selectedText.length > 2 || this._isJapaneseString(selectedText)) && canPerformActions && !isCurrentPageExcluded) {
|
||||||
|
this.pdf.enable(textActions);
|
||||||
|
} else {
|
||||||
|
this.pdf.disable(textActions);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
get changed() {
|
get changed() {
|
||||||
return this._pageRotationService.hasRotations();
|
return this._pageRotationService.hasRotations();
|
||||||
}
|
}
|
||||||
|
|
||||||
get #textSelected$() {
|
|
||||||
const textSelected$ = combineLatest([
|
|
||||||
this._documentViewer.textSelected$,
|
|
||||||
toObservable(this.pdfProxyService.canPerformActions, { injector: this._injector }),
|
|
||||||
this.state.file$,
|
|
||||||
]);
|
|
||||||
|
|
||||||
return textSelected$.pipe(
|
|
||||||
tap(([selectedText, canPerformActions, file]) => {
|
|
||||||
const isCurrentPageExcluded = file.isPageExcluded(this.pdf.currentPage());
|
|
||||||
|
|
||||||
if ((selectedText.length > 2 || this._isJapaneseString(selectedText)) && canPerformActions && !isCurrentPageExcluded) {
|
|
||||||
this.pdf.enable(textActions);
|
|
||||||
} else {
|
|
||||||
this.pdf.disable(textActions);
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
get #earmarks$() {
|
get #earmarks$() {
|
||||||
const isEarmarksViewMode$ = this._viewModeService.viewMode$.pipe(filter(() => this._viewModeService.isEarmarks()));
|
const isEarmarksViewMode$ = this._viewModeService.viewMode$.pipe(filter(() => this._viewModeService.isEarmarks()));
|
||||||
|
|
||||||
@ -440,17 +448,6 @@ export class FilePreviewScreenComponent
|
|||||||
}
|
}
|
||||||
|
|
||||||
loadAnnotations() {
|
loadAnnotations() {
|
||||||
const documentLoaded$ = this._documentViewer.loaded$.pipe(
|
|
||||||
tap(loaded => {
|
|
||||||
if (!loaded) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this._pageRotationService.clearRotations();
|
|
||||||
this._viewerHeaderService.disable(ROTATION_ACTION_BUTTONS);
|
|
||||||
return this.viewerReady();
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
const annotations$ = this._fileDataService.annotations$.pipe(
|
const annotations$ = this._fileDataService.annotations$.pipe(
|
||||||
startWith([] as AnnotationWrapper[]),
|
startWith([] as AnnotationWrapper[]),
|
||||||
pairwise(),
|
pairwise(),
|
||||||
@ -471,7 +468,7 @@ export class FilePreviewScreenComponent
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
return combineLatest([currentPageAnnotations$, documentLoaded$]).pipe(
|
return combineLatest([currentPageAnnotations$, this._documentViewer.loaded$]).pipe(
|
||||||
filter(([, loaded]) => loaded),
|
filter(([, loaded]) => loaded),
|
||||||
map(([annotations]) => annotations),
|
map(([annotations]) => annotations),
|
||||||
map(annotations => this.drawChangedAnnotations(...annotations)),
|
map(annotations => this.drawChangedAnnotations(...annotations)),
|
||||||
@ -608,23 +605,10 @@ export class FilePreviewScreenComponent
|
|||||||
.pipe(tap(() => this.#handleDeletedFile()))
|
.pipe(tap(() => this.#handleDeletedFile()))
|
||||||
.subscribe();
|
.subscribe();
|
||||||
|
|
||||||
this.addActiveScreenSubscription = combineLatest([this._viewModeService.viewMode$, this.state.file$])
|
|
||||||
.pipe(
|
|
||||||
filter(([viewMode, file]) => viewMode === ViewModes.TEXT_HIGHLIGHTS && !file.hasHighlights),
|
|
||||||
tap(() => this._viewModeService.switchToStandard()),
|
|
||||||
)
|
|
||||||
.subscribe();
|
|
||||||
|
|
||||||
this.addActiveScreenSubscription = this._documentViewer.pageComplete$.subscribe(() => {
|
|
||||||
this.#setExcludedPageStyles();
|
|
||||||
});
|
|
||||||
|
|
||||||
this.addActiveScreenSubscription = this._documentViewer.keyUp$.subscribe($event => {
|
this.addActiveScreenSubscription = this._documentViewer.keyUp$.subscribe($event => {
|
||||||
this.handleKeyEvent($event);
|
this.handleKeyEvent($event);
|
||||||
});
|
});
|
||||||
|
|
||||||
this.addActiveScreenSubscription = this.#textSelected$.subscribe();
|
|
||||||
|
|
||||||
this.addActiveScreenSubscription = this.#earmarks$.subscribe();
|
this.addActiveScreenSubscription = this.#earmarks$.subscribe();
|
||||||
this.addActiveScreenSubscription = this.deleteEarmarksOnViewChange$().subscribe();
|
this.addActiveScreenSubscription = this.deleteEarmarksOnViewChange$().subscribe();
|
||||||
|
|
||||||
|
|||||||
@ -23,12 +23,15 @@ export class DocumentInfoService {
|
|||||||
) {
|
) {
|
||||||
this.hidden = computed(() => !this.#show$());
|
this.hidden = computed(() => !this.#show$());
|
||||||
this.shown = this.#show$.asReadonly();
|
this.shown = this.#show$.asReadonly();
|
||||||
effect(() => {
|
effect(
|
||||||
if (this.shown()) {
|
() => {
|
||||||
this._multiSelectService.deactivate();
|
if (this.shown()) {
|
||||||
this._excludedPagesService.hide();
|
this._multiSelectService.deactivate();
|
||||||
}
|
this._excludedPagesService.hide();
|
||||||
});
|
}
|
||||||
|
},
|
||||||
|
{ allowSignalWrites: true },
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
fileAttributes$(fileId: string, dossierId: string, dossierTemplateId: string) {
|
fileAttributes$(fileId: string, dossierId: string, dossierTemplateId: string) {
|
||||||
|
|||||||
@ -15,6 +15,7 @@ import { TranslateService } from '@ngx-translate/core';
|
|||||||
import { DictionariesMapService } from '@services/entity-services/dictionaries-map.service';
|
import { DictionariesMapService } from '@services/entity-services/dictionaries-map.service';
|
||||||
import { DossierDictionariesMapService } from '@services/entity-services/dossier-dictionaries-map.service';
|
import { DossierDictionariesMapService } from '@services/entity-services/dossier-dictionaries-map.service';
|
||||||
import { toSignal } from '@angular/core/rxjs-interop';
|
import { toSignal } from '@angular/core/rxjs-interop';
|
||||||
|
import { ViewModeService } from './view-mode.service';
|
||||||
|
|
||||||
const ONE_MEGABYTE = 1024 * 1024;
|
const ONE_MEGABYTE = 1024 * 1024;
|
||||||
|
|
||||||
@ -64,6 +65,7 @@ export class FilePreviewStateService {
|
|||||||
private readonly _dictionariesMapService: DictionariesMapService,
|
private readonly _dictionariesMapService: DictionariesMapService,
|
||||||
private readonly _translateService: TranslateService,
|
private readonly _translateService: TranslateService,
|
||||||
private readonly _loadingService: LoadingService,
|
private readonly _loadingService: LoadingService,
|
||||||
|
private readonly _viewModeService: ViewModeService,
|
||||||
) {
|
) {
|
||||||
const dossiersService = dossiersServiceResolver(_injector, router);
|
const dossiersService = dossiersServiceResolver(_injector, router);
|
||||||
this.dossier$ = dossiersService.getEntityChanged$(this.dossierId);
|
this.dossier$ = dossiersService.getEntityChanged$(this.dossierId);
|
||||||
@ -82,12 +84,20 @@ export class FilePreviewStateService {
|
|||||||
this.blob$ = this.#blob$;
|
this.blob$ = this.#blob$;
|
||||||
|
|
||||||
this.dossierDictionary = toSignal(this._dossierDictionariesMapService.watch$(this.dossierId, 'dossier_redaction'));
|
this.dossierDictionary = toSignal(this._dossierDictionariesMapService.watch$(this.dossierId, 'dossier_redaction'));
|
||||||
this.#dossierFileChange = toSignal(this.#dossierFilesChange$());
|
this.#dossierFileChange = toSignal(this.#dossierFilesChange$);
|
||||||
effect(() => {
|
effect(() => {
|
||||||
if (this.#dossierFileChange()) {
|
if (this.#dossierFileChange()) {
|
||||||
this._filesService.loadAll(this.dossierId);
|
this._filesService.loadAll(this.dossierId);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
effect(
|
||||||
|
() => {
|
||||||
|
if (this._viewModeService.isEarmarks() && !this.file().hasHighlights) {
|
||||||
|
this._viewModeService.switchToStandard();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ allowSignalWrites: true },
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
get dictionaries(): Dictionary[] {
|
get dictionaries(): Dictionary[] {
|
||||||
@ -113,6 +123,13 @@ export class FilePreviewStateService {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get #dossierFilesChange$() {
|
||||||
|
return this._dossiersService.dossierFileChanges$.pipe(
|
||||||
|
filter(dossierId => dossierId === this.dossierId),
|
||||||
|
map(() => true),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
reloadBlob(): void {
|
reloadBlob(): void {
|
||||||
this.#reloadBlob$.next(true);
|
this.#reloadBlob$.next(true);
|
||||||
}
|
}
|
||||||
@ -127,13 +144,6 @@ export class FilePreviewStateService {
|
|||||||
return `${remainingTime} ${seconds}`;
|
return `${remainingTime} ${seconds}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
#dossierFilesChange$() {
|
|
||||||
return this._dossiersService.dossierFileChanges$.pipe(
|
|
||||||
filter(dossierId => dossierId === this.dossierId),
|
|
||||||
map(() => true),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#downloadOriginalFile(cacheIdentifier: string, wipeCaches = true): Observable<Blob> {
|
#downloadOriginalFile(cacheIdentifier: string, wipeCaches = true): Observable<Blob> {
|
||||||
const downloadFile$ = this.#getFileToDownload(cacheIdentifier);
|
const downloadFile$ = this.#getFileToDownload(cacheIdentifier);
|
||||||
const obs = wipeCaches ? from(wipeCache('files')) : of({});
|
const obs = wipeCaches ? from(wipeCache('files')) : of({});
|
||||||
|
|||||||
@ -184,7 +184,7 @@ export class PdfProxyService {
|
|||||||
|
|
||||||
private _addManualRedactionOfType(type: ManualRedactionEntryType) {
|
private _addManualRedactionOfType(type: ManualRedactionEntryType) {
|
||||||
const selectedQuads: Record<string, Quad[]> = this._pdf.documentViewer.getSelectedTextQuads();
|
const selectedQuads: Record<string, Quad[]> = this._pdf.documentViewer.getSelectedTextQuads();
|
||||||
const text = this._documentViewer.selectedText;
|
const text = this._documentViewer.selectedText();
|
||||||
const manualRedactionEntry = this._getManualRedaction(selectedQuads, text, true);
|
const manualRedactionEntry = this._getManualRedaction(selectedQuads, text, true);
|
||||||
this.manualAnnotationRequested$.next({ manualRedactionEntry, type });
|
this.manualAnnotationRequested$.next({ manualRedactionEntry, type });
|
||||||
}
|
}
|
||||||
@ -208,7 +208,7 @@ export class PdfProxyService {
|
|||||||
this._pdf.enable(TEXT_POPUPS_TO_TOGGLE);
|
this._pdf.enable(TEXT_POPUPS_TO_TOGGLE);
|
||||||
this._viewerHeaderService.enable(HEADER_ITEMS_TO_TOGGLE);
|
this._viewerHeaderService.enable(HEADER_ITEMS_TO_TOGGLE);
|
||||||
|
|
||||||
if (this._documentViewer.selectedText.length > 2) {
|
if (this._documentViewer.selectedText().length > 2) {
|
||||||
this._pdf.enable([TextPopups.ADD_DICTIONARY, TextPopups.ADD_FALSE_POSITIVE]);
|
this._pdf.enable([TextPopups.ADD_DICTIONARY, TextPopups.ADD_FALSE_POSITIVE]);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
<div [style.visibility]="(documentViewer.loaded$ | async) ? 'visible' : 'hidden'" class="pagination noselect">
|
<div [style.visibility]="documentViewer.loaded() ? 'visible' : 'hidden'" class="pagination noselect">
|
||||||
<div (click)="pdf.navigatePreviousPage()">
|
<div (click)="pdf.navigatePreviousPage()">
|
||||||
<mat-icon class="chevron-icon" svgIcon="red:nav-prev"></mat-icon>
|
<mat-icon class="chevron-icon" svgIcon="red:nav-prev"></mat-icon>
|
||||||
</div>
|
</div>
|
||||||
@ -8,7 +8,7 @@
|
|||||||
#pageInput
|
#pageInput
|
||||||
(change)="pdf.navigateTo(pageInput.value)"
|
(change)="pdf.navigateTo(pageInput.value)"
|
||||||
[max]="pdf.totalPages()"
|
[max]="pdf.totalPages()"
|
||||||
[value]="pdf.currentPage$ | async"
|
[value]="pdf.currentPage()"
|
||||||
class="page-number-input"
|
class="page-number-input"
|
||||||
id="currentPageNumber"
|
id="currentPageNumber"
|
||||||
min="1"
|
min="1"
|
||||||
|
|||||||
@ -66,8 +66,7 @@ export class AnnotationDrawService {
|
|||||||
const annotations = annotationWrappers
|
const annotations = annotationWrappers
|
||||||
?.map(annotation => this._computeAnnotation(annotation, hideSkipped, totalPages, dossierTemplateId))
|
?.map(annotation => this._computeAnnotation(annotation, hideSkipped, totalPages, dossierTemplateId))
|
||||||
.filterTruthy();
|
.filterTruthy();
|
||||||
const documentLoaded = await firstValueFrom(this._documentViewer.loaded$);
|
if (!this._documentViewer.loaded()) {
|
||||||
if (!documentLoaded) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
await this._annotationManager.add(annotations);
|
await this._annotationManager.add(annotations);
|
||||||
|
|||||||
@ -1,32 +1,45 @@
|
|||||||
import { inject, Injectable } from '@angular/core';
|
import { inject, Injectable, NgZone, Signal, signal } from '@angular/core';
|
||||||
import { Core } from '@pdftron/webviewer';
|
import { Core } from '@pdftron/webviewer';
|
||||||
import { NGXLogger } from 'ngx-logger';
|
import { NGXLogger } from 'ngx-logger';
|
||||||
import { fromEvent, merge, Observable } from 'rxjs';
|
import { fromEvent, Observable } from 'rxjs';
|
||||||
import { debounceTime, filter, map, tap } from 'rxjs/operators';
|
import { filter, tap } from 'rxjs/operators';
|
||||||
import { ActivatedRoute } from '@angular/router';
|
import { ActivatedRoute } from '@angular/router';
|
||||||
import { PdfViewer } from './pdf-viewer.service';
|
import { PdfViewer } from './pdf-viewer.service';
|
||||||
import { UserPreferenceService } from '@users/user-preference.service';
|
import { UserPreferenceService } from '@users/user-preference.service';
|
||||||
import { log, shareDistinctLast, shareLast } from '@iqser/common-ui';
|
import { log } from '@iqser/common-ui';
|
||||||
import { stopAndPrevent, stopAndPreventIfNotAllowed } from '../utils/functions';
|
import { stopAndPrevent, stopAndPreventIfNotAllowed } from '../utils/functions';
|
||||||
import { RotationType, RotationTypes } from '@red/domain';
|
import { RotationType, RotationTypes } from '@red/domain';
|
||||||
import { AnnotationToolNames } from '../utils/constants';
|
import { AnnotationToolNames } from '../utils/constants';
|
||||||
|
import { toObservable } from '@angular/core/rxjs-interop';
|
||||||
import DocumentViewer = Core.DocumentViewer;
|
import DocumentViewer = Core.DocumentViewer;
|
||||||
import Color = Core.Annotations.Color;
|
import Color = Core.Annotations.Color;
|
||||||
import Quad = Core.Math.Quad;
|
import Quad = Core.Math.Quad;
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class REDDocumentViewer {
|
export class REDDocumentViewer {
|
||||||
loaded$: Observable<boolean>;
|
readonly loaded$: Observable<boolean>;
|
||||||
pageComplete$: Observable<boolean>;
|
|
||||||
keyUp$: Observable<KeyboardEvent>;
|
keyUp$: Observable<KeyboardEvent>;
|
||||||
textSelected$: Observable<string>;
|
readonly selectedText: Signal<string>;
|
||||||
selectedText = '';
|
readonly loaded: Signal<boolean>;
|
||||||
|
readonly pageComplete: Signal<unknown | undefined>;
|
||||||
|
|
||||||
#document: DocumentViewer;
|
#document: DocumentViewer;
|
||||||
|
readonly #loaded = signal(false);
|
||||||
|
readonly #pageComplete = signal<unknown | undefined>(undefined);
|
||||||
|
readonly #selectedText = signal('');
|
||||||
|
|
||||||
readonly #logger = inject(NGXLogger);
|
readonly #logger = inject(NGXLogger);
|
||||||
readonly #userPreferenceService = inject(UserPreferenceService);
|
readonly #userPreferenceService = inject(UserPreferenceService);
|
||||||
readonly #pdf = inject(PdfViewer);
|
readonly #pdf = inject(PdfViewer);
|
||||||
readonly #activatedRoute = inject(ActivatedRoute);
|
readonly #activatedRoute = inject(ActivatedRoute);
|
||||||
|
readonly #ngZone = inject(NgZone);
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.loaded = this.#loaded.asReadonly();
|
||||||
|
this.loaded$ = toObservable(this.#loaded);
|
||||||
|
this.pageComplete = this.#pageComplete.asReadonly();
|
||||||
|
this.selectedText = this.#selectedText.asReadonly();
|
||||||
|
}
|
||||||
|
|
||||||
get PDFDoc() {
|
get PDFDoc() {
|
||||||
return this.document?.getPDFDoc();
|
return this.document?.getPDFDoc();
|
||||||
@ -40,30 +53,6 @@ export class REDDocumentViewer {
|
|||||||
return this.document?.getPageRotation(this.#document.getCurrentPage());
|
return this.document?.getPageRotation(this.#document.getCurrentPage());
|
||||||
}
|
}
|
||||||
|
|
||||||
get #documentUnloaded$() {
|
|
||||||
const event$ = fromEvent(this.#document, 'documentUnloaded');
|
|
||||||
const toBool$ = event$.pipe(map(() => false));
|
|
||||||
|
|
||||||
return toBool$.pipe(tap(() => this.#logger.info('[PDF] Document unloaded')));
|
|
||||||
}
|
|
||||||
|
|
||||||
get #documentLoaded$() {
|
|
||||||
const event$ = fromEvent(this.#document, 'documentLoaded');
|
|
||||||
const toBool$ = event$.pipe(map(() => true));
|
|
||||||
|
|
||||||
return toBool$.pipe(
|
|
||||||
tap(() =>
|
|
||||||
this.#pdf.runWithCleanup(async () => {
|
|
||||||
await this.#flattenAnnotations();
|
|
||||||
this.#setCurrentPage();
|
|
||||||
this.#setInitialDisplayMode();
|
|
||||||
this.updateTooltipsVisibility();
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
tap(() => this.#logger.info('[PDF] Document loaded')),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
get #keyUp$() {
|
get #keyUp$() {
|
||||||
return fromEvent<KeyboardEvent>(this.#document, 'keyUp').pipe(
|
return fromEvent<KeyboardEvent>(this.#document, 'keyUp').pipe(
|
||||||
tap(stopAndPreventIfNotAllowed),
|
tap(stopAndPreventIfNotAllowed),
|
||||||
@ -81,22 +70,6 @@ export class REDDocumentViewer {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
get #pageComplete$() {
|
|
||||||
return fromEvent(this.#document, 'pageComplete').pipe(debounceTime(300));
|
|
||||||
}
|
|
||||||
|
|
||||||
get #textSelected$(): Observable<string> {
|
|
||||||
return fromEvent<[Quad, string, number]>(this.#document, 'textSelected').pipe(
|
|
||||||
tap(([, selectedText]) => (this.selectedText = selectedText)),
|
|
||||||
tap(([, , pageNumber]) => this.#disableTextPopupIfCompareMode(pageNumber)),
|
|
||||||
map(([, selectedText]) => selectedText),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
get #loaded$() {
|
|
||||||
return merge(this.#documentUnloaded$, this.#documentLoaded$).pipe(shareDistinctLast());
|
|
||||||
}
|
|
||||||
|
|
||||||
clearSelection() {
|
clearSelection() {
|
||||||
this.#document.clearSelection();
|
this.#document.clearSelection();
|
||||||
this.#pdf.disable('textPopup');
|
this.#pdf.disable('textPopup');
|
||||||
@ -120,10 +93,8 @@ export class REDDocumentViewer {
|
|||||||
|
|
||||||
init(document: DocumentViewer) {
|
init(document: DocumentViewer) {
|
||||||
this.#document = document;
|
this.#document = document;
|
||||||
this.loaded$ = this.#loaded$;
|
this.#listenForDocEvents();
|
||||||
this.pageComplete$ = this.#pageComplete$.pipe(shareLast());
|
|
||||||
this.keyUp$ = this.#keyUp$;
|
this.keyUp$ = this.#keyUp$;
|
||||||
this.textSelected$ = this.#textSelected$;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async blob() {
|
async blob() {
|
||||||
@ -172,6 +143,45 @@ export class REDDocumentViewer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#listenForDocEvents() {
|
||||||
|
this.#document.addEventListener('textSelected', ([, selectedText, pageNumber]: [Quad, string, number]) => {
|
||||||
|
this.#ngZone.run(() => {
|
||||||
|
this.#disableTextPopupIfCompareMode(pageNumber);
|
||||||
|
this.#selectedText.set(selectedText);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
this.#document.addEventListener('pageComplete', event => {
|
||||||
|
this.#ngZone.run(() => {
|
||||||
|
setTimeout(() => {
|
||||||
|
this.#pageComplete.set(event);
|
||||||
|
}, 300);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
this.#document.addEventListener('documentUnloaded', () => {
|
||||||
|
this.#ngZone.run(() => {
|
||||||
|
this.#logger.info('[PDF] Document unloaded');
|
||||||
|
this.#loaded.set(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
this.#document.addEventListener('documentLoaded', () => {
|
||||||
|
this.#ngZone.run(() => {
|
||||||
|
this.#logger.info('[PDF] Document loaded');
|
||||||
|
|
||||||
|
this.#pdf.runWithCleanup(() => {
|
||||||
|
this.#flattenAnnotations().then();
|
||||||
|
this.#setCurrentPage();
|
||||||
|
this.#setInitialDisplayMode();
|
||||||
|
this.updateTooltipsVisibility();
|
||||||
|
});
|
||||||
|
|
||||||
|
this.#loaded.set(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
async #flattenAnnotations() {
|
async #flattenAnnotations() {
|
||||||
const pdfDoc = await this.PDFDoc;
|
const pdfDoc = await this.PDFDoc;
|
||||||
await pdfDoc.flattenAnnotations(false);
|
await pdfDoc.flattenAnnotations(false);
|
||||||
|
|||||||
@ -162,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: toObservable(this._documentInfoService.shown, { injector: this._injector }),
|
ariaExpanded: toObservable(this._documentInfoService?.shown, { injector: this._injector }),
|
||||||
icon: 'red:status-info',
|
icon: 'red:status-info',
|
||||||
show: !!this._documentInfoService,
|
show: !!this._documentInfoService,
|
||||||
},
|
},
|
||||||
@ -171,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: toObservable(this._excludedPagesService.shown, { injector: this._injector }),
|
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:
|
||||||
|
|||||||
@ -44,7 +44,7 @@
|
|||||||
<iqser-circle-button
|
<iqser-circle-button
|
||||||
(menuClosed)="expanded = false"
|
(menuClosed)="expanded = false"
|
||||||
(menuOpened)="expanded = true"
|
(menuOpened)="expanded = true"
|
||||||
*ngIf="hiddenButtons.length > 0"
|
*ngIf="hiddenButtons?.length > 0"
|
||||||
[attr.aria-expanded]="expanded"
|
[attr.aria-expanded]="expanded"
|
||||||
[icon]="'iqser:more-actions'"
|
[icon]="'iqser:more-actions'"
|
||||||
[matMenuTriggerFor]="hiddenButtonsMenu"
|
[matMenuTriggerFor]="hiddenButtonsMenu"
|
||||||
|
|||||||
@ -19,9 +19,9 @@ import { MatDialog } from '@angular/material/dialog';
|
|||||||
styleUrls: ['./expandable-file-actions.component.scss'],
|
styleUrls: ['./expandable-file-actions.component.scss'],
|
||||||
})
|
})
|
||||||
export class ExpandableFileActionsComponent implements OnChanges {
|
export class ExpandableFileActionsComponent implements OnChanges {
|
||||||
|
@Input({ required: true }) actions: Action[];
|
||||||
@Input() maxWidth: number;
|
@Input() maxWidth: number;
|
||||||
@Input() minWidth: number;
|
@Input() minWidth: number;
|
||||||
@Input() actions: Action[];
|
|
||||||
@Input() buttonType: CircleButtonType;
|
@Input() buttonType: CircleButtonType;
|
||||||
@Input() tooltipPosition: IqserTooltipPosition;
|
@Input() tooltipPosition: IqserTooltipPosition;
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user