Source: viewBrowsePlaylist.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 viewBrowsePlaylists_js */

/**
 * Handles BrowsePlaylistDetail
 * @returns {void}
 */
function handleBrowsePlaylistDetail() {
    handleSearchExpression('BrowsePlaylistDetail');

    sendAPI("MYMPD_API_PLAYLIST_CONTENT_LIST", {
        "offset": app.current.offset,
        "limit": app.current.limit,
        "expression": app.current.search,
        "plist": app.current.tag,
        "fields": settings.viewBrowsePlaylistDetailFetch.fields
    }, parsePlaylistDetail, true);
}

/**
 * Handles BrowsePlaylistList
 * @returns {void}
 */
function handleBrowsePlaylistList() {
    handleSearchSimple('BrowsePlaylistList');
    toggleBtnChkId('BrowsePlaylistListSortDesc', app.current.sort.desc);
    selectTag('BrowsePlaylistListSortTagsList', undefined, app.current.sort.tag);

    sendAPI("MYMPD_API_PLAYLIST_LIST", {
        "offset": app.current.offset,
        "limit": app.current.limit,
        "searchstr": app.current.search,
        "type": 0,
        "sort": app.current.sort.tag,
        "sortdesc": app.current.sort.desc,
        "fields": settings.viewBrowsePlaylistList.fields
    }, parsePlaylistList, true);
    elHideId('playlistDetailAlert');
}

/**
 * Initializes the playlist elements
 * @returns {void}
 */
function initViewPlaylist() {
    elGetById('BrowsePlaylistDetailSortTagsDropdown').addEventListener('click', function(event) {
        if (event.target.nodeName === 'BUTTON') {
            event.preventDefault();
            currentPlaylistSort(getData(event.target, 'tag'), getBtnChkValueId('BrowsePlaylistDetailSortDesc'));
        }
    }, false);

    initSearchSimple('BrowsePlaylistList');
    initSearchExpression('BrowsePlaylistDetail');
    initSortBtns('BrowsePlaylistList');

    setView('BrowsePlaylistList');
    setView('BrowsePlaylistDetail');
}

/**
 * Shows the edit sticker modal for the current playlist
 * @param {Event} event triggering click event
 * @returns {void}
 */
//eslint-disable-next-line no-unused-vars
function showPlaylistSticker(event) {
    event.preventDefault();
    const uri = getDataId('BrowsePlaylistDetailList', 'uri');
    showStickerModal(uri, 'playlist');
}

/**
 * Click event handler for playlist list
 * @param {MouseEvent} event click event
 * @param {HTMLElement} target calculated target
 * @returns {void}
 */
function viewPlaylistListListClickHandler(event, target) {
    if (getData(target, 'smartpls-only') === false) {
        clickPlaylist(getData(target, 'uri'), event);
    }
    else {
        showNotification(tn('Playlist is empty'), 'playlist', 'warn');
    }
}

/**
 * Click event handler for playlist detail list
 * @param {MouseEvent} event click event
 * @param {HTMLElement} target calculated target
 * @returns {void}
 */
function viewPlaylistDetailListClickHandler(event, target) {
    clickSong(getData(target, 'uri'), event);
}

/**
 * Parses the MYMPD_API_PLAYLIST_LIST jsonrpc response
 * @param {object} obj jsonrpc response
 * @returns {void}
 */
function parsePlaylistList(obj) {
    const table = elGetById('BrowsePlaylistListList');
    if (checkResult(obj, table, undefined) === false) {
        return;
    }

    if (settings['view' + app.id].mode === 'table') {
        const tfoot = table.querySelector('tfoot');
        elClear(tfoot);
        updateTable(obj, 'BrowsePlaylistList', function(row, data) {
            parsePlaylistListUpdate(row, data);
        });
        addTblFooter(tfoot,
            elCreateTextTnNr('span', {}, 'Num entries', obj.result.totalEntities)
        );
        return;
    }
    if (settings['view' + app.id].mode === 'grid') {
        updateGrid(obj, app.id, function(card, data) {
            parsePlaylistListUpdate(card, data);
        });
        return;
    }
    updateList(obj, app.id, function(card, data) {
        parsePlaylistListUpdate(card, data);
    });
}

