diff --git a/apps/red-ui/src/app/app.component.html b/apps/red-ui/src/app/app.component.html
index 20a8fecb2..f7795dfd6 100644
--- a/apps/red-ui/src/app/app.component.html
+++ b/apps/red-ui/src/app/app.component.html
@@ -1,4 +1,9 @@
+
+
+
diff --git a/apps/red-ui/src/app/app.component.ts b/apps/red-ui/src/app/app.component.ts
index 05dec74fc..d63edc783 100644
--- a/apps/red-ui/src/app/app.component.ts
+++ b/apps/red-ui/src/app/app.component.ts
@@ -1,6 +1,7 @@
import { Component, ViewContainerRef } from '@angular/core';
import { RouterHistoryService } from '@services/router-history.service';
import { UserService } from '@services/user.service';
+import { ReusablePdfViewer } from './modules/shared/components/reusable-pdf-viewer/reusable-pdf-viewer.service';
@Component({
selector: 'redaction-root',
@@ -14,5 +15,6 @@ export class AppComponent {
public viewContainerRef: ViewContainerRef,
private readonly _routerHistoryService: RouterHistoryService,
private readonly _userService: UserService,
+ readonly reusablePdfViewer: ReusablePdfViewer,
) {}
}
diff --git a/apps/red-ui/src/app/app.module.ts b/apps/red-ui/src/app/app.module.ts
index 40bb58e12..21308feca 100644
--- a/apps/red-ui/src/app/app.module.ts
+++ b/apps/red-ui/src/app/app.module.ts
@@ -20,7 +20,7 @@ import { AppRoutingModule } from './app-routing.module';
import { SharedModule } from '@shared/shared.module';
import { FileUploadDownloadModule } from '@upload-download/file-upload-download.module';
import { DatePipe as BaseDatePipe, PlatformLocation } from '@angular/common';
-import { ACTIVE_DOSSIERS_SERVICE, ARCHIVED_DOSSIERS_SERVICE, BASE_HREF } from './tokens';
+import { ACTIVE_DOSSIERS_SERVICE, ARCHIVED_DOSSIERS_SERVICE, BASE_HREF, BASE_HREF_FN } from './tokens';
import { MonacoEditorModule } from '@materia-ui/ngx-monaco-editor';
import { GlobalErrorHandler } from '@utils/global-error-handler.service';
import { REDMissingTranslationHandler } from '@utils/missing-translations-handler';
@@ -52,6 +52,7 @@ import { LoggerModule, NgxLoggerLevel, TOKEN_LOGGER_CONFIG, TOKEN_LOGGER_RULES_S
import { LoggerRulesService } from '@services/logger-rules.service';
import { ILoggerConfig } from '@red/domain';
import { SystemPreferencesService } from '@services/system-preferences.service';
+import { ReusablePdfViewerComponent } from './modules/shared/components/reusable-pdf-viewer/reusable-pdf-viewer.component';
export function httpLoaderFactory(httpClient: HttpClient, configService: ConfigService): PruningTranslationLoader {
return new PruningTranslationLoader(httpClient, '/assets/i18n/', `.json?version=${configService.values.FRONTEND_APP_VERSION}`);
@@ -69,7 +70,15 @@ function cleanupBaseUrl(baseUrl: string) {
const screens = [BaseScreenComponent, DownloadsListScreenComponent];
-const components = [AppComponent, AuthErrorComponent, NotificationsComponent, SpotlightSearchComponent, BreadcrumbsComponent, ...screens];
+const components = [
+ AppComponent,
+ ReusablePdfViewerComponent,
+ AuthErrorComponent,
+ NotificationsComponent,
+ SpotlightSearchComponent,
+ BreadcrumbsComponent,
+ ...screens,
+];
@NgModule({
declarations: [...components],
@@ -149,6 +158,11 @@ const components = [AppComponent, AuthErrorComponent, NotificationsComponent, Sp
useFactory: (s: PlatformLocation) => cleanupBaseUrl(s.getBaseHrefFromDOM()),
deps: [PlatformLocation],
},
+ {
+ provide: BASE_HREF_FN,
+ useFactory: (baseHref: string) => (path: string) => baseHref + path,
+ deps: [BASE_HREF],
+ },
{
provide: HTTP_INTERCEPTORS,
multi: true,
diff --git a/apps/red-ui/src/app/modules/file-preview/components/pdf-viewer/pdf-viewer.component.ts b/apps/red-ui/src/app/modules/file-preview/components/pdf-viewer/pdf-viewer.component.ts
index 3f70aabed..2e0dbbd49 100644
--- a/apps/red-ui/src/app/modules/file-preview/components/pdf-viewer/pdf-viewer.component.ts
+++ b/apps/red-ui/src/app/modules/file-preview/components/pdf-viewer/pdf-viewer.component.ts
@@ -43,6 +43,7 @@ import { FileDataService } from '../../services/file-data.service';
import { ViewerHeaderConfigService } from '../../services/viewer-header-config.service';
import { TooltipsService } from '../../services/tooltips.service';
import { ManualRedactionService } from '../../services/manual-redaction.service';
+import { ReusablePdfViewer } from '../../../shared/components/reusable-pdf-viewer/reusable-pdf-viewer.service';
import Tools = Core.Tools;
import TextTool = Tools.TextTool;
import Annotation = Core.Annotations.Annotation;
@@ -84,6 +85,7 @@ export class PdfViewerComponent extends AutoUnsubscribe implements OnInit, OnCha
private readonly _headerConfigService: ViewerHeaderConfigService,
private readonly _tooltipsService: TooltipsService,
private readonly _errorService: ErrorService,
+ private readonly _reusablePdfViewer: ReusablePdfViewer,
readonly stateService: FilePreviewStateService,
readonly viewModeService: ViewModeService,
readonly multiSelectService: MultiSelectService,
@@ -102,6 +104,7 @@ export class PdfViewerComponent extends AutoUnsubscribe implements OnInit, OnCha
withLatestFrom(this.stateService.file$),
tap(() => this._errorService.clear()),
tap(([blob, file]) => this._loadDocument(blob, file)),
+ tap(([blob, file]) => this._reusablePdfViewer.loadDocument(blob, file)),
)
.subscribe();
}
diff --git a/apps/red-ui/src/app/modules/shared/components/reusable-pdf-viewer/reusable-pdf-viewer.component.ts b/apps/red-ui/src/app/modules/shared/components/reusable-pdf-viewer/reusable-pdf-viewer.component.ts
new file mode 100644
index 000000000..bc49c8de7
--- /dev/null
+++ b/apps/red-ui/src/app/modules/shared/components/reusable-pdf-viewer/reusable-pdf-viewer.component.ts
@@ -0,0 +1,27 @@
+import { Component, ElementRef, OnInit, ViewChild } from '@angular/core';
+import { ReusablePdfViewer } from './reusable-pdf-viewer.service';
+
+@Component({
+ selector: 'redaction-reusable-pdf-viewer',
+ template: '
',
+ styles: [
+ `
+ div {
+ width: calc(100% - 350px);
+ height: calc(100% - 111px);
+ bottom: 0;
+ left: 0;
+ position: absolute;
+ }
+ `,
+ ],
+})
+export class ReusablePdfViewerComponent implements OnInit {
+ @ViewChild('viewer', { static: true }) readonly viewer: ElementRef;
+
+ constructor(readonly reusablePdfViewer: ReusablePdfViewer) {}
+
+ ngOnInit() {
+ return this.reusablePdfViewer.init(this.viewer.nativeElement);
+ }
+}
diff --git a/apps/red-ui/src/app/modules/shared/components/reusable-pdf-viewer/reusable-pdf-viewer.service.ts b/apps/red-ui/src/app/modules/shared/components/reusable-pdf-viewer/reusable-pdf-viewer.service.ts
new file mode 100644
index 000000000..80223fffb
--- /dev/null
+++ b/apps/red-ui/src/app/modules/shared/components/reusable-pdf-viewer/reusable-pdf-viewer.service.ts
@@ -0,0 +1,131 @@
+import { Inject, Injectable, Injector } from '@angular/core';
+import WebViewer, { Core, WebViewerInstance, WebViewerOptions } from '@pdftron/webviewer';
+import { environment } from '../../../../../environments/environment';
+import { BASE_HREF_FN, BaseHrefFn } from '../../../../tokens';
+import { File } from '@red/domain';
+import { CustomError, ErrorService, LoadingService, log, shareDistinctLast, shareLast } from '@iqser/common-ui';
+import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker/';
+import { ActivatedRoute, ActivationStart, Router } from '@angular/router';
+import { filter, map } from 'rxjs/operators';
+import { fromEvent, merge, Observable } from 'rxjs';
+import { ConfigService } from '../../../../services/config.service';
+import DocumentViewer = Core.DocumentViewer;
+import AnnotationManager = Core.AnnotationManager;
+import TextTool = Core.Tools.TextTool;
+
+const DocLoadingError = new CustomError(_('error.file-preview.label'), _('error.file-preview.action'), 'iqser:refresh');
+const uselessElements = [
+ 'pageNavOverlay',
+ 'menuButton',
+ 'selectToolButton',
+ 'textHighlightToolButton',
+ 'textUnderlineToolButton',
+ 'textSquigglyToolButton',
+ 'textStrikeoutToolButton',
+ 'viewControlsButton',
+ 'contextMenuPopup',
+ 'linkButton',
+ 'toggleNotesButton',
+ 'notesPanel',
+ 'thumbnailControl',
+ 'documentControl',
+ 'ribbons',
+ 'toolsHeader',
+ 'rotateClockwiseButton',
+ 'rotateCounterClockwiseButton',
+ 'annotationStyleEditButton',
+ 'annotationGroupButton',
+];
+
+@Injectable({
+ providedIn: 'root',
+})
+export class ReusablePdfViewer {
+ readonly currentPage$ = this._injector.get(ActivatedRoute).queryParamMap.pipe(
+ map(params => Number(params.get('page') ?? '1')),
+ shareDistinctLast(),
+ );
+ documentViewer: DocumentViewer;
+ annotationManager: AnnotationManager;
+
+ show$: Observable;
+ documentLoaded$: Observable;
+ #instance: WebViewerInstance;
+
+ constructor(
+ @Inject(BASE_HREF_FN) private readonly _convertPath: BaseHrefFn,
+ private readonly _loadingService: LoadingService,
+ private readonly _errorService: ErrorService,
+ private readonly _router: Router,
+ private readonly _configService: ConfigService,
+ private readonly _injector: Injector,
+ ) {}
+
+ get #documentLoaded$() {
+ const docLoadedEvent = this.#instance.UI.Events.DOCUMENT_LOADED;
+ const event$ = fromEvent(this.documentViewer, docLoadedEvent).pipe(map(() => true));
+
+ return event$.pipe(log('[PDF] Document loaded'), shareLast());
+ }
+
+ get #show$() {
+ const routeChanged = this._router.events.pipe(
+ filter(event => event instanceof ActivationStart),
+ map(() => false),
+ );
+
+ return merge(routeChanged, this.documentLoaded$);
+ }
+
+ async init(htmlElement: HTMLElement) {
+ this.#instance = await this.#getInstance(htmlElement);
+ console.log('[PDF] Initialized');
+
+ this.documentViewer = this.#instance.Core.documentViewer;
+ this.annotationManager = this.#instance.Core.annotationManager;
+
+ this.documentLoaded$ = this.#documentLoaded$;
+ this.show$ = this.#show$;
+ this.#setSelectionMode();
+ this.#configureElements();
+ }
+
+ loadDocument(blob: Blob, file: File) {
+ console.log('[PDF] Loading document', blob, file);
+ const onError = () => {
+ this._loadingService.stop();
+ this._errorService.set(DocLoadingError);
+ // this.stateService.reloadBlob();
+ };
+ // const pdfNet = this._instance.Core.PDFNet;
+ // console.log('pdfnet', pdfNet);
+ // await pdfNet.initialize(environment.licenseKey ? atob(environment.licenseKey) : null);
+ // console.log('pdfnet initialized');
+ // const document = await pdfNet.PDFDoc.createFromBuffer(await blob.arrayBuffer());
+ // console.log('document initialized');
+ // await document.flattenAnnotations(false);
+ // console.log(document);
+ this.#instance.UI.loadDocument(blob, { filename: file?.filename + '.pdf' ?? 'document.pdf', onError });
+ }
+
+ #configureElements() {
+ this.#instance.UI.disableElements(uselessElements);
+ }
+
+ #setSelectionMode(): void {
+ const textTool = this.#instance.Core.Tools.TextTool as unknown as TextTool;
+ textTool.SELECTION_MODE = this._configService.values.SELECTION_MODE;
+ }
+
+ #getInstance(htmlElement: HTMLElement) {
+ const options: WebViewerOptions = {
+ licenseKey: environment.licenseKey ? atob(environment.licenseKey) : null,
+ fullAPI: true,
+ path: this._convertPath('/assets/wv-resources'),
+ css: this._convertPath('/assets/pdftron/stylesheet.css'),
+ backendType: 'ems',
+ };
+
+ return WebViewer(options, htmlElement);
+ }
+}
diff --git a/apps/red-ui/src/app/tokens.ts b/apps/red-ui/src/app/tokens.ts
index ce8f6a595..d19e040af 100644
--- a/apps/red-ui/src/app/tokens.ts
+++ b/apps/red-ui/src/app/tokens.ts
@@ -1,7 +1,9 @@
import { InjectionToken } from '@angular/core';
-export const BASE_HREF: InjectionToken = new InjectionToken('BASE_HREF');
-export const DOSSIER_ID: InjectionToken = new InjectionToken('DOSSIER_ID');
+export const BASE_HREF = new InjectionToken('BASE_HREF');
+export type BaseHrefFn = (path: string) => string;
+export const BASE_HREF_FN = new InjectionToken('Convert path function');
+export const DOSSIER_ID = new InjectionToken('DOSSIER_ID');
export const ACTIVE_DOSSIERS_SERVICE = new InjectionToken('Active dossiers service');
export const ARCHIVED_DOSSIERS_SERVICE = new InjectionToken('Archived dossiers service');