291 lines
11 KiB
TypeScript
291 lines
11 KiB
TypeScript
import { Inject, Injectable, Renderer2, RendererFactory2 } from '@angular/core';
|
|
import { MatDialog } from '@angular/material/dialog';
|
|
import { TranslateService } from '@ngx-translate/core';
|
|
import { BehaviorSubject, firstValueFrom } from 'rxjs';
|
|
import { getConfig } from '../services';
|
|
import { IqserUserPreferenceService } from '../services';
|
|
import { HelpModeDialogComponent } from './help-mode-dialog/help-mode-dialog.component';
|
|
import { HELP_MODE_KEYS, MANUAL_BASE_URL } from './tokens';
|
|
import { HelpModeKey } from './types';
|
|
import {
|
|
DOCUMINE_THEME_CLASS,
|
|
HELP_HIGHLIGHT_CLASS,
|
|
HELP_MODE_CLASS,
|
|
OVERLAPPING_DROPDOWNS_IDS,
|
|
OverlappingElement,
|
|
PDF_TRON_IFRAME_ID,
|
|
SCROLL_BUTTONS_IDS,
|
|
SCROLLABLE_PARENT_VIEWS_IDS,
|
|
ScrollableParentView,
|
|
ScrollableParentViews,
|
|
WEB_VIEWER_ELEMENTS,
|
|
} from './utils/constants';
|
|
import { toSignal } from '@angular/core/rxjs-interop';
|
|
|
|
export interface Helper {
|
|
readonly element: HTMLElement;
|
|
readonly helperElement: HTMLElement;
|
|
readonly scrollableParentView?: ScrollableParentView;
|
|
readonly overlappingElements?: OverlappingElement[];
|
|
readonly dialogElement?: boolean;
|
|
readonly iframeElement?: boolean;
|
|
}
|
|
|
|
@Injectable()
|
|
export class HelpModeService {
|
|
readonly #isHelpModeActive$ = new BehaviorSubject(false);
|
|
readonly #helpModeDialogIsOpened$ = new BehaviorSubject(false);
|
|
readonly #renderer: Renderer2;
|
|
readonly #isDocumine = getConfig().IS_DOCUMINE;
|
|
#helpers: Record<string, Helper> = {};
|
|
#dialogMode = false;
|
|
readonly isHelpModeActive$ = this.#isHelpModeActive$.asObservable();
|
|
readonly isHelpModeActive = toSignal(this.isHelpModeActive$, { initialValue: false });
|
|
readonly helpModeDialogIsOpened$ = this.#helpModeDialogIsOpened$.asObservable();
|
|
|
|
constructor(
|
|
@Inject(HELP_MODE_KEYS) private readonly _keys: HelpModeKey[],
|
|
@Inject(MANUAL_BASE_URL) private readonly _manualBaseURL: string,
|
|
private readonly _dialog: MatDialog,
|
|
private readonly _rendererFactory: RendererFactory2,
|
|
private readonly _translateService: TranslateService,
|
|
private readonly _iqserUserPreferenceService: IqserUserPreferenceService,
|
|
) {
|
|
this.#renderer = this._rendererFactory.createRenderer(null, null);
|
|
}
|
|
|
|
get helpModeDialogIsOpened(): boolean {
|
|
return this.#helpModeDialogIsOpened$.getValue();
|
|
}
|
|
|
|
async openHelpModeDialog() {
|
|
if (!this._iqserUserPreferenceService.getHelpModeDialog()) {
|
|
this.#helpModeDialogIsOpened$.next(true);
|
|
|
|
const ref = this._dialog.open(HelpModeDialogComponent, {
|
|
width: '600px',
|
|
});
|
|
|
|
firstValueFrom(ref.afterClosed()).then(result => {
|
|
this.#helpModeDialogIsOpened$.next(false);
|
|
if (result) {
|
|
this._iqserUserPreferenceService.toggleHelpModeDialog();
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
activateHelpMode(dialogMode: boolean = false): void {
|
|
if (!this.isHelpModeActive()) {
|
|
document.body.style.setProperty('overflow', 'unset');
|
|
this.#isHelpModeActive$.next(true);
|
|
this.openHelpModeDialog();
|
|
this.#dialogMode = dialogMode;
|
|
setTimeout(() => {
|
|
this.#createHelpers();
|
|
this.#createWebViewerHelpers();
|
|
this.#enableHelperElements();
|
|
}, 500);
|
|
}
|
|
}
|
|
|
|
deactivateHelpMode(): void {
|
|
if (this.isHelpModeActive()) {
|
|
document.body.style.removeProperty('overflow');
|
|
this.#isHelpModeActive$.next(false);
|
|
this.#disableHelperElements();
|
|
this.#helpers = {};
|
|
}
|
|
}
|
|
|
|
highlightHelperElements(): void {
|
|
Object.values(this.#helpers).forEach(helper => {
|
|
this.#renderer.addClass(helper.helperElement, HELP_HIGHLIGHT_CLASS);
|
|
setTimeout(() => {
|
|
this.#renderer.removeClass(helper.helperElement, HELP_HIGHLIGHT_CLASS);
|
|
}, 500);
|
|
});
|
|
}
|
|
|
|
updateHelperElements() {
|
|
Object.values(this.#helpers).forEach(helper => this.#updateHelperElement(helper));
|
|
}
|
|
|
|
#createHelpers() {
|
|
for (const key of Object.values(this._keys)) {
|
|
const elements = document.querySelectorAll(`[help-mode-key='${key.elementKey}']`);
|
|
elements.forEach(element => {
|
|
const helperKey = `${key.elementKey}-${this.#generateId()}`;
|
|
|
|
this.#helpers[helperKey] = {
|
|
element: element as HTMLElement,
|
|
helperElement: this.#getHelperElement(element as HTMLElement, key.documentKey),
|
|
scrollableParentView: key.scrollableParentView,
|
|
overlappingElements: key.overlappingElements,
|
|
dialogElement: key.elementKey.endsWith('_DIALOG'),
|
|
};
|
|
});
|
|
}
|
|
}
|
|
|
|
#createWebViewerHelpers(): void {
|
|
const iframe: HTMLIFrameElement = document.getElementById(PDF_TRON_IFRAME_ID) as HTMLIFrameElement;
|
|
if (iframe) {
|
|
WEB_VIEWER_ELEMENTS.forEach(e => {
|
|
const element: HTMLElement = iframe.contentWindow?.document.querySelector(e.querySelector) as HTMLElement;
|
|
|
|
this.#helpers[e.documentKey] = {
|
|
element,
|
|
helperElement: this.#getHelperElement(element, e.documentKey),
|
|
dialogElement: false,
|
|
iframeElement: true,
|
|
};
|
|
});
|
|
}
|
|
}
|
|
|
|
#getHelperElement(element: HTMLElement, key: string): HTMLElement {
|
|
const helperElement = this.#renderer.createElement('a') as HTMLElement;
|
|
this.#renderer.setAttribute(helperElement, 'href', this.#generateDocsLink(key));
|
|
this.#renderer.setAttribute(helperElement, 'target', '_blank');
|
|
this.#renderer.addClass(helperElement, HELP_MODE_CLASS);
|
|
if (this.#isDocumine) {
|
|
this.#renderer.addClass(helperElement, DOCUMINE_THEME_CLASS);
|
|
}
|
|
return helperElement;
|
|
}
|
|
|
|
#generateId(): string {
|
|
return Math.random().toString(36).substring(2, 9);
|
|
}
|
|
|
|
#generateDocsLink(key: string) {
|
|
const currentLang = this._translateService.currentLang;
|
|
return `${this._manualBaseURL}/${currentLang}/index-${currentLang}.html?contextId=${key}`;
|
|
}
|
|
|
|
#isElementVisible(helper: Helper): boolean {
|
|
if (helper.iframeElement && !this.#isFilePreviewPage()) {
|
|
return false;
|
|
}
|
|
let elementRect: DOMRect = helper.element.getBoundingClientRect();
|
|
|
|
if (elementRect.top === 0 && elementRect.left === 0 && elementRect.bottom === 0 && elementRect.bottom === 0) {
|
|
return false;
|
|
}
|
|
|
|
if (helper.scrollableParentView) {
|
|
const scrollableElementId = SCROLLABLE_PARENT_VIEWS_IDS[helper.scrollableParentView];
|
|
const scrollableElement: HTMLElement = <HTMLElement>document.getElementById(scrollableElementId);
|
|
|
|
if (!scrollableElement) {
|
|
return false;
|
|
}
|
|
|
|
const scrollableElementRect = scrollableElement.getBoundingClientRect();
|
|
|
|
if (
|
|
!(
|
|
elementRect.top > scrollableElementRect.top &&
|
|
elementRect.left > scrollableElementRect.left &&
|
|
elementRect.bottom < scrollableElementRect.bottom &&
|
|
elementRect.right < scrollableElementRect.right
|
|
)
|
|
) {
|
|
return false;
|
|
}
|
|
|
|
if (helper.scrollableParentView === ScrollableParentViews.VIRTUAL_SCROLL) {
|
|
for (const id of SCROLL_BUTTONS_IDS) {
|
|
const scroll: HTMLElement = <HTMLElement>document.getElementById(id);
|
|
|
|
elementRect = helper.element.getBoundingClientRect();
|
|
const scrollRect = scroll?.getBoundingClientRect();
|
|
|
|
if (
|
|
elementRect.top + elementRect.height > scrollRect.top &&
|
|
elementRect.left + elementRect.width > scrollRect.left &&
|
|
elementRect.bottom - elementRect.height < scrollRect.bottom &&
|
|
elementRect.right - elementRect.width < scrollRect.right
|
|
) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (helper.overlappingElements) {
|
|
for (const overlappingElementName of helper.overlappingElements) {
|
|
const overlappingElement = document.getElementById(OVERLAPPING_DROPDOWNS_IDS[overlappingElementName]);
|
|
|
|
if (!overlappingElement) {
|
|
continue;
|
|
}
|
|
|
|
const overlappingElementRect = overlappingElement.getBoundingClientRect();
|
|
|
|
return !(
|
|
Math.max(elementRect.left, overlappingElementRect.left) < Math.min(elementRect.right, overlappingElementRect.right) &&
|
|
Math.max(elementRect.top, overlappingElementRect.top) < Math.min(elementRect.bottom, overlappingElementRect.bottom)
|
|
);
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
#updateHelperElement(helper: Helper) {
|
|
if (this.#isElementVisible(helper)) {
|
|
const dimensions = this.#getElementDimensions(helper.element);
|
|
|
|
if (helper.iframeElement) {
|
|
const iframe: HTMLIFrameElement = document.getElementById(PDF_TRON_IFRAME_ID) as HTMLIFrameElement;
|
|
const iframeRect = iframe.getBoundingClientRect();
|
|
dimensions.y += iframeRect.top;
|
|
dimensions.x += iframeRect.left;
|
|
}
|
|
|
|
helper.helperElement.style.cssText = `
|
|
top:${dimensions.y}px;
|
|
left:${dimensions.x}px;
|
|
width:${dimensions.width}px;
|
|
height:${dimensions.height}px;
|
|
`;
|
|
helper.helperElement.classList.add(HELP_MODE_CLASS);
|
|
} else {
|
|
helper.helperElement.classList.remove(HELP_MODE_CLASS);
|
|
}
|
|
}
|
|
|
|
#enableHelperElements() {
|
|
Object.values(this.#helpers).forEach(helper => {
|
|
if (this.#dialogMode === helper.dialogElement) {
|
|
document.body.appendChild(helper.helperElement);
|
|
this.#updateHelperElement(helper);
|
|
}
|
|
});
|
|
}
|
|
|
|
#disableHelperElements() {
|
|
Object.values(this.#helpers).forEach(helper => {
|
|
if (document.body.contains(helper.helperElement)) {
|
|
document.body.removeChild(helper.helperElement);
|
|
}
|
|
});
|
|
}
|
|
|
|
#getElementDimensions(element: HTMLElement) {
|
|
const rect = element.getBoundingClientRect();
|
|
return {
|
|
y: rect.top,
|
|
x: rect.left,
|
|
height: rect.height,
|
|
width: rect.width,
|
|
};
|
|
}
|
|
|
|
#isFilePreviewPage() {
|
|
return window.location.href.includes('/file/');
|
|
}
|
|
}
|