/**
 * Callback function for row or card
 * @param {HTMLElement} card Row or card
 * @param {object} data Data object
 * @returns {void}
 */
function parsePlaylistListUpdate(card, data) {
    const rowTitle = settingsWebuiFields.clickPlaylist.validValues[settings.webuiSettings.clickPlaylist];
    setData(card, 'uri', data.uri);
    setData(card, 'type', data.Type);
    setData(card, 'name', data.Name);
    setData(card, 'smartpls-only', data.smartplsOnly);
    card.setAttribute('title', tn(rowTitle));
}

/**
 * Parses the MYMPD_API_PLAYLIST_CONTENT_LIST jsonrpc response
 * @param {object} obj jsonrpc response
 * @returns {void}
 */
function parsePlaylistDetail(obj) {
    const table = elGetById('BrowsePlaylistDetailList');
    const imageEl = elGetById('BrowsePlaylistDetailImage');
    const stickerEl = elGetById('BrowsePlaylistDetailSticker');
    elClear(imageEl);
    elClear(stickerEl);

    if (checkResult(obj, table, undefined) === false) {
        return;
    }

    // set toolbar
    let rw = false;
    if (isMPDplaylist(obj.result.plist) === false) {
        // playlist in music directory
        setData(table, 'ro', true);
        elHideId('BrowsePlaylistDetailContentBtns');
        elHideId('BrowsePlaylistDetailSmartPlaylistContentBtns');
        table.setAttribute('data-rw', 'false');
    }
    else if (obj.result.smartpls === true) {
        // smart playlist
        setData(table, 'ro', true);
        elHideId('BrowsePlaylistDetailContentBtns');
        elShowId('BrowsePlaylistDetailSmartPlaylistContentBtns');
        table.setAttribute('data-rw', 'false');
    }
    else {
        // mpd playlist
        setData(table, 'ro', false);
        elShowId('BrowsePlaylistDetailContentBtns');
        elHideId('BrowsePlaylistDetailSmartPlaylistContentBtns');
        table.setAttribute('data-rw', 'true');
        rw = true;
    }

    if (obj.result.pics === true) {
        const img = elCreateEmpty('div', {});
        img.style.backgroundImage = getCssImageUri('/playlistart?type=' +
            (obj.result.smartpls === false ? 'plist' : 'smartpls') + '&playlist=' + myEncodeURIComponent(obj.result.plist));
        img.addEventListener('click', function(event) {
            zoomPicture(event.target);
        }, false);
        imageEl.appendChild(img);
        elShow(imageEl);
    }
    else {
        elHide(imageEl);
    }

    if (features.featStickers === true) {
        let stickerCount = 0;
        const tbl = elCreateEmpty('table', {'class':['table', 'table-sm']});
        for (const key in obj.result.sticker) {
            tbl.appendChild(
                elCreateNodes('tr', {}, [
                    elCreateText('th', {'class': ['pe-2']}, key ),
                    elCreateText('td', {}, obj.result.sticker[key])
                ])
            );
            stickerCount++;
        }
        if (obj.result.lastPlayedSong.uri !== '') {
            stickerCount++;
            const resumeBtn = pEl.resumeBtn.cloneNode(true);
            resumeBtn.classList.add('ms-3', 'dropdown');
            resumeBtn.classList.remove('dropup');
            setData(resumeBtn, 'pos', obj.result.lastPlayedSong.pos);
            new BSN.Dropdown(resumeBtn.firstElementChild);
            resumeBtn.lastElementChild.firstElementChild.addEventListener('click', function(event) {
                clickResumePlist(event);
            }, false);
            tbl.appendChild(
                elCreateNodes('tr', {}, [
                    elCreateTextTn('th', {'class': ['pe-2']}, 'Last played'),
                    elCreateNodes('td', {}, [
                        document.createTextNode(obj.result.lastPlayedSong.title),
                        resumeBtn
                    ])
                ])
            );
        }
        if (stickerCount > 0) {
            stickerEl.appendChild(elCreateTextTn('h4', {}, 'Sticker'));
            stickerEl.appendChild(tbl);
        }
    }

    setData(table, 'playlistlength', obj.result.totalEntities);
    setData(table, 'uri', obj.result.plist);
    setData(table, 'type', obj.result.smartpls === true ? 'smartpls' : 'plist');
    elGetById('BrowsePlaylistDetailTitle').textContent =
        (obj.result.smartpls === true ? tn('Smart playlist') : tn('Playlist')) + ': ' + obj.result.plist;
    if (features.featStickers === true) {
        const feedbackGrp = elGetById('BrowsePlaylistDetailFeedback').firstElementChild;
        setData(feedbackGrp, 'uri', obj.result.plist);
        setFeedback(feedbackGrp, obj.result.like, obj.result.rating);
    }

    if (settings['view' + app.id].mode === 'table') {
        const tfoot = table.querySelector('tfoot');
        elClear(tfoot);
        updateTable(obj, app.id, function(row, data) {
            if (rw === true) {
                row.setAttribute('draggable', 'true');
                row.setAttribute('tabindex', 0);
            }
            parsePlaylistDetailUpdate(row, data);
        });
        setPlaylistDetailListFooter(obj.result.totalEntities, obj.result.totalTime);
        return;
    }
    if (settings['view' + app.id].mode === 'grid') {
        updateGrid(obj, app.id, function(card, data) {
            if (rw === true) {
                card.setAttribute('draggable', 'true');
                card.setAttribute('tabindex', '0');
            }
            parsePlaylistDetailUpdate(card, data);
        });
        return;
    }
    updateList(obj, app.id, function(card, data) {
        if (rw === true) {
            card.setAttribute('draggable', 'true');
            card.setAttribute('tabindex', '0');
        }
        parsePlaylistDetailUpdate(card, data);
    });
}

