moved all help mode service constants in a separate file, updated service properties, added logic to use helper elements over the webviewer iframe

This commit is contained in:
Valentin Mihai 2023-02-02 11:59:07 +02:00
parent 484ff6eed5
commit 76ee8afbc2
4 changed files with 156 additions and 120 deletions

View File

@ -1,12 +1,13 @@
import { Directive, ElementRef, Input, OnInit, Optional, Renderer2 } from '@angular/core';
import { HelpModeService, OverlappingElement, ScrollableParentView } from './help-mode.service';
import { HELP_MODE_CLASS, OverlappingElement, ScrollableParentView } from './utils/constants';
import { Helper, HelpModeService } from './help-mode.service';
@Directive({
selector: '[iqserHelpMode]',
exportAs: 'iqserHelpMode',
})
export class HelpModeDirective implements OnInit {
@Input('iqserHelpMode') elementName!: string;
@Input('iqserHelpMode') helpModeKey!: string;
@Input() scrollableParentView!: ScrollableParentView;
@Input() overlappingElements: OverlappingElement[] = [];
@Input() dialogElement = false;
@ -18,28 +19,29 @@ export class HelpModeDirective implements OnInit {
) {}
ngOnInit(): void {
if (this.elementName && this._helpModeService) {
if (this.helpModeKey && this._helpModeService) {
this._createHelperElement();
}
}
private _createHelperElement() {
const element = this._elementRef.nativeElement as HTMLElement;
const helperElementName = `${this.elementName}-${this._generateId()}`;
const helperKey = `${this.helpModeKey}-${this._generateId()}`;
const helperElement = this._renderer.createElement('a') as HTMLElement;
this._renderer.setAttribute(helperElement, 'href', this._helpModeService.getDocsLink(this.elementName));
this._renderer.setAttribute(helperElement, 'href', this._helpModeService.getDocsLink(this.helpModeKey));
this._renderer.setAttribute(helperElement, 'target', '_blank');
this._renderer.addClass(helperElement, 'help-mode');
this._renderer.addClass(helperElement, HELP_MODE_CLASS);
this._helpModeService.addElement(
helperElementName,
const helper: Helper = {
element,
helperElement,
this.scrollableParentView,
this.overlappingElements,
this.dialogElement,
);
scrollableParentView: this.scrollableParentView,
overlappingElements: this.overlappingElements,
dialogElement: this.dialogElement,
}
this._helpModeService.addHelper(helperKey, helper);
}
private _generateId(): string {

View File

@ -5,49 +5,34 @@ import { BehaviorSubject, firstValueFrom } from 'rxjs';
import { HelpModeDialogComponent } from './help-mode-dialog/help-mode-dialog.component';
import { HELP_DOCS, MANUAL_BASE_URL } from './tokens';
import { HelpDocs } from './help-docs';
import {
ANNOTATIONS_LIST_ID, HELP_HIGHLIGHT_CLASS, HELP_MODE_CLASS, OVERLAPPING_DROPDOWNS_IDS,
OverlappingElement, PDF_TRON_IFRAME_ID,
SCROLL_BUTTONS_IDS,
ScrollableParentView,
ScrollableParentViews,
VIRTUAL_SCROLL_ID, WEB_VIEWER_ELEMENTS
} from './utils/constants';
const VIRTUAL_SCROLL_ID = 'virtual-scroll';
const ANNOTATIONS_LIST_ID = 'annotations-list';
const OVERLAPPING_DROPDOWNS_IDS = {
USER_MENU: 'user-menu-items',
WORKLOAD_FILTER: 'workload-filters',
DOCUMENT_INFO: 'document-info',
};
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;
export const OverlappingElements = {
USER_MENU: 'USER_MENU',
WORKLOAD_FILTER: 'WORKLOAD_FILTER',
DOCUMENT_INFO: 'DOCUMENT_INFO',
} as const;
export type OverlappingElement = keyof typeof OverlappingElements;
interface Helper {
export interface Helper {
readonly element: HTMLElement;
readonly helperElement: HTMLElement;
readonly scrollableParentView: ScrollableParentView;
readonly overlappingElements: OverlappingElement[];
readonly scrollableParentView?: ScrollableParentView;
readonly overlappingElements?: OverlappingElement[];
readonly dialogElement?: boolean;
readonly iframeElement?: boolean;
}
@Injectable()
export class HelpModeService {
helpButtonKey: string | undefined;
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;
readonly #isHelpModeActive$ = new BehaviorSubject(false);
readonly isHelpModeActive$ = this.#isHelpModeActive$.asObservable();
readonly #helpModeDialogIsOpened$ = new BehaviorSubject(false);
readonly helpModeDialogIsOpened$ = this.#helpModeDialogIsOpened$.asObservable();
readonly #helpers: Record<string, Helper> = {};
readonly #renderer: Renderer2;
#dialogMode = false;
constructor(
@Inject(HELP_DOCS) private readonly _docs: HelpDocs,
@ -56,26 +41,26 @@ export class HelpModeService {
private readonly _rendererFactory: RendererFactory2,
private readonly _translateService: TranslateService,
) {
this._renderer = this._rendererFactory.createRenderer(null, null);
this.#renderer = this._rendererFactory.createRenderer(null, null);
}
get isHelpModeActive(): boolean {
return this._isHelpModeActive$.getValue();
return this.#isHelpModeActive$.getValue();
}
get helpModeDialogIsOpened(): boolean {
return this._helpModeDialogIsOpened$.getValue();
return this.#helpModeDialogIsOpened$.getValue();
}
openHelpModeDialog(): MatDialogRef<HelpModeDialogComponent> {
this._helpModeDialogIsOpened$.next(true);
this.#helpModeDialogIsOpened$.next(true);
const ref = this._dialog.open(HelpModeDialogComponent, {
width: '600px',
});
firstValueFrom(ref.afterClosed()).then(() => {
this._helpModeDialogIsOpened$.next(false);
this.#helpModeDialogIsOpened$.next(false);
});
return ref;
}
@ -87,11 +72,12 @@ export class HelpModeService {
activateHelpMode(dialogMode: boolean = false): void {
if (!this.isHelpModeActive) {
document.body.style.setProperty('overflow', 'unset');
this._isHelpModeActive$.next(true);
this.#isHelpModeActive$.next(true);
this.openHelpModeDialog();
this._dialogMode = dialogMode;
this.#dialogMode = dialogMode;
setTimeout(() => {
this._enableHelperElements();
this.#createWebViewerHelpers();
this.#enableHelperElements();
}, 500);
}
}
@ -99,56 +85,65 @@ export class HelpModeService {
deactivateHelpMode(): void {
if (this.isHelpModeActive) {
document.body.style.removeProperty('overflow');
this._isHelpModeActive$.next(false);
this._disableHelperElements();
this.#isHelpModeActive$.next(false);
this.#disableHelperElements();
}
}
highlightHelperElements(): void {
Object.values(this._helperElements).forEach(({ helperElement }) => {
this._renderer.addClass(helperElement, 'help-highlight');
Object.values(this.#helpers).forEach(helper => {
this.#renderer.addClass(helper.helperElement, HELP_HIGHLIGHT_CLASS);
setTimeout(() => {
this._renderer.removeClass(helperElement, 'help-highlight');
this.#renderer.removeClass(helper.helperElement, HELP_HIGHLIGHT_CLASS);
}, 500);
});
}
addElement(
helperElementName: string,
element: HTMLElement,
helperElement: HTMLElement,
scrollableParentView: ScrollableParentView,
overlappingElements: OverlappingElement[],
dialogElement: boolean,
): void {
this._helperElements[helperElementName] = {
element,
helperElement,
scrollableParentView,
overlappingElements,
dialogElement,
};
#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;
const helperElement: HTMLElement = this.#renderer.createElement('a') as HTMLElement;
this.#renderer.setAttribute(helperElement, 'href', this.getDocsLink(e.helpModeKey));
this.#renderer.setAttribute(helperElement, 'target', '_blank');
this.#renderer.addClass(helperElement, HELP_MODE_CLASS);
const helper: Helper = {
element,
helperElement,
dialogElement: false,
iframeElement: true
}
this.addHelper(e.helpModeKey, helper)
})
}
}
addHelper(helperKey: string, helper: Helper): void {
this.#helpers[helperKey] = helper;
}
updateHelperElements() {
Object.values(this._helperElements).forEach(({ element, helperElement, scrollableParentView, overlappingElements }) => {
this._updateHelperElement(element, helperElement, scrollableParentView, overlappingElements);
});
Object.values(this.#helpers).forEach(helper => this.#updateHelperElement(helper));
}
private _isElementVisible(
element: HTMLElement,
scrollableParentView: ScrollableParentView,
overlappingElements: OverlappingElement[],
): boolean {
let elementRect: DOMRect = element.getBoundingClientRect();
#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 (scrollableParentView) {
if (helper.scrollableParentView) {
const scrollableElementId =
scrollableParentView === ScrollableParentViews.VIRTUAL_SCROLL ? VIRTUAL_SCROLL_ID : ANNOTATIONS_LIST_ID;
helper.scrollableParentView === ScrollableParentViews.VIRTUAL_SCROLL ? VIRTUAL_SCROLL_ID : ANNOTATIONS_LIST_ID;
const scrollableElement: HTMLElement = <HTMLElement>document.getElementById(scrollableElementId);
if (!scrollableElement) {
@ -168,11 +163,11 @@ export class HelpModeService {
return false;
}
if (scrollableParentView === ScrollableParentViews.VIRTUAL_SCROLL) {
if (helper.scrollableParentView === ScrollableParentViews.VIRTUAL_SCROLL) {
for (const id of SCROLL_BUTTONS_IDS) {
const scroll: HTMLElement = <HTMLElement>document.getElementById(id);
elementRect = element.getBoundingClientRect();
elementRect = helper.element.getBoundingClientRect();
const scrollRect = scroll?.getBoundingClientRect();
if (
@ -187,64 +182,67 @@ export class HelpModeService {
}
}
for (const overlappingElementName of overlappingElements) {
const overlappingElement = document.getElementById(OVERLAPPING_DROPDOWNS_IDS[overlappingElementName]);
if (helper.overlappingElements) {
for (const overlappingElementName of helper.overlappingElements) {
const overlappingElement = document.getElementById(OVERLAPPING_DROPDOWNS_IDS[overlappingElementName]);
if (!overlappingElement) {
continue;
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)
);
}
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;
}
private _updateHelperElement(
element: HTMLElement,
helperElement: HTMLElement,
scrollableParentView: ScrollableParentView,
overlappingElements: OverlappingElement[],
) {
if (this._isElementVisible(element, scrollableParentView, overlappingElements)) {
const dimensions = this._getElementDimensions(element);
helperElement.style.cssText = `
#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;
}
helper.helperElement.style.cssText = `
top:${dimensions.y}px;
left:${dimensions.x}px;
width:${dimensions.width}px;
height:${dimensions.height}px;
`;
helperElement.classList.add('help-mode');
helper.helperElement.classList.add(HELP_MODE_CLASS);
} else {
helperElement.classList.remove('help-mode');
helper.helperElement.classList.remove(HELP_MODE_CLASS);
}
}
private _enableHelperElements() {
Object.values(this._helperElements).forEach(
({ element, helperElement, scrollableParentView, overlappingElements, dialogElement }) => {
if (this._dialogMode === dialogElement) {
document.body.appendChild(helperElement);
this._updateHelperElement(element, helperElement, scrollableParentView, overlappingElements);
#enableHelperElements() {
Object.values(this.#helpers).forEach(helper => {
if (this.#dialogMode === helper.dialogElement) {
document.body.appendChild(helper.helperElement);
this.#updateHelperElement(helper);
}
},
);
}
private _disableHelperElements() {
Object.values(this._helperElements).forEach(({ helperElement }) => {
if (document.body.contains(helperElement)) {
document.body.removeChild(helperElement);
#disableHelperElements() {
Object.values(this.#helpers).forEach(helper => {
if (document.body.contains(helper.helperElement)) {
document.body.removeChild(helper.helperElement);
}
});
}
private _getElementDimensions(element: HTMLElement) {
#getElementDimensions(element: HTMLElement) {
const rect = element.getBoundingClientRect();
return {
y: rect.top,
@ -253,4 +251,8 @@ export class HelpModeService {
width: rect.width,
};
}
#isFilePreviewPage() {
return window.location.href.includes('/file/');
}
}

View File

@ -6,3 +6,4 @@ export * from './help-mode.directive';
export * from './help-mode/help-mode.component';
export * from './help-button/help-button.component';
export * from './help-mode-dialog/help-mode-dialog.component';
export * from './utils/constants';

View File

@ -0,0 +1,31 @@
export const VIRTUAL_SCROLL_ID = 'virtual-scroll';
export const ANNOTATIONS_LIST_ID = 'annotations-list';
export const OVERLAPPING_DROPDOWNS_IDS = {
USER_MENU: 'user-menu-items',
WORKLOAD_FILTER: 'workload-filters',
DOCUMENT_INFO: 'document-info',
};
export const SCROLL_BUTTONS_IDS = ['scroll-up', 'scroll-down'];
export const PDF_TRON_IFRAME_ID = 'webviewer-1';
export const WEB_VIEWER_ELEMENTS = [{
querySelector: '.HeaderItems',
helpModeKey: 'pdf_features'
}];
export const ScrollableParentViews = {
VIRTUAL_SCROLL: 'VIRTUAL_SCROLL',
ANNOTATIONS_LIST: 'ANNOTATIONS_LIST',
} as const;
export type ScrollableParentView = keyof typeof ScrollableParentViews;
export const OverlappingElements = {
USER_MENU: 'USER_MENU',
WORKLOAD_FILTER: 'WORKLOAD_FILTER',
DOCUMENT_INFO: 'DOCUMENT_INFO',
} as const;
export type OverlappingElement = keyof typeof OverlappingElements;
export const HELP_MODE_CLASS = 'help-mode';
export const HELP_HIGHLIGHT_CLASS = 'help-highlight';