Spaces:
Paused
Paused
| import { QuickReply } from '../../QuickReply.js'; | |
| import { QuickReplySet } from '../../QuickReplySet.js'; | |
| import { MenuHeader } from './MenuHeader.js'; | |
| import { MenuItem } from './MenuItem.js'; | |
| export class ContextMenu { | |
| /**@type {MenuItem[]}*/ itemList = []; | |
| /**@type {Boolean}*/ isActive = false; | |
| /**@type {HTMLElement}*/ root; | |
| /**@type {HTMLElement}*/ menu; | |
| constructor(/**@type {QuickReply}*/qr) { | |
| // this.itemList = items; | |
| this.itemList = this.build(qr).children; | |
| this.itemList.forEach(item => { | |
| item.onExpand = () => { | |
| this.itemList.filter(it => it !== item) | |
| .forEach(it => it.collapse()); | |
| }; | |
| }); | |
| } | |
| /** | |
| * @param {QuickReply} qr | |
| * @param {String} chainedMessage | |
| * @param {QuickReplySet[]} hierarchy | |
| * @param {String[]} labelHierarchy | |
| */ | |
| build(qr, chainedMessage = null, hierarchy = [], labelHierarchy = []) { | |
| const tree = { | |
| icon: qr.icon, | |
| showLabel: qr.showLabel, | |
| label: qr.label, | |
| title: qr.title, | |
| message: (chainedMessage && qr.message ? `${chainedMessage} | ` : '') + qr.message, | |
| children: [], | |
| }; | |
| qr.contextList.forEach((cl) => { | |
| if (!cl.set) return; | |
| if (!hierarchy.includes(cl.set)) { | |
| const nextHierarchy = [...hierarchy, cl.set]; | |
| const nextLabelHierarchy = [...labelHierarchy, tree.label]; | |
| tree.children.push(new MenuHeader(cl.set.name)); | |
| // If the Quick Reply's own set is added as a context menu, | |
| // show only the sub-QRs that are Invisible but have an icon | |
| // intent: allow a QR set to be assigned to one of its own QR buttons for a "burger" menu | |
| // with "UI" QRs either in the bar or in the menu, and "library function" QRs still hidden. | |
| // - QRs already visible on the bar are filtered out, | |
| // - hidden QRs without an icon are filtered out, | |
| // - hidden QRs **with an icon** are shown in the menu | |
| // so everybody is happy | |
| const qrsOwnSetAddedAsContextMenu = cl.set.qrList.includes(qr); | |
| const visible = (subQr) => { | |
| return qrsOwnSetAddedAsContextMenu | |
| ? subQr.isHidden && !!subQr.icon // yes .isHidden gets inverted here | |
| : !subQr.isHidden; | |
| }; | |
| cl.set.qrList.filter(visible).forEach(subQr => { | |
| const subTree = this.build(subQr, cl.isChained ? tree.message : null, nextHierarchy, nextLabelHierarchy); | |
| tree.children.push(new MenuItem( | |
| subTree.icon, | |
| subTree.showLabel, | |
| subTree.label, | |
| subTree.title, | |
| subTree.message, | |
| (evt) => { | |
| evt.stopPropagation(); | |
| const finalQr = Object.assign(new QuickReply(), subQr); | |
| finalQr.message = subTree.message.replace(/%%parent(-\d+)?%%/g, (_, index) => { | |
| return nextLabelHierarchy.slice(parseInt(index ?? '-1'))[0]; | |
| }); | |
| cl.set.execute(finalQr); | |
| }, | |
| subTree.children, | |
| )); | |
| }); | |
| } | |
| }); | |
| return tree; | |
| } | |
| render() { | |
| if (!this.root) { | |
| const blocker = document.createElement('div'); { | |
| this.root = blocker; | |
| blocker.classList.add('ctx-blocker'); | |
| blocker.addEventListener('click', () => this.hide()); | |
| const menu = document.createElement('ul'); { | |
| this.menu = menu; | |
| menu.classList.add('list-group'); | |
| menu.classList.add('ctx-menu'); | |
| this.itemList.forEach(it => menu.append(it.render())); | |
| blocker.append(menu); | |
| } | |
| } | |
| } | |
| return this.root; | |
| } | |
| show({ clientX, clientY }) { | |
| if (this.isActive) return; | |
| this.isActive = true; | |
| this.render(); | |
| this.menu.style.bottom = `${window.innerHeight - clientY}px`; | |
| this.menu.style.left = `${clientX}px`; | |
| document.body.append(this.root); | |
| } | |
| hide() { | |
| if (this.root) { | |
| this.root.remove(); | |
| } | |
| this.isActive = false; | |
| } | |
| toggle(/**@type {PointerEvent}*/evt) { | |
| if (this.isActive) { | |
| this.hide(); | |
| } else { | |
| this.show(evt); | |
| } | |
| } | |
| } | |