Source: customElements.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 customElements_js */

/**
 * Creates the pre-generated elements
 * @returns {void}
 */
function createPreGeneratedElements() {
    pEl.selectBtn = elCreateText('button', {"type": "button", "href": "#", "class": ["btn", "mi", "border-0", "color-darkgrey"], "data-title-phrase": "Select"}, 'radio_button_unchecked');
    pEl.selectAllBtn = elCreateText('button', {"type": "button", "href": "#", "class": ["btn", "mi", "border-0"], "data-title-phrase": "Select all"}, 'radio_button_unchecked');
    pEl.actionsBtn = elCreateText('a', {"data-action": "popover", "href": "#", "class": ["mi", "color-darkgrey"], "data-title-phrase": "Actions"}, ligatures['more']);
    pEl.actionsDiscBtn = elCreateText('a', {"data-action": "popover", "data-contextmenu": "disc", "href": "#", "class": ["mi", "color-darkgrey"], "data-title-phrase": "Actions"}, ligatures['more']);
    pEl.actionsWorkBtn = elCreateText('a', {"data-action": "popover", "data-contextmenu": "work", "href": "#", "class": ["mi", "color-darkgrey"], "data-title-phrase": "Actions"}, ligatures['more']);
    pEl.removeBtn = elCreateText('a', {"data-action": "quickRemove", "href": "#", "class": ["mi", "color-darkgrey", "me-1"], "data-title-phrase": "Remove"}, 'clear');
    pEl.playBtn = elCreateText('a', {"data-action": "quickPlay", "href": "#", "class": ["mi", "color-darkgrey", "me-1"], "data-title-phrase": "Quick play"}, 'play_arrow');
    pEl.showSongsBtn = elCreateText('a', {"data-action": "showSongsByTag", "class": ["mi", "color-darkgrey", "me-1"], "href": "#", "data-title-phrase": "Show songs"}, 'music_note');
    pEl.showAlbumsBtn = elCreateText('a', {"data-action": "showAlbumsByTag", "class": ["mi", "color-darkgrey", "me-1"], "href": "#", "data-title-phrase": "Show albums"}, 'album');
    pEl.showStickersBtn = elCreateText('a', {"data-action": "showStickersByTag", "class": ["mi", "color-darkgrey", "me-1", "featStickerAdv"], "href": "#", "data-title-phrase": "Sticker"}, 'discount');

    pEl.actionMenu = [
        pEl.actionsBtn,
        pEl.selectBtn
    ];
    pEl.actionMenuPlay = [
        pEl.playBtn,
        pEl.actionsBtn,
        pEl.selectBtn
    ];
    pEl.actionMenuRemove = [
        pEl.removeBtn,
        pEl.actionsBtn,
        pEl.selectBtn
    ];
    pEl.actionMenuPlayRemove = [
        pEl.playBtn,
        pEl.removeBtn,
        pEl.actionsBtn,
        pEl.selectBtn
    ];
    pEl.actionMenuBrowseDatabaseTagSongs = [
        pEl.showSongsBtn
    ];
    pEl.actionMenuBrowseDatabaseTagSongsStickers = [
        pEl.showSongsBtn,
        pEl.showStickersBtn
    ];
    pEl.actionMenuBrowseDatabaseTagSongsAlbums = [
        pEl.showSongsBtn,
        pEl.showAlbumsBtn
    ];
    pEl.actionMenuBrowseDatabaseTagSongsAlbumsStickers = [
        pEl.showSongsBtn,
        pEl.showAlbumsBtn,
        pEl.showStickersBtn
    ];
    pEl.actionMenuDisc = [
        pEl.actionsDiscBtn
    ];
    pEl.actionMenuWork = [
        pEl.actionsWorkBtn
    ];
    pEl.actionMenuDiscPlay = [
        pEl.playBtn,
        pEl.actionsDiscBtn
    ];
    pEl.actionMenuWorkPlay = [
        pEl.playBtn,
        pEl.actionsWorkBtn
    ];

    pEl.actionIcons = pEl.actionMenu;
    pEl.actionDiscIcons = pEl.actionMenuDisc;
    pEl.actionWorkIcons = pEl.actionMenuWork;
    pEl.actionQueueIcons = pEl.actionMenu;
    pEl.actionJukeboxIcons = pEl.actionMenu;
    pEl.actionPlaylistDetailIcons = pEl.actionMenu;
    pEl.actionPlaylistIcons = pEl.actionMenu;

    pEl.viewTable = elCreateNode('div', {'class': ['table-responsive', 'scrollContainer']},
        elCreateNodes('table', {'class': ['table', 'table-hover', 'table-sm']}, [
            elCreateNode('thead', {},
                elCreateEmpty('tr', {})
            ),
            elCreateEmpty('tbody', {'class': ['clickable']}),
            elCreateEmpty('tfoot')
        ])
    );
    pEl.viewGrid = elCreateNode('div', {'class': ['container-fluid', 'cardsContainer', 'scrollContainer']},
        elCreateEmpty('div', {'class': ['row', 'mympd-grid']})
    );
    pEl.viewList = elCreateNode('div', {'class': ['scrollContainer', 'listContainer']},
        elCreateEmpty('div', {'class': ['list-group']})
    );
    pEl.resumeBtn = elCreateNodes('div', {'class': ['btn-group', 'dropup']}, [
        elCreateText('button', {'type': 'button', 'data-title-phrase': 'Resume', 'data-bs-toggle': 'dropdown', 'class': ['btn', 'btn-sm', 'btn-secondary', 'dropdown-toggle', 'mi', 'mi-sm']}, 'replay'),
        elCreateNode('div', {'class': ['dropdown-menu', 'px-2']}, 
            elCreateNodes('div', {'class': ['d-grid', 'gap-2']}, [
                elCreateTextTn('button', {'class': ['btn', 'btn-sm', 'btn-secondary'], 'data-action': 'append'}, 'Append to queue'),
                elCreateTextTn('button', {'class': ['btn', 'btn-sm', 'btn-secondary'], 'data-action': 'insert'}, 'Insert at start of queue'),
                elCreateTextTn('button', {'class': ['btn', 'btn-sm', 'btn-secondary'], 'data-action': 'replace'}, 'Replace queue'),
            ])
        )
    ]);
}

