Source: contextMenuPopover.js

"use strict";
// SPDX-License-Identifier: GPL-3.0-or-later
// myMPD (c) 2018-2024 Juergen Mang <mail@jcgames.de>
// https://github.com/jcorporation/mympd

/** @module contextMenuPopover_js */

/**
 * Shows the context menu popover
 * @param {HTMLElement} target calculated target
 * @param {string} contextMenuType type of the context menu
 * @returns {void}
 */
function showPopover(target, contextMenuType) {
    hidePopover(target);
    //context menu is shown
    if (target.getAttribute('aria-describedby') !== null ||
        target.classList.contains('not-clickable'))
    {
        return;
    }
    //check for existing popover instance
    let popoverInit = BSN.Popover.getInstance(target);
    //create it, if no popover instance is found
    if (popoverInit === null) {
        switch (contextMenuType) {
            case 'NavbarPlayback':
            case 'NavbarQueue':
            case 'NavbarBrowse':
                popoverInit = createPopoverSimple(target, target.getAttribute('title'), addMenuItemsNavbarActions);
                break;
            case 'footer':
                popoverInit = createPopoverSimple(target, 'Playback controls', addActionsPopoverFooter);
                domCache.footer.addEventListener('updated.bs.popover', function() {
                    updatePlaybackControls();
                }, false);
                domCache.footer.addEventListener('show.bs.popover', function() {
                    elGetById('advPlaybackControlsBtn').classList.add('active');
                }, false);
                domCache.footer.addEventListener('hidden.bs.popover', function() {
                    elGetById('advPlaybackControlsBtn').classList.remove('active');
                }, false);
                break;
            // No Default
        }
    }
    popoverInit.show();
}

/**
 * Hides all popovers
 * @param {EventTarget} [thisEl] triggering element
 * @returns {void}
 */
function hidePopover(thisEl) {
    const popoverEls = document.querySelectorAll('[aria-describedby]');
    for (const el of popoverEls) {
        if (thisEl === el) {
            //do not hide popover that should be opened
            continue;
        }
        BSN.Popover.getInstance(el).hide();
    }
    if (popoverEls.length === 0) {
        //handle popover dom nodes without a trigger element
        const popover = document.querySelector('.popover');
        if (popover !== null) {
            //simply remove the popover dom node
            popover.remove();
        }
    }
}

/**
 * Creates the popover body
 * @param {string} template tabs = create a popover body with two tabes, else create an empty body
 * @returns {HTMLElement} the popover body
 */
function createPopoverBody(template) {
    if (template === 'tabs') {
        return elCreateNodes('div', {"class": ["popover-tabs", "py-2"]}, [
                   elCreateNodes('ul', {"class": ["nav", "nav-tabs", "px-2"]}, [
                       elCreateNode('li', {"class": ["nav-item"]},
                           elCreateEmpty('a', {"class": ["nav-link", "active"], "href": "#"})
                       ),
                       elCreateNode('li', {"class": ["nav-item"]},
                           elCreateEmpty('a', {"class": ["nav-link"], "href": "#"})
                       )
                   ]),
                   elCreateNodes('div', {"class": ["tab-content"]}, [
                       elCreateEmpty('div', {"class": ["tab-pane", "pt-2", "active", "show"], "id": "popoverTab0"}),
                       elCreateEmpty('div', {"class": ["tab-pane", "pt-2"], "id": "popoverTab1"})
                   ])
               ]);
    }
    return elCreateEmpty('div', {"class": ["popover-body"]});
}

/**
 * Creates a new BSN popover
 * @param {EventTarget} target triggering element
 * @param {string} title popover title 
 * @param {string} [bodyTemplate] the popover body
 * @returns {object} BSN popover object
 */
function createPopoverInit(target, title, bodyTemplate) {
    const template = elCreateNodes('div', {"class": ["popover"]}, [
            elCreateEmpty('div', {"class": ["popover-arrow"]}),
            elCreateEmpty('h3', {"class": ["popover-header"]}),
            createPopoverBody(bodyTemplate)
        ]
    );
    const options = {
        trigger: 'manual',
        delay: 0,
        dismissible: false,
        title: (title !== '' ? elCreateText('span', {}, title) : ''),
        template: template, content: document.createTextNode('dummy')
    };

    let popoverType = target.getAttribute('data-contextmenu');
    if (popoverType === null) {
        popoverType = target.getAttribute('data-col');
    }
    if (popoverType === null) {
        popoverType = target.parentNode.getAttribute('data-col');
    }
    switch(popoverType) {
        case 'NavbarPlayback':
        case 'NavbarQueue':
        case 'NavbarBrowse':
            // @ts-ignore
            options.placement = getXpos(target) < 100
                ? 'right'
                : 'bottom';
            break;
        // No Default
    }
    return new BSN.Popover(target, options);
}

/**
 * Creates the click handler for the popover menu
 * @param {HTMLElement} el container of the menu items
 * @returns {void}
 */
function createPopoverClickHandler(el) {
    el.addEventListener('click', function(eventClick) {
        if (eventClick.target.nodeName === 'A') {
            const cmd = getData(eventClick.target, 'href');
            if (cmd) {
                parseCmd(eventClick, cmd);
                hidePopover();
            }
        }
        else if (eventClick.target.nodeName === 'BUTTON') {
            const cmd = getData(eventClick.target, 'href');
            if (typeof(cmd) === 'object') {
                parseCmd(eventClick, cmd);
            }
            else {
                parseCmdFromJSON(eventClick, cmd);
            }
        }
        eventClick.preventDefault();
        eventClick.stopPropagation();
    }, false);
}

