RED-6412: pdf actions run with cleanup
This commit is contained in:
parent
07abfefe56
commit
a7eca09d8e
@ -146,6 +146,7 @@ const routes: IqserRoutes = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'downloads',
|
path: 'downloads',
|
||||||
|
// TODO: transform into a lazy loaded module
|
||||||
component: DownloadsListScreenComponent,
|
component: DownloadsListScreenComponent,
|
||||||
canActivate: [CompositeRouteGuard, IqserPermissionsGuard],
|
canActivate: [CompositeRouteGuard, IqserPermissionsGuard],
|
||||||
data: {
|
data: {
|
||||||
@ -224,7 +225,7 @@ const routes: IqserRoutes = [
|
|||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [RouterModule.forRoot(routes, { scrollPositionRestoration: 'enabled' })],
|
imports: [RouterModule.forRoot(routes, { scrollPositionRestoration: 'enabled' })],
|
||||||
providers: [{ provide: RouteReuseStrategy, useClass: CustomRouteReuseStrategy }],
|
providers: [{ provide: RouteReuseStrategy, useExisting: CustomRouteReuseStrategy }],
|
||||||
exports: [RouterModule],
|
exports: [RouterModule],
|
||||||
})
|
})
|
||||||
export class AppRoutingModule {}
|
export class AppRoutingModule {}
|
||||||
|
|||||||
@ -40,7 +40,7 @@ import { AnnotationDrawService } from '../pdf-viewer/services/annotation-draw.se
|
|||||||
import { AnnotationProcessingService } from './services/annotation-processing.service';
|
import { AnnotationProcessingService } from './services/annotation-processing.service';
|
||||||
import { Dictionary, File, ViewModes } from '@red/domain';
|
import { Dictionary, File, ViewModes } from '@red/domain';
|
||||||
import { PermissionsService } from '@services/permissions.service';
|
import { PermissionsService } from '@services/permissions.service';
|
||||||
import { combineLatest, firstValueFrom, from, Observable, of, pairwise } from 'rxjs';
|
import { combineLatest, firstValueFrom, Observable, of, pairwise } from 'rxjs';
|
||||||
import { PreferencesKeys, UserPreferenceService } from '@users/user-preference.service';
|
import { PreferencesKeys, UserPreferenceService } from '@users/user-preference.service';
|
||||||
import { byId, byPage, download, handleFilterDelta, hasChanges } from '../../utils';
|
import { byId, byPage, download, handleFilterDelta, hasChanges } from '../../utils';
|
||||||
import { FilesService } from '@services/files/files.service';
|
import { FilesService } from '@services/files/files.service';
|
||||||
@ -617,9 +617,8 @@ export class FilePreviewScreenComponent
|
|||||||
|
|
||||||
this.addActiveScreenSubscription = this.state.blob$
|
this.addActiveScreenSubscription = this.state.blob$
|
||||||
.pipe(
|
.pipe(
|
||||||
switchMap(blob => from(this._documentViewer.lock()).pipe(map(() => blob))),
|
|
||||||
tap(() => this._errorService.clear()),
|
tap(() => this._errorService.clear()),
|
||||||
tap(blob => this.pdf.loadDocument(blob, this.state.file, () => this.state.reloadBlob())),
|
switchMap(blob => this.pdf.loadDocument(blob, this.state.file, () => this.state.reloadBlob())),
|
||||||
)
|
)
|
||||||
.subscribe();
|
.subscribe();
|
||||||
|
|
||||||
|
|||||||
@ -36,7 +36,7 @@ export class AnnotationDrawService {
|
|||||||
|
|
||||||
async draw(annotations: List<AnnotationWrapper>, hideSkipped: boolean, dossierTemplateId: string) {
|
async draw(annotations: List<AnnotationWrapper>, hideSkipped: boolean, dossierTemplateId: string) {
|
||||||
try {
|
try {
|
||||||
await this._draw(annotations, hideSkipped, dossierTemplateId);
|
await this._pdf.runWithCleanup(async () => await this._draw(annotations, hideSkipped, dossierTemplateId));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,12 +1,12 @@
|
|||||||
import { inject, Injectable } from '@angular/core';
|
import { inject, Injectable } 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, merge, Observable, Subject } from 'rxjs';
|
||||||
import { debounceTime, filter, map, tap } from 'rxjs/operators';
|
import { debounceTime, filter, map, 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, shareLast } from '@iqser/common-ui';
|
import { log, shareDistinctLast, shareLast } 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';
|
||||||
@ -23,6 +23,8 @@ export class REDDocumentViewer {
|
|||||||
selectedText = '';
|
selectedText = '';
|
||||||
#document: DocumentViewer;
|
#document: DocumentViewer;
|
||||||
|
|
||||||
|
readonly #documentClosed$ = new Subject<undefined>();
|
||||||
|
|
||||||
readonly #logger = inject(NGXLogger);
|
readonly #logger = inject(NGXLogger);
|
||||||
readonly #userPreferenceService = inject(UserPreferenceService);
|
readonly #userPreferenceService = inject(UserPreferenceService);
|
||||||
readonly #pdf = inject(PdfViewer);
|
readonly #pdf = inject(PdfViewer);
|
||||||
@ -37,7 +39,7 @@ export class REDDocumentViewer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
get #documentUnloaded$() {
|
get #documentUnloaded$() {
|
||||||
const event$ = fromEvent(this.#document, 'documentUnloaded');
|
const event$ = merge(fromEvent(this.#document, 'documentUnloaded'), this.#documentClosed$);
|
||||||
const toBool$ = event$.pipe(map(() => false));
|
const toBool$ = event$.pipe(map(() => false));
|
||||||
|
|
||||||
return toBool$.pipe(tap(() => this.#logger.info('[PDF] Document unloaded')));
|
return toBool$.pipe(tap(() => this.#logger.info('[PDF] Document unloaded')));
|
||||||
@ -48,10 +50,14 @@ export class REDDocumentViewer {
|
|||||||
const toBool$ = event$.pipe(map(() => true));
|
const toBool$ = event$.pipe(map(() => true));
|
||||||
|
|
||||||
return toBool$.pipe(
|
return toBool$.pipe(
|
||||||
tap(() => this.#flattenAnnotations()),
|
tap(() =>
|
||||||
tap(() => this.#setCurrentPage()),
|
this.#pdf.runWithCleanup(async () => {
|
||||||
tap(() => this.#setInitialDisplayMode()),
|
await this.#flattenAnnotations();
|
||||||
tap(() => this.updateTooltipsVisibility()),
|
this.#setCurrentPage();
|
||||||
|
this.#setInitialDisplayMode();
|
||||||
|
this.updateTooltipsVisibility();
|
||||||
|
}),
|
||||||
|
),
|
||||||
tap(() => this.#logger.info('[PDF] Document loaded')),
|
tap(() => this.#logger.info('[PDF] Document loaded')),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -86,7 +92,7 @@ export class REDDocumentViewer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
get #loaded$() {
|
get #loaded$() {
|
||||||
return merge(this.#documentUnloaded$, this.#documentLoaded$).pipe(shareLast());
|
return merge(this.#documentUnloaded$, this.#documentLoaded$).pipe(shareDistinctLast());
|
||||||
}
|
}
|
||||||
|
|
||||||
clearSelection() {
|
clearSelection() {
|
||||||
@ -95,9 +101,16 @@ export class REDDocumentViewer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
close() {
|
close() {
|
||||||
this.#logger.info('[PDF] Closing document');
|
this.#documentClosed$.next(undefined);
|
||||||
this.#document.closeDocument();
|
|
||||||
this.#pdf.closeCompareMode();
|
const closeAction = async () => {
|
||||||
|
this.#logger.info('[PDF] Closing document');
|
||||||
|
this.#document.closeDocument();
|
||||||
|
this.#pdf.closeCompareMode();
|
||||||
|
await this.#pdf.instance.UI.closeDocument();
|
||||||
|
};
|
||||||
|
|
||||||
|
this.#pdf.runWithCleanup(closeAction).then();
|
||||||
}
|
}
|
||||||
|
|
||||||
updateTooltipsVisibility(): void {
|
updateTooltipsVisibility(): void {
|
||||||
@ -113,17 +126,6 @@ export class REDDocumentViewer {
|
|||||||
this.textSelected$ = this.#textSelected$;
|
this.textSelected$ = this.#textSelected$;
|
||||||
}
|
}
|
||||||
|
|
||||||
async lock() {
|
|
||||||
const document = await this.PDFDoc;
|
|
||||||
if (!document) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
await document.lock();
|
|
||||||
this.#logger.info('[PDF] Locked');
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
async blob() {
|
async blob() {
|
||||||
const data = await this.document.getFileData();
|
const data = await this.document.getFileData();
|
||||||
return new Blob([new Uint8Array(data)], { type: 'application/pdf' });
|
return new Blob([new Uint8Array(data)], { type: 'application/pdf' });
|
||||||
|
|||||||
@ -1,11 +1,10 @@
|
|||||||
import { Inject, Injectable, Injector } from '@angular/core';
|
import { inject, Inject, Injectable, Injector } from '@angular/core';
|
||||||
import WebViewer, { Core, WebViewerInstance, WebViewerOptions } from '@pdftron/webviewer';
|
import WebViewer, { Core, WebViewerInstance, WebViewerOptions } from '@pdftron/webviewer';
|
||||||
import { BASE_HREF_FN, BaseHrefFn, ErrorService, shareDistinctLast } from '@iqser/common-ui';
|
import { BASE_HREF_FN, BaseHrefFn, ErrorService, getConfig, shareDistinctLast } from '@iqser/common-ui';
|
||||||
import { File, IHeaderElement } from '@red/domain';
|
import { AppConfig, File, IHeaderElement } from '@red/domain';
|
||||||
import { ActivatedRoute } from '@angular/router';
|
import { ActivatedRoute } from '@angular/router';
|
||||||
import { map, startWith } from 'rxjs/operators';
|
import { map, startWith } from 'rxjs/operators';
|
||||||
import { BehaviorSubject, combineLatest, fromEvent, Observable, switchMap } from 'rxjs';
|
import { BehaviorSubject, combineLatest, fromEvent, Observable, switchMap } from 'rxjs';
|
||||||
import { ConfigService } from '@services/config.service';
|
|
||||||
import { NGXLogger } from 'ngx-logger';
|
import { NGXLogger } from 'ngx-logger';
|
||||||
import { DISABLED_HOTKEYS, DOCUMENT_LOADING_ERROR, SEARCH_OPTIONS, USELESS_ELEMENTS } from '../utils/constants';
|
import { DISABLED_HOTKEYS, DOCUMENT_LOADING_ERROR, SEARCH_OPTIONS, USELESS_ELEMENTS } from '../utils/constants';
|
||||||
import { Rgb } from '../utils/types';
|
import { Rgb } from '../utils/types';
|
||||||
@ -22,7 +21,7 @@ import Quad = Core.Math.Quad;
|
|||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class PdfViewer {
|
export class PdfViewer {
|
||||||
readonly currentPage$ = this._activatedRoute.queryParamMap.pipe(
|
readonly currentPage$ = inject(ActivatedRoute).queryParamMap.pipe(
|
||||||
map(params => Number(params.get('page') ?? '1')),
|
map(params => Number(params.get('page') ?? '1')),
|
||||||
shareDistinctLast(),
|
shareDistinctLast(),
|
||||||
);
|
);
|
||||||
@ -37,11 +36,13 @@ export class PdfViewer {
|
|||||||
totalPages$: Observable<number>;
|
totalPages$: Observable<number>;
|
||||||
|
|
||||||
#instance: WebViewerInstance;
|
#instance: WebViewerInstance;
|
||||||
|
readonly #licenseKey = inject(LicenseService).activeLicenseKey;
|
||||||
|
readonly #config = getConfig<AppConfig>();
|
||||||
readonly #compareMode$ = new BehaviorSubject(false);
|
readonly #compareMode$ = new BehaviorSubject(false);
|
||||||
readonly #searchButton: IHeaderElement = {
|
readonly #searchButton: IHeaderElement = {
|
||||||
type: 'actionButton',
|
type: 'actionButton',
|
||||||
img: this._convertPath('/assets/icons/general/pdftron-action-search.svg'),
|
img: this._convertPath('/assets/icons/general/pdftron-action-search.svg'),
|
||||||
title: this._translateService.instant('pdf-viewer.text-popup.actions.search'),
|
title: inject(TranslateService).instant('pdf-viewer.text-popup.actions.search'),
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
this.#instance.UI.openElements(['searchPanel']);
|
this.#instance.UI.openElements(['searchPanel']);
|
||||||
setTimeout(() => this.#searchForSelectedText(), 250);
|
setTimeout(() => this.#searchForSelectedText(), 250);
|
||||||
@ -51,10 +52,7 @@ export class PdfViewer {
|
|||||||
constructor(
|
constructor(
|
||||||
private readonly _logger: NGXLogger,
|
private readonly _logger: NGXLogger,
|
||||||
private readonly _injector: Injector,
|
private readonly _injector: Injector,
|
||||||
private readonly _activatedRoute: ActivatedRoute,
|
|
||||||
private readonly _licenseService: LicenseService,
|
|
||||||
private readonly _errorService: ErrorService,
|
private readonly _errorService: ErrorService,
|
||||||
private readonly _translateService: TranslateService,
|
|
||||||
private readonly _userPreferenceService: UserPreferenceService,
|
private readonly _userPreferenceService: UserPreferenceService,
|
||||||
@Inject(BASE_HREF_FN) private readonly _convertPath: BaseHrefFn,
|
@Inject(BASE_HREF_FN) private readonly _convertPath: BaseHrefFn,
|
||||||
) {}
|
) {}
|
||||||
@ -154,26 +152,28 @@ export class PdfViewer {
|
|||||||
this.#instance.Core.setCustomFontURL('https://' + window.location.host + this._convertPath('/assets/pdftron'));
|
this.#instance.Core.setCustomFontURL('https://' + window.location.host + this._convertPath('/assets/pdftron'));
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
await this.runWithCleanup(async () => {
|
||||||
await this.PDFNet.initialize(this._licenseService.activeLicenseKey);
|
try {
|
||||||
} catch (e) {
|
await this.PDFNet.initialize(this.#licenseKey);
|
||||||
this._errorService.set(e);
|
} catch (e) {
|
||||||
throw e;
|
this._errorService.set(e);
|
||||||
}
|
throw e;
|
||||||
|
}
|
||||||
|
|
||||||
this.#instance.UI.setTheme(this._userPreferenceService.getTheme());
|
this.#instance.UI.setTheme(this._userPreferenceService.getTheme());
|
||||||
this._logger.info('[PDF] Initialized');
|
this._logger.info('[PDF] Initialized');
|
||||||
|
|
||||||
this.documentViewer = this.#instance.Core.documentViewer;
|
this.documentViewer = this.#instance.Core.documentViewer;
|
||||||
|
|
||||||
this.compareMode$ = this.#compareMode$.asObservable();
|
this.compareMode$ = this.#compareMode$.asObservable();
|
||||||
this.pageChanged$ = this.#pageChanged$.pipe(shareDistinctLast());
|
this.pageChanged$ = this.#pageChanged$.pipe(shareDistinctLast());
|
||||||
this.totalPages$ = this.#totalPages$.pipe(shareDistinctLast());
|
this.totalPages$ = this.#totalPages$.pipe(shareDistinctLast());
|
||||||
this.#setSelectionMode();
|
this.#setSelectionMode();
|
||||||
this.#configureElements();
|
this.#configureElements();
|
||||||
this.#disableHotkeys();
|
this.#disableHotkeys();
|
||||||
this.#clearSearchResultsWhenVisibilityChanged();
|
this.#clearSearchResultsWhenVisibilityChanged();
|
||||||
this.#listenForCommandF();
|
this.#listenForCommandF();
|
||||||
|
});
|
||||||
|
|
||||||
return this.#instance;
|
return this.#instance;
|
||||||
}
|
}
|
||||||
@ -196,6 +196,10 @@ export class PdfViewer {
|
|||||||
this.#compareMode$.next(false);
|
this.#compareMode$.next(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
runWithCleanup(action: () => Promise<void> | void) {
|
||||||
|
return this.PDFNet.runWithCleanup(action, this.#licenseKey);
|
||||||
|
}
|
||||||
|
|
||||||
async loadDocument(blob: Blob, file: File, actionOnError?: () => void) {
|
async loadDocument(blob: Blob, file: File, actionOnError?: () => void) {
|
||||||
const onError = () => {
|
const onError = () => {
|
||||||
this._injector.get(ErrorService).set(DOCUMENT_LOADING_ERROR);
|
this._injector.get(ErrorService).set(DOCUMENT_LOADING_ERROR);
|
||||||
@ -208,7 +212,11 @@ export class PdfViewer {
|
|||||||
|
|
||||||
this._logger.info('[PDF] Loading document...');
|
this._logger.info('[PDF] Loading document...');
|
||||||
|
|
||||||
this.#instance.UI.loadDocument(blob, { documentId: file.fileId, filename: file?.filename ?? 'document.pdf', onError });
|
await this.runWithCleanup(async () => {
|
||||||
|
const document = await this.documentViewer.getDocument()?.getPDFDoc();
|
||||||
|
await document?.lock();
|
||||||
|
this.#instance.UI.loadDocument(blob, { documentId: file.fileId, filename: file?.filename ?? 'document.pdf', onError });
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
quad(x1: number, y1: number, x2: number, y2: number, x3: number, y3: number, x4: number, y4: number) {
|
quad(x1: number, y1: number, x2: number, y2: number, x3: number, y3: number, x4: number, y4: number) {
|
||||||
@ -307,13 +315,12 @@ export class PdfViewer {
|
|||||||
|
|
||||||
#setSelectionMode(): void {
|
#setSelectionMode(): void {
|
||||||
const textTool = this.#instance.Core.Tools.TextTool as unknown as TextTool;
|
const textTool = this.#instance.Core.Tools.TextTool as unknown as TextTool;
|
||||||
const configService = this._injector.get(ConfigService);
|
textTool.SELECTION_MODE = this.#config.SELECTION_MODE;
|
||||||
textTool.SELECTION_MODE = configService.values.SELECTION_MODE;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#getInstance(htmlElement: HTMLElement) {
|
#getInstance(htmlElement: HTMLElement) {
|
||||||
const options: WebViewerOptions = {
|
const options: WebViewerOptions = {
|
||||||
licenseKey: this._licenseService.activeLicenseKey,
|
licenseKey: this.#licenseKey,
|
||||||
fullAPI: true,
|
fullAPI: true,
|
||||||
path: this._convertPath('/assets/wv-resources'),
|
path: this._convertPath('/assets/wv-resources'),
|
||||||
css: this._convertPath('/assets/pdftron/stylesheet.css'),
|
css: this._convertPath('/assets/pdftron/stylesheet.css'),
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user