/**
 * Initializes all elements with data-is attribute
 * We do not use the custom element is="" feature - safari does not support it
 * @param {Element} parent root element to find custom elements
 * @returns {void}
 */
function initElements(parent) {
    for (const el of parent.querySelectorAll('[data-is]')) {
        initElement(el, el.getAttribute('data-is'));
    }
}

/**
 * Initializes a custom element
 * @param {Element} el elment
 * @param {string} elType type of the custom element
 * @returns {void}
 */
function initElement(el, elType) {
    switch(elType) {
        case 'mympd-input-clear':
            setInputClear(el);
            break;
        case 'mympd-input-reset':
            setInputReset(el);
            break;
        case 'mympd-input-password':
            setInputPassword(el);
            break;
        case 'mympd-select-search':
            setSelectSearch(el);
            break;
        case 'mympd-select-new':
            setSelectNew(el);
            break;
        default:
            logError('Invalid element type: ' + elType);
    }
    el.removeAttribute('data-is');
}

/**
 * Creates an input element with clear button
 * @param {Element} el element
 * @returns {void}
 */
function setInputClear(el) {
    const button = elCreateText('button', {"data-title-phrase": "Clear", "class": ["mi", "mi-sm", "input-inner-button"]}, 'clear');
    el.button = button;
    el.classList.add('innerButton');
    if (el.classList.contains('alwaysEnabled')) {
        el.button.classList.add('alwaysEnabled');
    }
    if (el.parentNode.classList.contains('col')) {
        el.button.style.right = '1rem';
    }
    el.parentNode.insertBefore(el.button, el.nextElementSibling);
    el.updateBtn = function() {
        if (el.value === '') {
            elHide(el.button);
        }
        else {
            elShow(el.button);
        }
    };
    el.updateBtn();
    
    el.addEventListener('keyup', function() {
        el.updateBtn();
    }, false);

    el.button.addEventListener('click', function(event) {
        event.preventDefault();
        event.stopPropagation();
    }, false);

    el.button.addEventListener('mouseup', function(event) {
        event.target.previousElementSibling.value = '';
        const dataClearEvent = event.target.previousElementSibling.getAttribute('data-clear-event');
        if (dataClearEvent !== null) {
            const clearEvent = new KeyboardEvent(dataClearEvent, {key: 'Enter'});
            event.target.previousElementSibling.dispatchEvent(clearEvent);
        }
    }, false);
}

