Source: modal.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 modal_js */

/**
 * Opens a modal
 * @param {string} modal name of the modal
 * @returns {void}
 */
//eslint-disable-next-line no-unused-vars
function openModal(modal) {
    switch(modal) {
        case 'modalScripts':
            showListScriptModal();
            break;
        default:
            uiElements[modal].show();
    }
}

/**
 * Gets the currently opened modal
 * @returns {Element} the opened modal or null if no modal is opened
 */
 function getOpenModal() {
    return document.querySelector('.modal.show');
}

/**
 * Sets the focus to the first input or select element
 * @param {EventTarget | Element} container the parent of the elements
 * @returns {void}
 */
function focusFirstInput(container) {
    const inputs = container.querySelectorAll('.modal-body input, .modal-body select, .modal-body textarea');
    for (const input of inputs) {
        if (input.offsetHeight === 0) {
            // element is not shown
            continue;
        }
        if (input.getAttribute('readonly') !== null &&
            input.getAttribute('data-is') !== 'mympd-select-search')
        {
            // readonly element
            continue;
        }
        setFocus(input);
        break;
    }
}

/**
 * Populates the entities element
 * @param {string} id element id
 * @param {Array} entities array with entities to add
 * @returns {void}
 */
function populateEntities(id, entities) {
    elGetById(id).value = arrayToLines(entities);
}


/**
 * Handles the apply jsonrpc response for a list modal
 * Shows the possible error and leaves the modal open
 * @param {object} obj jsonrpc response
 * @returns {boolean} true on close, else false
 */
function modalListApply(obj) {
    return _modalClose(obj, false, false);
}

/**
 * Handles the apply jsonrpc response for a modal
 * Shows the possible error and leaves the modal open
 * @param {object} obj jsonrpc response
 * @returns {boolean} true on close, else false
 */
function modalApply(obj) {
    return _modalClose(obj, false, true);
}

/**
 * Handles the save/apply jsonrpc response for a modal
 * Shows the possible error or closes the modal
 * @param {object} obj jsonrpc response
 * @returns {boolean} true on close, else false
 */
function modalClose(obj) {
    return _modalClose(obj, true, true);
}

/**
 * Handles the save/apply jsonrpc response for a modal
 * @param {object} obj jsonrpc response
 * @param {boolean} close Close the modal if there is no error?
 * @param {boolean} isForm Highlight form element on error?
 * @returns {boolean} true on close, else false
 */
function _modalClose(obj, close, isForm) {
    const modal = getOpenModal();
    const modalId = modal.getAttribute('id');
    const spinnerEl = modal.querySelector('.spinner-border');
    if (spinnerEl) {
        btnWaiting(spinnerEl.parentNode, false);
    }
    if (obj.error) {
        if (isForm === false ||
            highlightInvalidInput(modalId, obj) === false)
        {
            showModalAlert(obj);
        }
        return false;
    }
    // no error
    if (close === true) {
        uiElements[modalId].hide();
    }
    return true;
}

/**
 * Removes all invalid indicators and warning messages from a modal with the given id.
 * @param {string} id id of the modal
 * @returns {void}
 */
 function cleanupModalId(id) {
    cleanupModal(elGetById(id));
}

/**
 * Removes all invalid indicators and warning messages from a modal pointed by el.
 * @param {Element} el the modal element
 * @returns {void}
 */
function cleanupModal(el) {
    //remove validation warnings
    removeIsInvalid(el);
    //remove enter pin footer
    const enterPinFooter = el.querySelector('.enterPinFooter');
    if (enterPinFooter !== null) {
        removeEnterPinFooter(enterPinFooter);
    }
    //remove error messages
    hideModalAlert(el);
    //remove spinners
    const spinners = el.querySelectorAll('.spinner-border');
    for (let i = spinners.length - 1; i >= 0; i--) {
        const btn = spinners[i].parentNode;
        if (btn) {
            btn.removeAttribute('disabled');
        }
        spinners[i].remove();
    }
}

/**
 * Shows a confirmation modal
 * @param {string} text text to show (already translated)
 * @param {string} btnText text for the yes button (already translated)
 * @param {Function} callback callback function on confirmation
 * @returns {void}
 */
 function showConfirm(text, btnText, callback) {
    elGetById('modalConfirmText').textContent = text;
    const yesBtn = elCreateText('button', {"id": "modalConfirmYesBtn", "class": ["btn", "btn-danger"]}, btnText);
    yesBtn.addEventListener('click', function() {
        if (callback !== undefined &&
            typeof(callback) === 'function')
        {
            callback();
        }
        uiElements.modalConfirm.hide();
    }, false);
    elGetById('modalConfirmYesBtn').replaceWith(yesBtn);
    uiElements.modalConfirm.show();
}

/**
 * Shows an inline confirmation (for open modals)
 * @param {Element | ChildNode} el parent element to add the confirmation dialog
 * @param {string} text text to show (already translated)
 * @param {string} btnText text for the yes button (already translated)
 * @param {Function} callback callback function on confirmation
 * @returns {void}
 */
function showConfirmInline(el, text, btnText, callback) {
    const confirm = elCreateNode('div', {"class": ["alert", "alert-danger", "mt-2", "not-clickable"]},
        elCreateText('p', {}, text)
    );

    const cancelBtn = elCreateTextTn('button', {"class": ["btn", "btn-secondary"]}, 'Cancel');
    cancelBtn.addEventListener('click', function(event) {
        event.stopPropagation();
        this.parentNode.remove();
    }, false);
    confirm.appendChild(cancelBtn);

    const yesBtn = elCreateText('button', {"class": ["btn", "btn-danger", "float-end"]}, btnText);
    yesBtn.addEventListener('click', function(event) {
        event.stopPropagation();
        if (callback !== undefined &&
            typeof(callback) === 'function')
        {
            callback();
        }
        this.parentNode.remove();
    }, false);
    confirm.appendChild(yesBtn);
    el.appendChild(confirm);
}