/**
 * Callback function for row or card
 * @param {HTMLElement} card Row or card
 * @param {object} data Data object
 * @returns {void}
 */
function parsePlaylistDetailUpdate(card, data) {
    const rowTitle = settingsWebuiFields.clickSong.validValues[settings.webuiSettings.clickSong];
    card.setAttribute('id', 'playlistSongId' + data.Pos);
    setData(card, 'type', data.Type);
    setData(card, 'uri', data.uri);
    setData(card, 'name', data.Title);
    setData(card, 'pos', data.Pos);
    card.setAttribute('title', tn(rowTitle));
}

/**
 * Sets the footer text for the playlist content view
 * @param {number} entities entity count
 * @param {number} playtime total playtime
 * @returns {void}
 */
function setPlaylistDetailListFooter(entities, playtime) {
    const footerEl = entities === -1
        ? elCreateNode('small', {},
              elCreateText('button', {"data-title-phrase": "Enumerate", "title": "Enumerate", "id": "BrowsePlaylistDetailEnumerateBtn", "class": ["btn", "btn-sm", "btn-secondary", "mi"]}, 'insights')
          )
        : elCreateNodes('small', {}, [
              elCreateTextTnNr('span', {}, 'Num songs', entities),
              elCreateText('span', {}, smallSpace + nDash + smallSpace + fmtDuration(playtime))
          ]);

    const tfoot = elGetById('BrowsePlaylistDetailList').querySelector('tfoot');
    const colspan = settings.viewBrowsePlaylistDetail.fields.length + 1;

    elReplaceChild(tfoot,
        elCreateNode('tr', {"class": ["not-clickable"]},
            elCreateNode('td', {"colspan": colspan}, footerEl)
        )
    );

    if (entities === -1) {
        footerEl.addEventListener('click', function() {
            currentPlaylistEnumerate();
        }, false);
    }
}

/**
 * Enumerates the current displayed playlist
 * @returns {void}
 */
function currentPlaylistEnumerate() {
    btnWaitingId('BrowsePlaylistDetailEnumerateBtn', true);
    sendAPI("MYMPD_API_PLAYLIST_CONTENT_ENUMERATE", {
        "plist": getDataId('BrowsePlaylistDetailList', 'uri')
    }, function(obj) {
        if (obj.result) {
            setPlaylistDetailListFooter(obj.result.entities, obj.result.playtime);
        }
        else {
            setPlaylistDetailListFooter(-1, 0);
        }
    }, true);
}

/**
 * Opens the playlist detail view
 * @param {string} uri shows the playlist detail view
 * @returns {void}
 */