/**
 * Creates a simple popover
 * @param {EventTarget} target triggering element
 * @param {string} title popover title
 * @param {Function} contentCallback callback to create the popover content
 * @returns {object} BSN popover object
 */
function createPopoverSimple(target, title, contentCallback) {
    const popoverInit = createPopoverInit(target, tn(title));
    //update content on each show event
    target.addEventListener('show.bs.popover', function() {
        const popoverBody = elCreateEmpty('div', {"class": ["popover-body", "px-0"]});
        popoverInit.tooltip.querySelector('.popover-body').replaceWith(popoverBody);
        contentCallback(target, popoverBody);
        createPopoverClickHandler(popoverBody);
    }, false);
    return popoverInit;
}

/**
 * Creates a popover with two tabs
 * @param {EventTarget} target triggering element
 * @param {Function} tab1Callback callback to create the popover content for the first tab
 * @param {Function} tab2Callback callback to create the popover content for the second tab
 * @returns {object} BSN popover object
 */
//eslint-disable-next-line no-unused-vars
function createPopoverTabs(target, tab1Callback, tab2Callback) {
    const popoverInit = createPopoverInit(target, '', 'tabs');
    //update content on each show event
    target.addEventListener('show.bs.popover', function() {
        popoverInit.tooltip.querySelector('.popover-tabs').replaceWith(createPopoverBody('tabs'));
        const tabHeader = popoverInit.tooltip.querySelectorAll('.nav-link');
        const tabPanes = popoverInit.tooltip.querySelectorAll('.tab-pane');
        for (let i = 0; i < 2; i++) {
            tabHeader[i].addEventListener('click', function(event) {
                tabHeader[i].classList.add('active');
                tabPanes[i].classList.add('active', 'show');
                const j = i === 0 ? 1 : 0;
                tabHeader[j].classList.remove('active');
                tabPanes[j].classList.remove('active', 'show');
                event.preventDefault();
                event.stopPropagation();
            }, false);

            elClear(tabPanes[i]);
            const created = i === 0 ?
                tab1Callback(target, tabHeader[0], tabPanes[0]) :
                tab2Callback(target, tabHeader[1], tabPanes[1]);

            if (created === true) {
                createPopoverClickHandler(tabPanes[i]);
            }
            else {
                popoverInit.tooltip.querySelector('.popover-header').textContent = tabHeader[0].textContent;
                tabHeader[0].parentNode.parentNode.remove();
            }
        }
    }, false);
    return popoverInit;
}

/**
 * Populates the footer popover
 * @param {EventTarget} target the triggering target
 * @param {Element} popoverBody the popover body
 * @returns {void}
 */
function addActionsPopoverFooter(target, popoverBody) {
    const elapsed = currentState !== null
        ? fmtSongDuration(currentState.elapsedTime)
        : '0:00';
    popoverBody.appendChild(
        elCreateNodes('form', {"class": ["px-3"]}, [
            elCreateNodes('div', {"class": ["btn-group-vertical", "playbackPopoverBtns"]}, [
                elCreateNodes('div', {"class": ["btn-group"]}, [
                    elCreateText('button', {"class": ["btn", "btn-secondary", "mi", "rounded-start"], "id": "popoverFooterPrevBtn", "data-href": '{"cmd": "clickPrev", "options": []}'}, 'skip_previous'),
                    elCreateText('button', {"class": ["btn", "btn-secondary", "mi"], "id": "popoverFooterPlayBtn", "data-href": '{"cmd": "clickPlay", "options": []}'}, 'play_arrow'),
                    elCreateText('button', {"class": ["btn", "btn-secondary", "mi"], "id": "popoverFooterStopBtn", "data-href": '{"cmd": "clickStop", "options": []}'}, 'stop'),
                    elCreateText('button', {"class": ["btn", "btn-secondary", "mi", "rounded-end"], "id": "popoverFooterNextBtn", "data-href": '{"cmd": "clickNext", "options": []}'}, 'skip_next')
                ]),
                elCreateTextTn('div', {"class": ["w-100", "text-center", "p-2"]}, 'Seek seconds'),
                elCreateNodes('div', {"class": ["btn-group"]}, [
                    elCreateText('button', {"class": ["btn", "btn-secondary", "mi", "rounded-start"], "id": "popoverFooterFastRewindBtn", "data-href": '{"cmd": "clickFastRewindValue", "options": []}'}, 'fast_rewind'),
                    elCreateEmpty('input', {"class": ["form-control", "rounded-0", "text-center"], "id": "popoverFooterSeekInput", "value": lastSeekStep.toString()}),
                    elCreateText('button', {"class": ["btn", "btn-secondary", "mi", "rounded-end"], "id": "popoverFooterFastForwardBtn", "data-href": '{"cmd": "clickFastForwardValue", "options": []}'}, 'fast_forward')
                ]),
                elCreateTextTn('div', {"class": ["w-100", "text-center", "p-2"]}, 'Goto position'),
                elCreateNodes('div', {"class": ["btn-group"]}, [
                    elCreateEmpty('input', {"class": ["form-control", "rounded-start", "rounded-end-0"], "id": "popoverFooterGotoInput", "value": elapsed}),
                    elCreateText('button', {"class": ["btn", "btn-secondary", "mi", "rounded-end"], "id": "popoverFooterGotoBtn", "data-href": '{"cmd": "clickGotoPos", "options": []}'}, 'play_for_work')
                ])
            ])
        ])
    );
}