409 lines
17 KiB
TypeScript
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);
|
|
}
|
|
}
|
|
}
|