diff --git a/src/lib/help-mode/help-mode.directive.ts b/src/lib/help-mode/help-mode.directive.ts index 245611f..89990bb 100644 --- a/src/lib/help-mode/help-mode.directive.ts +++ b/src/lib/help-mode/help-mode.directive.ts @@ -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 { diff --git a/src/lib/help-mode/help-mode.service.ts b/src/lib/help-mode/help-mode.service.ts index 1d02640..c45cb46 100644 --- a/src/lib/help-mode/help-mode.service.ts +++ b/src/lib/help-mode/help-mode.service.ts @@ -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 = {}; - 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 = {}; + 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 { - 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 = 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 = 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/'); + } } diff --git a/src/lib/help-mode/index.ts b/src/lib/help-mode/index.ts index 62438eb..486a9ce 100644 --- a/src/lib/help-mode/index.ts +++ b/src/lib/help-mode/index.ts @@ -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'; diff --git a/src/lib/help-mode/utils/constants.ts b/src/lib/help-mode/utils/constants.ts new file mode 100644 index 0000000..b55b802 --- /dev/null +++ b/src/lib/help-mode/utils/constants.ts @@ -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';