common-ui/src/lib/help-mode/help-mode.service.ts
2022-04-06 22:19:37 +03:00

207 lines
7.6 KiB
TypeScript

import { Inject, Injectable, Renderer2, RendererFactory2 } from '@angular/core';
import { MatDialog, MatDialogRef } from '@angular/material/dialog';
import { TranslateService } from '@ngx-translate/core';
import { BehaviorSubject, firstValueFrom } from 'rxjs';
import { HelpModeDialogComponent } from './help-mode-dialog/help-mode-dialog.component';
import { HELP_DOCS, MANUAL_BASE_URL } from './tokens';
const VIRTUAL_SCROLL_ID = 'virtual-scroll';
const ANNOTATIONS_LIST_ID = 'annotations-list';
const SCROLL_BUTTONS_IDS = ['scroll-up', 'scroll-down'];
export const ScrollableParentViews = {
VIRTUAL_SCROLL: 'VIRTUAL_SCROLL',
ANNOTATIONS_LIST: 'ANNOTATIONS_LIST',
} as const;
export type ScrollableParentView = keyof typeof ScrollableParentViews;
interface Helper {
readonly element: HTMLElement;
readonly helperElement: HTMLElement;
readonly scrollableParentView: ScrollableParentView;
readonly dialogElement: boolean;
}
@Injectable({
providedIn: 'root',
})
export class HelpModeService {
private readonly _isHelpModeActive$ = new BehaviorSubject(false);
readonly isHelpModeActive$ = this._isHelpModeActive$.asObservable();
private readonly _helpModeDialogIsOpened$ = new BehaviorSubject(false);
readonly helpModeDialogIsOpened$ = this._helpModeDialogIsOpened$.asObservable();
private readonly _helperElements: Record<string, Helper> = {};
private readonly _renderer: Renderer2;
private _dialogMode = false;
constructor(
@Inject(HELP_DOCS) private readonly _docs: Record<string, Record<string, string>>,
@Inject(MANUAL_BASE_URL) private readonly _manualBaseURL: string,
private readonly _dialog: MatDialog,
private readonly _rendererFactory: RendererFactory2,
private readonly _translateService: TranslateService,
) {
this._renderer = this._rendererFactory.createRenderer(null, null);
}
get isHelpModeActive(): boolean {
return this._isHelpModeActive$.getValue();
}
get helpModeDialogIsOpened(): boolean {
return this._helpModeDialogIsOpened$.getValue();
}
openHelpModeDialog(): MatDialogRef<HelpModeDialogComponent> {
this._helpModeDialogIsOpened$.next(true);
const ref = this._dialog.open(HelpModeDialogComponent, {
width: '600px',
});
firstValueFrom(ref.afterClosed()).then(() => {
this._helpModeDialogIsOpened$.next(false);
});
return ref;
}
getDocsLink(elementName: string): string {
return this._docs[elementName] ? `${this._manualBaseURL}${this._docs[elementName][this._translateService.currentLang]}` : '';
}
activateHelpMode(dialogMode: boolean = false): void {
if (!this.isHelpModeActive) {
document.body.style.setProperty('overflow', 'hidden');
this._isHelpModeActive$.next(true);
this.openHelpModeDialog();
this._dialogMode = dialogMode;
setTimeout(() => {
this._enableHelperElements();
});
}
}
deactivateHelpMode(): void {
if (this.isHelpModeActive) {
document.body.style.removeProperty('overflow');
this._isHelpModeActive$.next(false);
this._disableHelperElements();
}
}
highlightHelperElements(): void {
Object.values(this._helperElements).forEach(({ element, helperElement }) => {
this._renderer.addClass(helperElement, 'help-highlight');
setTimeout(() => {
this._renderer.removeClass(helperElement, 'help-highlight');
}, 500);
});
}
addElement(
helperElementName: string,
element: HTMLElement,
helperElement: HTMLElement,
scrollableParentView: ScrollableParentView,
dialogElement: boolean,
): void {
this._helperElements[helperElementName] = { element, helperElement, scrollableParentView, dialogElement };
}
updateHelperElements() {
Object.values(this._helperElements).forEach(({ element, helperElement, scrollableParentView }) => {
this._updateHelperElement(element, helperElement, scrollableParentView);
});
}
private _isElementVisible(element: HTMLElement, scrollableParentView: ScrollableParentView): boolean {
let elementRect: DOMRect = element.getBoundingClientRect();
if (elementRect.top === 0 && elementRect.left === 0 && elementRect.bottom === 0 && elementRect.bottom === 0) {
return false;
}
if (scrollableParentView) {
const scrollableElementId = scrollableParentView === ScrollableParentViews.VIRTUAL_SCROLL ? VIRTUAL_SCROLL_ID : ANNOTATIONS_LIST_ID;
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 (scrollableParentView === ScrollableParentViews.VIRTUAL_SCROLL) {
for (const id of SCROLL_BUTTONS_IDS) {
const scroll: HTMLElement = <HTMLElement>document.getElementById(id);
elementRect = 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;
}
}
}
}
return true;
}
private _updateHelperElement(element: HTMLElement, helperElement: HTMLElement, scrollableParentView: ScrollableParentView) {
if (this._isElementVisible(element, scrollableParentView)) {
const dimensions = this._getElementDimensions(element);
helperElement.style.cssText = `
top:${dimensions.y}px;
left:${dimensions.x}px;
width:${dimensions.width}px;
height:${dimensions.height}px;
`;
helperElement.classList.add('help-mode');
} else {
helperElement.classList.remove('help-mode');
}
}
private _enableHelperElements() {
Object.values(this._helperElements).forEach(({ element, helperElement, scrollableParentView, dialogElement }) => {
if (this._dialogMode === dialogElement ) {
document.body.appendChild(helperElement);
this._updateHelperElement(element, helperElement, scrollableParentView);
}
});
}
private _disableHelperElements() {
Object.values(this._helperElements).forEach(({ helperElement }) => {
if (document.body.contains(helperElement)) {
document.body.removeChild(helperElement);
}
});
}
private _getElementDimensions(element: HTMLElement) {
const rect = element.getBoundingClientRect();
return {
y: rect.top,
x: rect.left,
height: rect.height,
width: rect.width,
};
}
}