red-ui/apps/red-ui/src/app/modules/pdf-viewer/services/viewer-header.service.ts
Valentin Mihai dbc609765b WIP on master
RED-8748 - Integrated component view in DocuMine
2024-07-29 01:02:46 +03:00

409 lines
17 KiB
TypeScript

import { inject, Injectable, NgZone } from '@angular/core';
import { getConfig, HelpModeService, IqserPermissionsService, isIqserDevMode } from '@iqser/common-ui';
import { TranslateService } from '@ngx-translate/core';
import { IHeaderElement, RotationTypes } from '@red/domain';
import { FilesMapService } from '@services/files/files-map.service';
import { Roles } from '@users/roles';
import { fromEvent, merge, Observable, Subject } from 'rxjs';
import { filter, map, switchMap, tap } from 'rxjs/operators';
import { HeaderElements, HeaderElementType } from '../../file-preview/utils/constants';
import { ROTATION_ACTION_BUTTONS, ROTATION_BUTTONS, ViewerEvents } from '../utils/constants';
import { ViewerEvent, VisibilityChangedEvent } from '../utils/types';
import { REDDocumentViewer } from './document-viewer.service';
import { LayersService } from './layers.service';
import { PageRotationService } from './page-rotation.service';
import { PdfViewer } from './pdf-viewer.service';
import { ReadableRedactionsService } from './readable-redactions.service';
import { TooltipsService } from './tooltips.service';
import { UI_ROOT_PATH_FN } from '@common-ui/utils';
const divider: IHeaderElement = {
type: 'divider',
};
@Injectable()
export class ViewerHeaderService {
readonly events$: Observable<ViewerEvent>;
toggleLoadAnnotations$: Observable<boolean>;
#convertPath = inject(UI_ROOT_PATH_FN);
readonly #iqserPermissionService = inject(IqserPermissionsService);
readonly #isDocumine = getConfig().IS_DOCUMINE;
#buttons: Map<HeaderElementType, IHeaderElement>;
readonly #panels = ['thumbnailsPanel', 'outlinesPanel', 'layersPanel', 'signaturePanel'];
#docBeforeCompare: Blob;
readonly #events$ = new Subject<ViewerEvent>();
readonly #config = new Map<HeaderElementType, boolean>([
[HeaderElements.SHAPE_TOOL_GROUP_BUTTON, !this.#iqserPermissionService.has(Roles.getRss)],
[HeaderElements.TOGGLE_TOOLTIPS, true],
[HeaderElements.TOGGLE_LAYERS, isIqserDevMode() || (this.#isDocumine && this.#iqserPermissionService.has(Roles.rules.write))],
[HeaderElements.TOGGLE_READABLE_REDACTIONS, false],
[HeaderElements.LOAD_ALL_ANNOTATIONS, false],
[HeaderElements.COMPARE_BUTTON, !this.#isDocumine],
[HeaderElements.CLOSE_COMPARE_BUTTON, false],
[HeaderElements.ROTATE_LEFT_BUTTON, false],
[HeaderElements.ROTATE_RIGHT_BUTTON, false],
[HeaderElements.APPLY_ROTATION, false],
[HeaderElements.DISCARD_ROTATION, false],
]);
constructor(
private readonly _filesMapService: FilesMapService,
private readonly _translateService: TranslateService,
private readonly _pdf: PdfViewer,
private readonly _documentViewer: REDDocumentViewer,
private readonly _rotationService: PageRotationService,
private readonly _tooltipsService: TooltipsService,
private readonly _layersService: LayersService,
private readonly _readableRedactionsService: ReadableRedactionsService,
private readonly _ngZone: NgZone,
private readonly _helpModeService: HelpModeService,
) {
this.events$ = this.#events$.asObservable();
}
get expandedPanelEvent$() {
const visibilityEvent$ = fromEvent(this._pdf.instance?.UI, this._pdf.instance.UI?.Events.VISIBILITY_CHANGED);
return visibilityEvent$.pipe(
map<CustomEvent, VisibilityChangedEvent>(event => event.detail),
filter(event => this.#panels.includes(event.element)),
map(event => event.isVisible),
);
}
get layersUpdated() {
const documentListener$ = this._documentViewer.loaded$.pipe(
filter(Boolean),
switchMap(() => fromEvent(this._documentViewer.document, 'layersUpdated')),
);
return documentListener$.pipe(
tap(async () => {
const layers = await this._documentViewer.document.getLayersArray();
const layersVisible = layers.filter(layer => layer.name === 'Layout grid').every(layer => layer.visible);
this._layersService.active.set(layersVisible);
}),
);
}
get #rectangle(): IHeaderElement {
return {
type: 'toolGroupButton',
toolGroup: 'rectangleTools',
dataElement: HeaderElements.SHAPE_TOOL_GROUP_BUTTON,
img: this.#convertPath('/assets/icons/general/pdftron-rectangle.svg'),
title: 'annotation.rectangle',
};
}
get #toggleTooltips(): IHeaderElement {
return {
type: 'actionButton',
element: HeaderElements.TOGGLE_TOOLTIPS,
dataElement: HeaderElements.TOGGLE_TOOLTIPS,
title: this._tooltipsService.toggleTooltipsBtnTitle,
img: this.#convertPath('/assets/icons/general/pdftron-action-enable-tooltips.svg'),
onClick: () => this._ngZone.run(() => this._tooltipsService.toggleTooltips()),
};
}
get #toggleLayers(): IHeaderElement {
return {
type: 'actionButton',
element: HeaderElements.TOGGLE_LAYERS,
dataElement: HeaderElements.TOGGLE_LAYERS,
title: this._layersService.toggleLayersBtnTitle,
img: this._layersService.toggleLayersBtnIcon,
onClick: () => this._ngZone.run(() => this._layersService.toggleLayers()),
};
}
get #toggleReadableRedactions(): IHeaderElement {
return {
type: 'actionButton',
element: HeaderElements.TOGGLE_READABLE_REDACTIONS,
dataElement: HeaderElements.TOGGLE_READABLE_REDACTIONS,
title: this._readableRedactionsService.toggleReadableRedactionsBtnTitle,
img: this._readableRedactionsService.toggleReadableRedactionsBtnIcon,
onClick: () => this._ngZone.run(() => this._readableRedactionsService.toggleReadableRedactions()),
};
}
get #loadAllAnnotations(): IHeaderElement {
return {
type: 'actionButton',
title: this._translateService.instant('viewer-header.load-all-annotations'),
img: this.#convertPath('/assets/icons/general/pdftron-action-load-all-annotations.svg'),
onClick: () => this._ngZone.run(() => this.#events$.next({ type: ViewerEvents.LOAD_ALL_ANNOTATIONS })),
dataElement: HeaderElements.LOAD_ALL_ANNOTATIONS,
};
}
get #rotateLeft(): IHeaderElement {
return {
type: 'actionButton',
element: HeaderElements.ROTATE_LEFT_BUTTON,
dataElement: HeaderElements.ROTATE_LEFT_BUTTON,
img: this.#convertPath('/assets/icons/general/rotate-left.svg'),
title: 'Rotate page left',
onClick: () =>
this._ngZone.run(() => {
this._rotationService.addRotation(RotationTypes.LEFT);
this.#toggleRotationActionButtons();
}),
};
}
get #applyRotation(): IHeaderElement {
return {
type: 'customElement',
dataElement: HeaderElements.APPLY_ROTATION,
render: () => {
const paragraph = document.createElement('p');
paragraph.innerText = this._translateService.instant('page-rotation.apply');
paragraph.style.cssText = `
font-size: 11px;
font-weight: 600;
color: #DD4D50;
cursor: pointer;
margin: 0 12px;
`;
paragraph.addEventListener('click', () =>
this._ngZone.run(async () => {
await this._rotationService.applyRotation();
this.disable(ROTATION_ACTION_BUTTONS);
}),
);
return paragraph;
},
};
}
get #discardRotationButton(): IHeaderElement {
return {
type: 'customElement',
dataElement: HeaderElements.DISCARD_ROTATION,
render: () => {
const paragraph = document.createElement('p');
paragraph.innerText = this._translateService.instant('page-rotation.discard');
paragraph.style.cssText = `
font-size: 11px;
font-weight: 600;
color: #283241;
cursor: pointer;
opacity: 0.7;
`;
paragraph.addEventListener('click', () => this._ngZone.run(() => this.#discardRotation()));
return paragraph;
},
};
}
get #rotateRight(): IHeaderElement {
return {
type: 'actionButton',
element: HeaderElements.ROTATE_RIGHT_BUTTON,
dataElement: HeaderElements.ROTATE_RIGHT_BUTTON,
img: this.#convertPath('/assets/icons/general/rotate-right.svg'),
title: 'Rotate page right',
onClick: () =>
this._ngZone.run(() => {
this._rotationService.addRotation(RotationTypes.RIGHT);
this.#toggleRotationActionButtons();
}),
};
}
get #compare(): IHeaderElement {
return {
type: 'actionButton',
element: HeaderElements.COMPARE_BUTTON,
dataElement: HeaderElements.COMPARE_BUTTON,
img: this.#convertPath('/assets/icons/general/pdftron-action-compare.svg'),
title: 'Compare',
onClick: () =>
this._ngZone.run(async () => {
document.getElementById('compareFileInput').click();
this.#docBeforeCompare = await this._documentViewer.blob();
}),
};
}
get #closeCompare(): IHeaderElement {
return {
type: 'actionButton',
element: HeaderElements.CLOSE_COMPARE_BUTTON,
dataElement: HeaderElements.CLOSE_COMPARE_BUTTON,
img: this.#convertPath('/assets/icons/general/pdftron-action-compare.svg'),
title: 'Leave Compare Mode',
onClick: () => this._ngZone.run(() => this.#closeCompareMode()),
};
}
get #toggleLoadAnnotations$() {
return merge(this.expandedPanelEvent$, this._helpModeService.isHelpModeActive$).pipe(
tap(enable =>
enable ? this.enable([HeaderElements.LOAD_ALL_ANNOTATIONS]) : this.disable([HeaderElements.LOAD_ALL_ANNOTATIONS]),
),
);
}
init(): void {
this.#buttons = new Map([
[HeaderElements.SHAPE_TOOL_GROUP_BUTTON, this.#rectangle],
[HeaderElements.ROTATE_LEFT_BUTTON, this.#rotateLeft],
[HeaderElements.ROTATE_RIGHT_BUTTON, this.#rotateRight],
[HeaderElements.APPLY_ROTATION, this.#applyRotation],
[HeaderElements.DISCARD_ROTATION, this.#discardRotationButton],
[HeaderElements.TOGGLE_TOOLTIPS, this.#toggleTooltips],
[HeaderElements.TOGGLE_LAYERS, this.#toggleLayers],
[HeaderElements.TOGGLE_READABLE_REDACTIONS, this.#toggleReadableRedactions],
[HeaderElements.LOAD_ALL_ANNOTATIONS, this.#loadAllAnnotations],
[HeaderElements.COMPARE_BUTTON, this.#compare],
[HeaderElements.CLOSE_COMPARE_BUTTON, this.#closeCompare],
]);
this.toggleLoadAnnotations$ = this.#toggleLoadAnnotations$;
this.updateElements();
}
enable(elements: HeaderElementType[]): void {
this.#updateState(elements, true);
}
disable(elements: HeaderElementType[]): void {
this.#updateState(elements, false);
}
updateElements(): void {
this._pdf.instance?.UI.setHeaderItems(header => {
const documineButtons = this.#isDocumine ? 1 : 0;
const enabledItems: IHeaderElement[] = [];
const groups: HeaderElementType[][] = [
[HeaderElements.COMPARE_BUTTON, HeaderElements.CLOSE_COMPARE_BUTTON],
[HeaderElements.TOGGLE_TOOLTIPS],
[HeaderElements.TOGGLE_LAYERS],
[HeaderElements.TOGGLE_READABLE_REDACTIONS],
[
HeaderElements.ROTATE_LEFT_BUTTON,
HeaderElements.ROTATE_RIGHT_BUTTON,
HeaderElements.APPLY_ROTATION,
HeaderElements.DISCARD_ROTATION,
],
];
header.get('selectToolButton').insertAfter(this.#buttons.get(HeaderElements.SHAPE_TOOL_GROUP_BUTTON));
groups.forEach(group => this.#pushGroup(enabledItems, group));
const loadAllAnnotationsButton = this.#buttons.get(HeaderElements.LOAD_ALL_ANNOTATIONS);
let startButtons = 11 - documineButtons;
let deleteCount = 15 - documineButtons;
if (this.#isEnabled(HeaderElements.LOAD_ALL_ANNOTATIONS)) {
if (!header.getItems().includes(loadAllAnnotationsButton)) {
header.get('leftPanelButton').insertAfter(loadAllAnnotationsButton);
}
startButtons = 12 - documineButtons;
deleteCount = 16 - documineButtons;
} else {
header.delete(HeaderElements.LOAD_ALL_ANNOTATIONS);
}
header.getItems().splice(startButtons, header.getItems().length - deleteCount, ...enabledItems);
});
this._pdf.instance?.UI.updateElement('selectToolButton', {
img: this.#convertPath('/assets/icons/general/pdftron-cursor.svg'),
});
if (this._pdf.instance) {
this._tooltipsService.updateIconState();
this._layersService.updateIconState();
const closeCompareButton = this._pdf.instance.UI.iframeWindow.document.querySelector(
`[data-element=${HeaderElements.CLOSE_COMPARE_BUTTON}]`,
);
closeCompareButton?.classList.add('active');
}
}
disableLoadAllAnnotations(): void {
this._pdf.instance.UI.updateElement(HeaderElements.LOAD_ALL_ANNOTATIONS, {
img: this.#convertPath('/assets/icons/general/pdftron-action-load-all-annotations-disabled.svg'),
title: this._translateService.instant('viewer-header.all-annotations-loaded'),
onClick: undefined,
});
}
enableLoadAllAnnotations(): void {
this._pdf.instance.UI.updateElement(HeaderElements.LOAD_ALL_ANNOTATIONS, {
img: this.#convertPath('/assets/icons/general/pdftron-action-load-all-annotations.svg'),
onClick: () => this._ngZone.run(() => this.#events$.next({ type: ViewerEvents.LOAD_ALL_ANNOTATIONS })),
});
}
enableRotationButtons(): void {
this.enable(ROTATION_BUTTONS);
}
disableRotationButtons(): void {
this.disable(ROTATION_BUTTONS);
this.#discardRotation();
}
resetCompareButtons() {
this.disable([HeaderElements.CLOSE_COMPARE_BUTTON]);
this.enable([HeaderElements.COMPARE_BUTTON]);
}
resetLayers() {
this._layersService.resetLayers();
}
#closeCompareMode() {
this._pdf.closeCompareMode();
const { dossierId, fileId } = this._pdf;
const file = this._filesMapService.get(dossierId, fileId);
const filename = file.filename ?? 'document.pdf';
this._pdf.instance.UI.loadDocument(this.#docBeforeCompare, { filename });
this.resetCompareButtons();
this._pdf.navigateTo(1);
}
#pushGroup(items: IHeaderElement[], group: HeaderElementType[]) {
const enabledItems = group.filter(item => this.#isEnabled(item));
if (enabledItems.length) {
items.push(divider);
enabledItems.forEach(item => items.push(this.#buttons.get(item)));
}
}
#updateState(elements: HeaderElementType[], value: boolean) {
if (this.#iqserPermissionService.has(Roles.getRss)) {
elements = elements.filter(element => element !== HeaderElements.SHAPE_TOOL_GROUP_BUTTON);
}
if (this.#isDocumine) {
elements = elements.filter(element => element !== HeaderElements.COMPARE_BUTTON);
}
elements.forEach(element => this.#config.set(element, value));
this.updateElements();
}
#isEnabled(key: HeaderElementType): boolean {
return this.#config.get(key);
}
#discardRotation(): void {
this._rotationService.discardRotation();
this.disable(ROTATION_ACTION_BUTTONS);
}
#toggleRotationActionButtons() {
if (this._rotationService.hasRotations()) {
this.enable(ROTATION_ACTION_BUTTONS);
} else {
this.disable(ROTATION_ACTION_BUTTONS);
}
}
}