/**
 * Creates an input element with reset to default button
 * @param {Element} el element
 * @returns {void}
 */
function setInputReset(el) {
    const button = elCreateText('button', {"data-title-phrase": "Reset to default", "class": ["mi", "mi-sm", "input-inner-button"]}, 'settings_backup_restore');
    el.button = button;
    el.classList.add('innerButton');
    if (el.classList.contains('alwaysEnabled')) {
        el.button.classList.add('alwaysEnabled');
    }
    if (el.nextElementSibling) {
        el.parentNode.insertBefore(el.button, el.nextElementSibling);
    }
    else {
        el.parentNode.appendChild(el.button);
    }
    el.button.addEventListener('click', function(event) {
        event.preventDefault();
        event.stopPropagation();
        const input = event.target.previousElementSibling;
        input.value = getData(input, 'default') !== undefined ? getData(input, 'default') :
            (input.getAttribute('placeholder') !== null ? input.getAttribute('placeholder') : '');
    }, false);
}

/**
 * Creates an password input element with show button
 * @param {Element} el element
 * @returns {void}
 */
function setInputPassword(el) {
    const button = elCreateText('button', {"data-title-phrase": "Show or hide", "class": ["mi", "mi-sm", "input-inner-button"]}, 'visibility');
    el.button = button;
    el.classList.add('innerButton');
    if (el.classList.contains('alwaysEnabled')) {
        el.button.classList.add('alwaysEnabled');
    }
    if (el.parentNode.classList.contains('col-sm-8')) {
        el.button.style.right = '1rem';
    }
    if (el.nextElementSibling) {
        el.parentNode.insertBefore(el.button, el.nextElementSibling);
    }
    else {
        el.parentNode.appendChild(el.button);
    }
    el.button.addEventListener('click', function(event) {
        event.preventDefault();
        event.stopPropagation();
        const input = event.target.previousElementSibling;
        if (input.type === 'password') {
            input.type = 'text';
            event.target.textContent = 'visibility_off';
        }
        else {
            input.type = 'password';
            event.target.textContent = 'visibility';
        }
    }, false);
}

/**
 * Creates a searchable combined select + input element from an input element
 * @param {Element} el element
 * @returns {void}
 */
function setSelectSearch(el) {
    const filterInput = elCreateEmpty('input', {"class": ["form-control", "form-control-sm", "mb-1"], "data-placeholder-phrase": "Filter", "placeholder": tn('Filter')});
    const filterResult = elCreateEmpty('ul', {"class": ["list-group", "list-group-scroll"]});
    const dropdown = elCreateNodes('div', {"class": ["dropdown-menu", "p-2", "w-100"]}, [
        filterInput,
        filterResult
    ]);
    el.parentNode.insertBefore(dropdown, el.nextElementSibling);

    const button = elCreateEmpty('button', {"class": ["input-inner-button", "select-inner-button"], "data-bs-toggle": "dropdown"});
    if (el.parentNode.classList.contains('col-sm-8')) {
        button.style.right = '1rem';
    }
    button.style.cursor = 'default';
    el.parentNode.insertBefore(button, el.nextElementSibling);
    el.dropdownButton = button;
    el.filterInput = filterInput;
    el.filterResult = filterResult;
    el.classList.add('innerButton');
    if (el.classList.contains('alwaysEnabled')) {
        el.button.classList.add('alwaysEnabled');
    }
    setData(el, 'value', el.value);
    el.setValue = function(value, valueTn) {
        el.value = valueTn;
        setData(el, 'value', value);
    };
    el.addEventListener('keyup', function(event) {
        setData(el, 'value', event.target.value);
    }, false);
    el.filterResult.addEventListener('click', function(event) {
        event.preventDefault();
        event.stopPropagation();
        el.value = event.target.textContent;
        setData(el, 'value', getData(event.target, 'value'));
        BSN.Dropdown.getInstance(el.nextElementSibling).hide();
        const changeEvent = new Event('change');
        el.dispatchEvent(changeEvent);
    }, false);
    el.filterInput.addEventListener('keyup', function(event) {
        const cb = getData(el, 'cb-filter');
        const cbOptions = getData(el, 'cb-filter-options');
        // @ts-ignore
        window[cb](... cbOptions, event.target.value);
    }, false);
    el.filterInput.addEventListener('click', function(event) {
        event.stopPropagation();
    }, false);
    el.addFilterResult = function(text, value) {
        const item = elCreateTextTn('li', {"class": ["list-group-item", "list-group-item-action", "clickable"]}, text);
        setData(item, 'value', value);
        el.filterResult.appendChild(item);
    };
    el.addFilterResultPlain = function(value) {
        const item = elCreateText('li', {"class": ["list-group-item", "list-group-item-action", "clickable"]}, value);
        setData(item, 'value', value);
        el.filterResult.appendChild(item);
    };
    new BSN.Dropdown(el.dropdownButton);
    if (el.getAttribute('readonly') === 'readonly') {
        el.addEventListener('click', function(event) {
            BSN.Dropdown.getInstance(event.target.nextElementSibling).toggle();
        }, false);
    }
    if (userAgentData.isMobile === true) {
        //scrolling optimization for mobile browsers
        el.parentNode.addEventListener('shown.bs.dropdown', function() {
            domCache.body.style.overflow = 'hidden';
        }, false);
        el.parentNode.addEventListener('hidden.bs.dropdown', function() {
            domCache.body.style.overflow = 'initial';
        }, false);
    }
    el.dropdownButton.addEventListener('click', function(event) {
        event.preventDefault();
        event.stopPropagation();
    }, false);
}