//eslint-disable-next-line no-unused-vars
function gotoPlaylist(uri) {
    setUpdateViewId('BrowsePlaylistDetailList');
    appGoto('Browse', 'Playlist', 'Detail', 0, undefined, undefined, {'tag': '', 'desc': false}, uri, '');
}

/**
 * Shuffles the playlist
 * @returns {void}
 */
//eslint-disable-next-line no-unused-vars
function currentPlaylistShuffle() {
    setUpdateViewId('BrowsePlaylistDetailList');
    sendAPI("MYMPD_API_PLAYLIST_CONTENT_SHUFFLE", {
        "plist": getDataId('BrowsePlaylistDetailList', 'uri')
    }, function() {
        unsetUpdateViewId('BrowsePlaylistDetailList');
    }, true);
}

/**
 * Validates the currently displayed playlist
 * @param {boolean} remove true = remove invalid entries, false = count number of invalid entries
 * @returns {void}
 */
//eslint-disable-next-line no-unused-vars
function currentPlaylistValidate(remove) {
    const plist = getDataId('BrowsePlaylistDetailList', 'uri');
    setUpdateViewId('BrowsePlaylistDetailList');
    playlistValidate(plist, remove);
}

/**
 * Deduplicates the currently displayed playlist
 * @param {boolean} remove true = remove invalid entries, false = count number of invalid entries
 * @returns {void}
 */
//eslint-disable-next-line no-unused-vars
function currentPlaylistDedup(remove) {
    setUpdateViewId('BrowsePlaylistDetailList');
    const plist = getDataId('BrowsePlaylistDetailList', 'uri'); 
    playlistDedup(plist, remove);
}

/**
 * Validates and deduplicates the currently displayed playlist
 * @param {boolean} remove true = remove invalid entries, false = count number of invalid entries
 * @returns {void}
 */
//eslint-disable-next-line no-unused-vars
function currentPlaylistValidateDedup(remove) {
    setUpdateViewId('BrowsePlaylistDetailList');
    const plist = getDataId('BrowsePlaylistDetailList', 'uri'); 
    playlistValidateDedup(plist, remove);
}

/**
 * Sorts the playlist by tag
 * @param {string} tag sort tag
 * @param {boolean} sortdesc sort descending
 * @returns {void}
 */
//eslint-disable-next-line no-unused-vars
function currentPlaylistSort(tag, sortdesc) {
    setUpdateViewId('BrowsePlaylistDetailList');
    sendAPI("MYMPD_API_PLAYLIST_CONTENT_SORT", {
        "plist": getDataId('BrowsePlaylistDetailList', 'uri'),
        "tag": tag,
        "sortdesc": sortdesc
    }, function() {
        unsetUpdateViewId('BrowsePlaylistDetailList');
    }, true);
}

/**
 * Moves a song in the current displayed playlist
 * @param {number} from from position
 * @param {number} to to position
 * @returns {void}
 */
function currentPlaylistMoveSong(from, to) {
    sendAPI("MYMPD_API_PLAYLIST_CONTENT_MOVE_POSITION", {
        "plist": getDataId('BrowsePlaylistDetailList', 'uri'),
        "from": from,
        "to": to
    }, null, false);
}

/**
 * Adds the currently displayed playlist to the queue or home screen
 * @param {string} action one of appendQueue, appendPlayQueue,
 *                               insertAfterCurrentQueue, replaceQueue,
 *                               replacePlayQueue, addToHome
 * @returns {void}
 */
//eslint-disable-next-line no-unused-vars
function currentPlaylistAddTo(action) {
    const uri = getDataId('BrowsePlaylistDetailList', 'uri');
    const type = getDataId('BrowsePlaylistDetailList', 'type');
    switch(action) {
        case 'appendQueue':
            appendQueue(type, [uri]);
            break;
        case 'appendPlayQueue':
            appendPlayQueue(type, [uri]);
            break;
        case 'insertAfterCurrentQueue':
            insertAfterCurrentQueue(type, [uri], null);
            break;
        case 'replaceQueue':
            replaceQueue(type, [uri]);
            break;
        case 'replacePlayQueue':
            replacePlayQueue(type, [uri]);
            break;
        case 'addToHome':
            addPlistToHome(uri, type, uri);
            break;
        default:
            logError('Invalid action: ' + action);
    }
}