/**
 * Creates a combined select + input element from an input element
 * @param {Element} el element
 * @returns {void}
 */
 function setSelectNew(el) {
    const filterResult = elCreateEmpty('ul', {"class": ["list-group", "list-group-scroll", "border"]});
    const dropdown = elCreateNodes('div', {"class": ["dropdown-menu", "p-2", "w-100"]}, [
        filterResult
    ]);
    el.parentNode.insertBefore(dropdown, el.nextElementSibling);

    const button = elCreateEmpty('button', {"class": ["input-inner-button", "select-inner-button"], "data-bs-toggle": "dropdown"});
    if (el.parentNode.classList.contains('col-sm-8')) {
        button.style.right = '1rem';
    }
    button.style.cursor = 'default';
    el.parentNode.insertBefore(button, el.nextElementSibling);
    el.dropdownButton = button;
    el.filterResult = filterResult;
    el.classList.add('innerButton');
    if (el.classList.contains('alwaysEnabled')) {
        el.button.classList.add('alwaysEnabled');
    }
    setData(el, 'value', el.value);
    el.setValue = function(value, valueTn) {
        el.value = valueTn;
        setData(el, 'value', value);
    };
    el.addEventListener('keyup', function(event) {
        setData(el, 'value', event.target.value);
    }, false);
    el.filterResult.addEventListener('click', function(event) {
        event.preventDefault();
        event.stopPropagation();
        el.value = event.target.textContent;
        setData(el, 'value', getData(event.target, 'value'));
        BSN.Dropdown.getInstance(el.nextElementSibling).hide();
        const changeEvent = new Event('change');
        el.dispatchEvent(changeEvent);
    }, false);
    el.addFilterResult = function(text, value) {
        const item = elCreateTextTn('li', {"class": ["list-group-item", "list-group-item-action", "clickable"]}, text);
        setData(item, 'value', value);
        el.filterResult.appendChild(item);
    };
    el.addFilterResultPlain = function(value) {
        const item = elCreateText('li', {"class": ["list-group-item", "list-group-item-action", "clickable"]}, value);
        setData(item, 'value', value);
        el.filterResult.appendChild(item);
    };
    new BSN.Dropdown(el.dropdownButton);
    if (userAgentData.isMobile === true) {
        //scrolling optimization for mobile browsers
        el.parentNode.addEventListener('shown.bs.dropdown', function() {
            domCache.body.style.overflow = 'hidden';
        }, false);
        el.parentNode.addEventListener('hidden.bs.dropdown', function() {
            domCache.body.style.overflow = 'initial';
        }, false);
    }
    el.dropdownButton.addEventListener('click', function(event) {
        event.preventDefault();
        event.stopPropagation();
    }, false);
}