Source: globales.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 globales_js */

/** @type {number} */
const startTime = Date.now();

// generate uniq id for this browser session
/** @type {number} */
const jsonrpcClientIdMin = 100000;
const jsonrpcClientIdMax = 999999;
const jsonrpcClientId = Math.floor(Math.random() * (jsonrpcClientIdMax - jsonrpcClientIdMin + 1) + jsonrpcClientIdMin);
let jsonrpcRequestId = 0;

const jsonRpcError = {
    "jsonrpc": "2.0",
    "id": 0,
    "error": {
        "method": "",
        "facility": "general",
        "severity": "error",
        "message": "",
        "data": {}
    }
};

let socket = null;

let websocketKeepAliveTimer = null;
let websocketLastPong = null;
let searchTimer = null;
let resizeTimer = null;
let progressTimer = null;

/** @type {number} */
const searchTimerTimeout = 500;

/** @type {object} */
let currentSongObj = {};

/** @type {object} */
let currentState = {};

/** @type {object} */
let settings = {
    /** @type {number} */
    "loglevel": 2,
    "partition": {}
};

/** @type {boolean} */
let myMPDready = false;

/** @type {boolean} */
let appInited = false;

/** @type {boolean} */
let scriptsInited = false;

/** @type {boolean} */
let uiEnabled = true;

/** @type {string} */
let settingsParsed = 'no';

// Reference to dom node for drag & drop
/** @type {EventTarget} */
let dragEl = undefined;

/** @type {boolean} */
let showSyncedLyrics = false;

/** @type {boolean} */
let scrollSyncedLyrics = true;

/** @type {string} */
const subdir = window.location.pathname.replace('/index.html', '').replace(/\/$/, '');

/** @type {object} */
const ligatures = {
    'checked': 'task_alt',
    'more': 'menu',
    'unchecked': 'radio_button_unchecked',
    'partitionSpecific': 'dashboard',
    'browserSpecific': 'web_asset',
    'stared': 'star',
    'star': 'star_border'
};

/** @type {Array} */
const widgetRefresh = [];

// pre-generated elements
/** @type {object} */
const pEl = {};

/** @type {string} */
const smallSpace = '\u2009';

/** @type {string} */
const nDash = '\u2013';

/** @type {string} */
let tagAlbumArtist = 'AlbumArtist';

/** @type {object} */
const albumFilters = [
    'AlbumArtist',
    'Composer',
    'Performer',
    'Conductor',
    'Ensemble'
];

/** @type {object} */
const webradioFields = [
    'StreamUri',
    'Homepage',
    'Genres',
    'Country',
    'Region',
    'Languages',
    'Codec',
    'Bitrate',
    'Description',
    'Added',
    'Last-Modified'
];

/** @type {object} */
const session = {
    "token": "",
    "timeout": 0
};

/** @type {number} */
const sessionLifetime = 1780;

/** @type {number} */
const sessionRenewInterval = sessionLifetime * 500;

let sessionTimer = null;

/** log message buffer */
const messages = [];
/** @type {number} */
const messagesMax = 100;

/** @type {boolean} */
const debugMode = document.querySelector("script").src.replace(/^.*[/]/, '') === 'combined.js' ? false : true;

/** @type {string} */
const scriptsUri = 'https://github.com/jcorporation/mympd-scripts/tree/main/';
/** @type {string} */
const scriptsImportUri = 'https://raw.githubusercontent.com/jcorporation/mympd-scripts/main/';

/** @type {object} */
const imageExtensions = ['webp', 'png', 'jpg', 'jpeg', 'svg', 'avif'];

/** @type {string} */
let locale = navigator.language || navigator.userLanguage;

const localeMap = {
    'de': 'de-DE',
    'es': 'es-ES',
    'fi': 'fi-FI',
    'fr': 'fr-FR',
    'it': 'it-IT',
    'ja': 'ja-JP',
    'ko': 'ko-KR',
    'nl': 'nl-NL',
    'zh': 'zh-Hans',
    'zh-CN': 'zh-Hans',
    'zh-TW': 'zh-Hant'
};

/** @type {object} */
let materialIcons = {};
/** @type {object} */
let phrasesDefault = {};
/** @type {object} */
let phrases = {};

/** @type {number} */
let lastSeekStep = 10;

/** @type {number} */
const maxElementsPerPage = 1000;

/**
 * This settings are saved in the browsers localStorage
 */
const settingsLocalFields = {
    "localPlaybackAutoplay": {
        "defaultValue": false,
        "inputType": "checkbox",
        "title": "Autoplay",
        "form": "modalSettingsLocalPlaybackCollapse",
        "help": "helpSettingsLocalPlaybackAutoplay",
        "hintIcon": ligatures['browserSpecific'],
        "hintText": "Browser specific setting"
    },
    "partition": {
        "defaultValue": "default",
        "inputType": "none"
    },
    "scaleRatio": {
        "defaultValue": "1.0",
        "inputType": "text",
        "title": "Scale ratio",
        "form": "modalSettingsThemeFrm2",
        "hintIcon": ligatures['browserSpecific'],
        "hintText": "Browser specific setting",
        "cssClass": ["featMobile"],
        "validate": {
            "cmd": "validateFloatEl",
            "options": []
        },
        "invalid": "Invalid scale ratio"
    },
    "viewMode": {
        "defaultValue": "auto",
        "validValues": {
            "auto": "Autodetect",
            "mobile": "Mobile",
            "desktop": "Desktop"
        },
        "inputType": "select",
        "title": "View mode",
        "form": "modalSettingsThemeFrm2",
        "help": "helpSettingsViewMode",
        "hintIcon": ligatures['browserSpecific'],
        "hintText": "Browser specific setting",
        "sort": 1
    }
};

const localSettings = {
    /** @type {string} */
    "viewMode": settingsLocalFields.viewMode.defaultValue,
    /** @type {boolean} */
    "localPlaybackAutoplay": settingsLocalFields.localPlaybackAutoplay.defaultValue,
    /** @type {string} */
    "partition": settingsLocalFields.partition.defaultValue,
    /** @type {string} */
    "scaleRatio": settingsLocalFields.scaleRatio.defaultValue
};

// partition specific settings
const settingsPartitionFields = {
    "mpdStreamPort": {
        "defaultValue": defaults["PARTITION_MPD_STREAM_PORT"],
        "inputType": "text",
        "contentType": "number",
        "title": "Stream port",
        "form": "modalSettingsLocalPlaybackCollapse",
        "help": "helpSettingsStreamPort",
        "hintIcon": ligatures['partitionSpecific'],
        "hintText": "Partition specific setting"
    },
    "streamUri": {
        "defaultValue": defaults["PARTITION_MPD_STREAM_URI"],
        "placeholder": "auto",
        "inputType": "text",
        "title": "Stream URI",
        "form": "modalSettingsLocalPlaybackCollapse",
        "help": "helpSettingsStreamUri",
        "hintIcon": ligatures['partitionSpecific'],
        "hintText": "Partition specific setting"
    },
    "highlightColor": {
        "defaultValue": defaults["PARTITION_HIGHLIGHT_COLOR"],
        "inputType": "color",
        "title": "Highlight color",
        "form": "modalSettingsThemeFrm1",
        "hintIcon": ligatures['partitionSpecific'],
        "hintText": "Partition specific setting",
        "sort": 3
    },
    "highlightColorContrast": {
        "defaultValue": defaults["PARTITION_HIGHLIGHT_COLOR_CONTRAST"],
        "inputType": "color",
        "title": "Highlight contrast color",
        "form": "modalSettingsThemeFrm1",
        "hintIcon": ligatures['partitionSpecific'],
        "hintText": "Partition specific setting",
        "sort":4
    }
};

// global settings
const settingsFields = {
    "volumeMin": {
        "defaultValue": defaults["MYMPD_VOLUME_MIN"],
        "inputType": "text",
        "contentType": "number",
        "title": "Volume min.",
        "form": "modalSettingsVolumeFrm",
        "invalid": "Must be a number"
    },
    "volumeMax": {
        "defaultValue": defaults["MYMPD_VOLUME_MAX"],
        "inputType": "text",
        "contentType": "number",
        "title": "Volume max.",
        "form": "modalSettingsVolumeFrm",
        "invalid": "Must be a number"
    },
    "volumeStep": {
        "defaultValue": defaults["MYMPD_VOLUME_STEP"],
        "inputType": "text",
        "contentType": "number",
        "title": "Volume step",
        "form": "modalSettingsVolumeFrm",
        "invalid": "Must be a number"
    },
    "lyricsUsltExt": {
        "defaultValue": defaults["MYMPD_LYRICS_USLT_EXT"],
        "inputType": "text",
        "title": "Unsynced lyrics extension",
        "form": "modalSettingsLyricsCollapse",
        "help": "helpSettingsUsltExt"
    },
    "lyricsSyltExt": {
        "defaultValue": defaults["MYMPD_LYRICS_SYLT_EXT"],
        "inputType": "text",
        "title": "Synced lyrics extension",
        "form": "modalSettingsLyricsCollapse",
        "help": "helpSettingsSyltExt"
    },
    "lyricsVorbisUslt": {
        "defaultValue": defaults["LYRICS"],
        "inputType": "text",
        "title": "Unsynced lyrics vorbis comment",
        "form": "modalSettingsLyricsCollapse",
        "help": "helpSettingsVorbisUslt"
    },
    "lyricsVorbisSylt": {
        "defaultValue": defaults["SYNCEDLYRICS"],
        "inputType": "text",
        "title": "Synced lyrics vorbis comment",
        "form": "modalSettingsLyricsCollapse",
        "help": "helpSettingsVorbisSylt"
    },
    "lastPlayedCount": {
        "defaultValue": defaults["MYMPD_LAST_PLAYED_COUNT"],
        "inputType": "text",
        "contentType": "number",
        "title": "Last played list count",
        "form": "modalSettingsStatisticsFrm",
        "help": "helpSettingsLastPlayedCount"
    },
    "bookletName": {
        "defaultValue": defaults["MYMPD_BOOKLET_NAME"],
        "inputType": "text",
        "title": "Booklet filename",
        "form": "modalSettingsAlbumInfoFrm",
        "help": "helpSettingsBookletName"
    },
    "infoTxtName": {
        "defaultValue": defaults["MYMPD_INFO_TXT_NAME"],
        "inputType": "text",
        "title": "Info filename",
        "form": "modalSettingsAlbumInfoFrm",
        "help": "helpSettingsInfoTxtName"
    },
    "coverimageNames": {
        "defaultValue": defaults["MYMPD_COVERIMAGE_NAMES"],
        "inputType": "text",
        "title": "Filenames",
        "form": "modalSettingsAlbumartFrm",
        "help": "helpSettingsCoverimageNames",
        "cssClass": ["featLibrary"]
    },
    "thumbnailNames": {
        "defaultValue": defaults["MYMPD_THUMBNAIL_NAMES"],
        "inputType": "text",
        "title": "Thumbnail names",
        "form": "modalSettingsAlbumartFrm",
        "help": "helpSettingsThumbnailNames",
        "cssClass": ["featLibrary"],
    },
    "smartpls": {
        "defaultValue": defaults["MYMPD_SMARTPLS"],
        "inputType": "checkbox"
    },
    "smartplsPrefix": {
        "defaultValue": defaults["MYMPD_SMARTPLS_PREFIX"],
        "inputType": "text",
        "title": "Smart playlists prefix",
        "form": "modalSettingsSmartplsFrm",
        "help": "helpSettingsSmartplsPrefix"
    },
    "smartplsSort": {
        "defaultValue": "",
        "inputType": "select",
        "title": "Order",
        "form": "modalSettingsSmartplsFrm",
        "help": "helpSettingsSmartplsSort",
    },
    "smartplsInterval": {
        "defaultValue": defaults["MYMPD_SMARTPLS_INTERVAL_HOURS"],
        "inputType": "text",
        "contentType": "number",
        "title": "Update interval",
        "unit": "Hours",
        "form": "modalSettingsSmartplsFrm",
        "help": "helpSettingsSmartplsInterval"
    },
    "tagDiscEmptyIsFirst": {
        "defaultValue": defaults["MYMPD_TAG_DISC_EMPTY_IS_FIRST"],
        "inputType": "checkbox",
        "title": "Enforce disc tag",
        "form": "modalSettingsTagsFrm",
        "help": "helpSettingsTagDiscEmptyIsFirst"
    }
};

// webui specific settings
const settingsWebuiFields = {
    "clickSong": {
        "defaultValue": "append",
        "validValues": {
            "append": "Append to queue",
            "appendPlay": "Append to queue and play",
            "insertAfterCurrent": "Insert after current playing song",
            "replace": "Replace queue",
            "replacePlay": "Replace queue and play",
            "view": "Song details",
            "context": "Context menu"
        },
        "inputType": "select",
        "title": "Click song",
        "form": "modalSettingsDefaultActionsFrm"
    },
    "clickWebradiodb": {
        "defaultValue": "append",
        "validValues": {
            "append": "Append to queue",
            "appendPlay": "Append to queue and play",
            "insertAfterCurrent": "Insert after current playing song",
            "replace": "Replace queue",
            "replacePlay": "Replace queue and play",
            "add": "Add to favorites",
            "view": "Webradio details",
            "context": "Context menu"
        },
        "inputType": "select",
        "title": "Click webradio",
        "form": "modalSettingsDefaultActionsFrm"
    },
    "clickRadioFavorites": {
        "defaultValue": "append",
        "validValues": {
            "append": "Append to queue",
            "appendPlay": "Append to queue and play",
            "insertAfterCurrent": "Insert after current playing song",
            "replace": "Replace queue",
            "replacePlay": "Replace queue and play",
            "edit": "Edit webradio favorite",
            "context": "Context menu"
        },
        "inputType": "select",
        "title": "Click webradio favorite",
        "form": "modalSettingsDefaultActionsFrm"
    },
    "clickQueueSong": {
        "defaultValue": "play",
        "validValues": {
            "play": "Play",
            "view": "Song details",
            "context": "Context menu"
        },
        "inputType": "select",
        "title": "Click song in queue",
        "form": "modalSettingsDefaultActionsFrm"
    },
    "clickPlaylist": {
        "defaultValue": "append",
        "validValues": {
            "append": "Append to queue",
            "appendPlay": "Append to queue and play",
            "insertAfterCurrent": "Insert after current playing song",
            "replace": "Replace queue",
            "replacePlay": "Replace queue and play",
            "view": "View playlist",
            "context": "Context menu"
        },
        "inputType": "select",
        "title": "Click playlist",
        "form": "modalSettingsDefaultActionsFrm"
    },
    "clickFilesystemPlaylist": {
        "defaultValue": "view",
        "validValues": {
            "append": "Append to queue",
            "appendPlay": "Append to queue and play",
            "insertAfterCurrent": "Insert after current playing song",
            "replace": "Replace queue",
            "replacePlay": "Replace queue and play",
            "view": "View playlist",
            "context": "Context menu"
        },
        "inputType": "select",
        "title": "Click filesystem playlist",
        "form": "modalSettingsDefaultActionsFrm"
    },
    "clickQuickPlay": {
        "defaultValue": "replacePlay",
        "validValues": {
            "append": "Append to queue",
            "appendPlay": "Append to queue and play",
            "insertAfterCurrent": "Insert after current playing song",
            "replace": "Replace queue",
            "replacePlay": "Replace queue and play"
        },
        "inputType": "select",
        "title": "Click quick play button",
        "form": "modalSettingsDefaultActionsFrm"
    },
    "notificationPlayer": {
        "defaultValue": false,
        "inputType": "checkbox",
        "title": "Playback",
        "form": "modalSettingsFacilitiesFrm",
        "help": "helpSettingsNotificationPlayer"
    },
    "notificationQueue": {
        "defaultValue": true,
        "inputType": "checkbox",
        "title": "Queue",
        "form": "modalSettingsFacilitiesFrm",
        "help": "helpSettingsNotificationQueue"
    },
    "notificationGeneral": {
        "defaultValue": true,
        "inputType": "checkbox",
        "title": "General",
        "form": "modalSettingsFacilitiesFrm",
        "help": "helpSettingsNotificationGeneral"
    },
    "notificationDatabase": {
        "defaultValue": true,
        "inputType": "checkbox",
        "title": "Database",
        "form": "modalSettingsFacilitiesFrm",
        "help": "helpSettingsNotificationDatabase"
    },
    "notificationPlaylist": {
        "defaultValue": true,
        "inputType": "checkbox",
        "title": "Playlist",
        "form": "modalSettingsFacilitiesFrm",
        "help": "helpSettingsNotificationPlaylist"
    },
    "notificationScript": {
        "defaultValue": true,
        "inputType": "checkbox",
        "title": "Script",
        "form": "modalSettingsFacilitiesFrm",
        "help": "helpSettingsNotificationScript"
    },
    "notifyPage": {
        "defaultValue": true,
        "inputType": "checkbox",
        "title": "On page notifications",
        "form": "modalSettingsNotificationsFrm",
        "help": "helpSettingsNotifyPage"
    },
    "notifyWeb": {
        "defaultValue": false,
        "inputType": "checkbox",
        "title": "Web notifications",
        "form": "modalSettingsNotificationsFrm",
        "onClick": "toggleBtnNotifyWeb",
        "help": "helpSettingsNotifyWeb"
    },
    "mediaSession": {
        "defaultValue": false,
        "inputType": "checkbox",
        "title": "Media session",
        "form": "modalSettingsNotificationsFrm",
        "warn": "Browser has no MediaSession support",
        "help": "helpSettingsMediaSession"
    },
    "footerPlaybackControls": {
        "defaultValue": "pause",
        "validValues": {
            "pause": "pause only",
            "stop": "stop only",
            "both": "pause and stop"
        },
        "inputType": "select",
        "title": "Playback controls",
        "form": "modalSettingsFooterFrm",
        "sort": 0
    },
    "footerSettingsPlayback": {
        "defaultValue": true,
        "inputType": "checkbox",
        "title": "Playback settings",
        "form": "modalSettingsFooterFrm",
        "sort": 2
    },
    "footerPlaybackControlsPopover": {
        "defaultValue": false,
        "inputType": "checkbox",
        "title": "Advanced playback controls",
        "form": "modalSettingsFooterFrm",
        "sort": 1
    },
    "footerSeek": {
        "defaultValue": false,
        "inputType": "checkbox",
        "title": "Seek",
        "form": "modalSettingsFooterFrm",
        "sort": 1
    },
    "footerAudioFormat": {
        "defaultValue": false,
        "inputType": "checkbox",
        "title": "AudioFormat",
        "form": "modalSettingsFooterFrm",
        "sort": 1
    },
    "footerVolumeLevel": {
        "defaultValue": false,
        "inputType": "checkbox",
        "title": "Volume level",
        "form": "modalSettingsFooterFrm",
        "sort": 3
    },
    "footerNotifications": {
        "defaultValue": false,
        "inputType": "checkbox",
        "title": "Notification icon",
        "form": "modalSettingsFooterFrm",
        "sort": 4
    },
    "showHelp": {
        "defaultValue": true,
        "inputType": "checkbox",
        "title": "Show help",
        "form": "modalSettingsThemeFrm3",
        "help": "helpSettingsHelp"
    },
    "maxElementsPerPage": {
        "defaultValue": 100,
        "validValues": {
            "25": 25,
            "50": 50,
            "100": 100,
            "250": 250,
            "500": 500,
            "1000": 1000
        },
        "inputType": "select",
        "contentType": "number",
        "title": "Elements per page",
        "form": "modalSettingsListsFrm",
        "help": "helpSettingsMaxElementsPerPage"
    },
    "smallWidthTagRows": {
        "defaultValue": true,
        "inputType": "checkbox",
        "title": "Display tags in rows for small displays",
        "form": "modalSettingsListsFrm",
        "help": "helpSettingsSmallWidthTagRows"
    },
    "quickPlayButton": {
        "defaultValue": true,
        "inputType": "checkbox",
        "title": "Quick play button",
        "form": "modalSettingsListsFrm",
        "help": "helpSettingsQuickPlay"
    },
    "quickRemoveButton": {
        "defaultValue": true,
        "inputType": "checkbox",
        "title": "Quick remove button",
        "form": "modalSettingsListsFrm",
        "help": "helpSettingsQuickRemove"
    },
    "compactGrids": {
        "defaultValue": true,
        "inputType": "checkbox",
        "title": "Compact grids",
        "form": "modalSettingsListsFrm",
        "help": "helpSettingsCompactGrids"
    },
    "viewTitles": {
        "defaultValue": true,
        "inputType": "checkbox",
        "title": "Show view titles",
        "form": "modalSettingsListsFrm",
        "help": "helpSettingsViewTitles"
    },
    "showBackButton": {
        "defaultValue": false,
        "inputType": "checkbox",
        "title": "History back button",
        "form": "modalSettingsNavigationBarFrm",
        "help": "helpSettingsBackButton"
    },
    "enableHome": {
        "defaultValue": true,
        "inputType": "checkbox",
        "title": "Homescreen",
        "form": "modalSettingsFurtherFeaturesFrm",
        "help": "helpSettingsEnableHome"
    },
    "enableScripting": {
        "defaultValue": true,
        "inputType": "checkbox",
        "title": "Scripting",
        "form": "modalSettingsFurtherFeaturesFrm",
        "warn": "Lua is not compiled in",
        "help": "helpSettingsEnableScripting"
    },
    "enableTimer": {
        "defaultValue": true,
        "inputType": "checkbox",
        "title": "Timer",
        "form": "modalSettingsFurtherFeaturesFrm",
        "help": "helpSettingsEnableTimer"
    },
    "enableMounts": {
        "defaultValue": true,
        "inputType": "checkbox",
        "title": "Mounts",
        "form": "modalSettingsFurtherFeaturesFrm",
        "warn": "MPD does not support mounts",
        "help": "helpSettingsEnableMounts"
    },
    "enableLocalPlayback": {
        "defaultValue": false,
        "inputType": "checkbox",
    },
    "enablePartitions": {
        "defaultValue": false,
        "inputType": "checkbox",
        "title": "Partitions",
        "form": "modalSettingsFurtherFeaturesFrm",
        "warn": "MPD does not support partitions",
        "help": "helpSettingsEnablePartitions"
    },
    "enableLyrics": {
        "defaultValue": true,
        "inputType": "checkbox",
    },
    "theme": {
        "defaultValue": "dark",
        "validValues": {
            "auto": "Autodetect",
            "dark": "Dark",
            "light": "Light"
        },
        "inputType": "select",
        "title": "Theme",
        "form": "modalSettingsThemeFrm1",
        "onChange": "eventChangeTheme",
        "sort": 0
    },
    "gridSize": {
        "defaultValue": 175,
        "inputType": "text",
        "contentType": "number",
        "title": "Grid size",
        "form": "modalSettingsListsFrm",
        "invalid": "Must be a number and greater than zero",
        "validate": {
            "cmd": "validateUintEl",
            "options": []
        },
        "unit": "Pixel",
        "help": "helpSettingsGridSize"
    },
    "bgCover": {
        "defaultValue": true,
        "inputType": "checkbox",
        "title": "Albumart",
        "form": "modalSettingsBgFrm",
        "sort": 0
    },
    "bgCssFilter": {
        "defaultValue": "grayscale(100%) opacity(20%)",
        "inputType": "text",
        "title": "CSS filter",
        "form": "modalSettingsBgFrm",
        "sort": 1
    },
    "bgColor": {
        "defaultValue": "#060708",
        "inputType": "color",
        "title": "Color",
        "form": "modalSettingsBgFrm",
        "reset": true,
        "sort": 2,
        "validate": {
            "cmd": "validateColorEl",
            "options": []
        },
        "invalid": "Must be a hex color value"
    },
    "bgImage": {
        "defaultValue": "",
        "inputType": "mympd-select-search",
        "readOnly": true,
        "cbCallback": "filterImageSelect",
        "cbCallbackOptions": ["modalSettingsBgImageInput"],
        "title": "Image",
        "form": "modalSettingsBgFrm",
        "sort": 3,
        "validate": {
            "cmd": "validatePlistEl",
            "options": []
        },
        "invalid": "Must be a valid filename"
    },
    "locale": {
        "defaultValue": "default",
        "inputType": "select",
        "title": "Locale",
        "form": "modalSettingsLocaleFrm",
        "onChange": "eventChangeLocale"
    },
    "startupView": {
        "defaultValue": "Home",
        "validValues": {
            "Home": "Home",
            "Playback": "Playback",
            "Queue/Current": "Queue",
            "Queue/LastPlayed": "LastPlayed",
            "Queue/Jukebox": "Jukebox Queue",
            "Browse/Database": "Database",
            "Browse/Playlists": "Playlists",
            "Browse/Filesystem": "Filesystem",
            "Browse/Radio": "Webradios",
            "Search": "Search"
        },
        "inputType": "select",
        "title": "Startup view",
        "form": "modalSettingsStartupFrm",
        "onChange": "eventChangeTheme"
    },
    "musicbrainzLinks": {
        "defaultValue": true,
        "inputType": "checkbox",
        "title": "Show MusicBrainz links",
        "form": "modalSettingsTagsFrm",
        "help": "helpSettingsMusicBrainzLinks"
    },
    "outputLigatures": {
        "defaultValue": {
            "default": "speaker",
            "fifo": "read_more",
            "httpd": "stream",
            "null": "check_box_outline_blank",
            "pipe": "terminal",
            "recorder": "voicemail",
            "shout": "cast",
            "snapcast": "hub"
        },
        "inputType": "none"
    },
    "seekStep": {
        "defaultValue": 10,
        "inputType": "text",
        "contentType": "number",
        "title": "Seek step",
        "unit": "Seconds",
        "form": "modalSettingsSeekFrm",
        "validate": {
            "cmd": "validateUintEl",
            "options": []
        },
        "invalid": "Must be a number"
    },
    "feedback": {
        "defaultValue": "like",
        "inputType": "none"
    },
    "browseDatabaseAlbumListSort": {
        "defaultValue": tagAlbumArtist,
        "validValues": {},
        "inputType": "select",
        "title": "Album list sort",
        "form": "modalSettingsSortFrm",
        "help": "helpSettingsAlbumListSort"
    },
    "showWorkTagAlbumDetail": {
        "defaultValue": false,
        "inputType": "checkbox",
        "title": "Show work in album detail",
        "form": "modalSettingsTagsFrm",
        "help": "helpSettingsShowWorkTagAlbumDetail"
    }
};

const settingsConnectionFields = {
    "mpdHost": {
        "defaultValue": defaults["MYMPD_MPD_HOST"],
        "inputType": "text",
        "title": "MPD host",
        "form": "modalConnectionFrm",
        "help": "helpConnectionMPDHost",
        "class": ["alwaysEnabled"]
    },
    "mpdPort": {
        "defaultValue": defaults["MYMPD_MPD_PORT"],
        "inputType": "text",
        "contentType": "number",
        "title": "MPD port",
        "form": "modalConnectionFrm",
        "help": "helpConnectionMPDPort",
        "class": ["alwaysEnabled"]
    },
    "mpdPass": {
        "defaultValue": defaults["MYMPD_MPD_PASS"],
        "inputType": "password",
        "title": "MPD password",
        "form": "modalConnectionAdvFrm1",
        "help": "helpConnectionMPDPassword",
        "class": ["alwaysEnabled"]
    },
    "mpdTimeout": {
        "defaultValue": defaults["MYMPD_MPD_TIMEOUT_SEC"],
        "inputType": "text",
        "title": "Timeout",
        "form": "modalConnectionAdvFrm2",
        "help": "helpConnectionTimeout",
        "unit": "Seconds",
        "class": ["alwaysEnabled"]
    },
    "mpdKeepalive": {
        "defaultValue": defaults["MYMPD_MPD_KEEPALIVE"],
        "inputType": "checkbox",
        "title": "Keepalive",
        "form": "modalConnectionAdvFrm2",
        "help": "helpConnectionKeepalive",
        "class": ["alwaysEnabled"]
    },
    "mpdBinarylimit": {
        "defaultValue": defaults["MYMPD_MPD_BINARYLIMIT"] / 1024,
        "inputType": "text",
        "title": "Binary limit",
        "form": "modalConnectionAdvFrm2",
        "help": "helpConnectionBinaryLimit",
        "unit": "kB",
        "class": ["alwaysEnabled"]
    },
    "stickerdbMpdHost": {
        "defaultValue": defaults["MYMPD_MPD_HOST"],
        "inputType": "text",
        "title": "MPD host",
        "form": "modalConnectionStickerdbFrm",
        "help": "helpConnectionMPDHost",
        "class": ["alwaysEnabled"]
    },
    "stickerdbMpdPort": {
        "defaultValue": defaults["MYMPD_MPD_PORT"],
        "inputType": "text",
        "contentType": "number",
        "title": "MPD port",
        "form": "modalConnectionStickerdbFrm",
        "help": "helpConnectionMPDPort",
        "class": ["alwaysEnabled"]
    },
    "stickerdbMpdPass": {
        "defaultValue": defaults["MYMPD_MPD_PASS"],
        "inputType": "password",
        "title": "MPD password",
        "form": "modalConnectionStickerdbFrm",
        "help": "helpConnectionMPDPassword",
        "class": ["alwaysEnabled"]
    },
    "stickerdbMpdTimeout": {
        "defaultValue": defaults["MYMPD_MPD_TIMEOUT_SEC"],
        "inputType": "text",
        "title": "Timeout",
        "form": "modalConnectionStickerdbFrm",
        "help": "helpConnectionTimeout",
        "unit": "Seconds",
        "class": ["alwaysEnabled"]
    },
    "stickerdbMpdKeepalive": {
        "defaultValue": defaults["MYMPD_MPD_KEEPALIVE"],
        "inputType": "checkbox",
        "title": "Keepalive",
        "form": "modalConnectionStickerdbFrm",
        "help": "helpConnectionKeepalive",
        "class": ["alwaysEnabled"]
    },
};

const settingsPlaybackFields = {
    "random": {
        "inputType": "checkbox",
        "title": "Random",
        "form": "modalPlaybackPlaybackFrm1",
        "help": "helpQueueRandom"
    },
    "repeat": {
        "inputType": "checkbox",
        "title": "Repeat",
        "form": "modalPlaybackPlaybackFrm1",
        "help": "helpQueueRepeat"
    },
    "autoPlay": {
        "inputType": "checkbox",
        "title": "Autoplay",
        "form": "modalPlaybackPlaybackFrm1",
        "help": "helpQueueAutoPlay"
    },
    "crossfade": {
        "defaultValue": 0,
        "inputType": "text",
        "contentType": "number",
        "title": "Crossfade",
        "form": "modalPlaybackPlaybackFrm2",
        "help": "helpQueueCrossfade",
        "unit": "Seconds"
    },
    "mixrampDb": {
        "defaultValue": 0,
        "inputType": "text",
        "contentType": "number",
        "title": "Mixramp db",
        "form": "modalPlaybackPlaybackFrm2",
        "help": "helpQueueMixrampDb",
        "unit": "DB"
    },
    "mixrampDelay": {
        "defaultValue": -1,
        "inputType": "text",
        "contentType": "number",
        "title": "Mixramp delay",
        "form": "modalPlaybackPlaybackFrm2",
        "help": "helpQueueMixrampDelay",
        "unit": "Seconds"
    },
    "jukeboxPlaylist": {
        "inputType": "mympd-select-search",
        "defaultValue": defaults["MYMPD_JUKEBOX_PLAYLIST"],
        "readOnly": true,
        "cbCallback": "filterPlaylistsSelect",
        "cbCallbackOptions": [0, 'selectJukeboxPlaylist'],
        "title": "Playlist",
        "form": "modalPlaybackJukeboxCollapse",
        "help": "helpJukeboxPlaylist",
        "class": ["jukeboxSongOnly"]
    },
    "jukeboxQueueLength": {
        "inputType": "text",
        "defaultValue": defaults["MYMPD_JUKEBOX_QUEUE_LENGTH"],
        "contentType": "number",
        "title": "Keep queue length",
        "form": "modalPlaybackJukeboxCollapse",
        "help": "helpJukeboxQueueLength",
        "class": ["jukeboxSongOnly"]
    },
    "jukeboxUniqTag": {
        "inputType": "select",
        "defaultValue": defaults["MYMPD_JUKEBOX_UNIQ_TAG"],
        "title": "Enforce uniqueness",
        "form": "modalPlaybackJukeboxCollapse",
        "help": "helpJukeboxUniqueTag"
    },
    "jukeboxLastPlayed": {
        "inputType": "text",
        "contentType": "number",
        "defaultValue": defaults["MYMPD_JUKEBOX_LAST_PLAYED"],
        "title": "Song was played last",
        "form": "modalPlaybackJukeboxCollapse",
        "help": "helpJukeboxLastPlayed",
        "unit": "Hours ago",
        "class": ["featAdvAlbum"]
    },
    "jukeboxIgnoreHated": {
        "inputType": "checkbox",
        "defaultValue": defaults["MYMPD_JUKEBOX_IGNORE_HATED"],
        "title": "Ignore hated songs",
        "form": "modalPlaybackJukeboxCollapse",
        "help": "helpJukeboxIgnoreHated",
        "class": ["jukeboxSongOnly"]
    },
    "jukeboxMinSongDuration": {
        "inputType": "text",
        "contentType": "number",
        "defaultValue": defaults["MYMPD_JUKEBOX_MIN_SONG_DURATION"],
        "title": "Min. song duration",
        "form": "modalPlaybackJukeboxCollapse",
        "help": "helpJukeboxMinSongDuration",
        "class": ["jukeboxSongOnly"],
        "unit": "Seconds"
    },
    "jukeboxMaxSongDuration": {
        "inputType": "text",
        "contentType": "number",
        "defaultValue": defaults["MYMPD_JUKEBOX_MAX_SONG_DURATION"],
        "title": "Max. song duration",
        "form": "modalPlaybackJukeboxCollapse",
        "help": "helpJukeboxMaxSongDuration",
        "class": ["jukeboxSongOnly"],
        "unit": "Seconds"
    },
    "jukeboxFilterInclude": {
        "inputType": "text",
        "defaultValue": "",
        "title": "Include expression",
        "form": "modalPlaybackJukeboxCollapse",
        "help": "helpJukeboxFilterInclude"
    },
    "jukeboxFilterExclude": {
        "inputType": "text",
        "defaultValue": "",
        "title": "Exclude expression",
        "form": "modalPlaybackJukeboxCollapse",
        "help": "helpJukeboxFilterExclude"
    }
};

/**
 * Parses a string to boolean or number
 * @param {string} str string to parse
 * @returns {string | number | boolean} parsed string
 */
function parseString(str) {
    return str === 'true'
        ? true
        : str === 'false'
            ? false
            // @ts-ignore
            : isNaN(str)
                ? str
                : Number(str);
}

//Get settings from localStorage
for (const key in localSettings) {
    const value = localStorage.getItem(key);
    if (value !== null) {
        localSettings[key] = parseString(value);
    }
}

const userAgentData = {};
userAgentData.hasIO = 'IntersectionObserver' in window ? true : false;

/**
 * Sets the useragentData object
 * @returns {void}
 */
function setUserAgentData() {
    //get interesting browser agent data
    //https://developer.mozilla.org/en-US/docs/Web/API/User-Agent_Client_Hints_API
    if (navigator.userAgentData) {
        navigator.userAgentData.getHighEntropyValues(["platform"]).then(ua => {
            /** @type {boolean} */
            userAgentData.isMobile = localSettings.viewMode === 'mobile'
                ? true
                : localSettings.viewMode === 'desktop'
                    ? false
                    : ua.mobile;
            //Safari does not support this API
            /** @type {boolean} */
            userAgentData.isSafari = false;
            /** @type {boolean} */
            userAgentData.isAndroid = /Android/i.test(ua.platform);
        });
    }
    else {
        /** @type {boolean} */
        userAgentData.isMobile = localSettings.viewMode === 'mobile'
            ? true
            : localSettings.viewMode === 'desktop'
                ? false
                : /iPhone|iPad|iPod|Android|Mobile/i.test(navigator.userAgent);
        /** @type {boolean} */
        userAgentData.isSafari = /Safari/i.test(navigator.userAgent) && ! /Chrome/i.test(navigator.userAgent);
        /** @type {boolean} */
        userAgentData.isAndroid = /Android/i.test(navigator.userAgent);
    }
}
setUserAgentData();

//minimum stable mpd version to support all myMPD features
const mpdVersion = {
    "major": 0,
    "minor": 23,
    "patch": 5
};

//remember offset for filesystem browsing uris
const browseFilesystemHistory = {};

//list of stickers for songs
/** @type {Array} */
const stickerListSongs = [
    'playCount',
    'skipCount',
    'lastPlayed',
    'lastSkipped',
    'like',
    'rating',
    'elapsed'
];

//list of stickers for playlists and filters (albums)
/** @type {Array} */
const stickerListAll = [
    'like',
    'rating'
];

//application state
const app = {};
app.cards = {
    "Home": {
        "offset": 0,
        "limit": 100,
        "filter": "",
        "sort": {
            "tag": "",
            "desc": false
        },
        "tag": "",
        "search": "",
        "scrollPos": 0
    },
    "Playback": {
        "offset": 0,
        "limit": 100,
        "filter": "",
        "sort": {
            "tag": "",
            "desc": false
        },
        "tag": "",
        "search": "",
        "scrollPos": 0
    },
    "Queue": {
        "active": "Current",
        "tabs": {
            "Current": {
                "offset": 0,
                "limit": 100,
                "filter": "any",
                "sort": {
                    "tag": "Priority",
                    "desc": false
                },
                "tag": "",
                "search": "",
                "scrollPos": 0
            },
            "LastPlayed": {
                "offset": 0,
                "limit": 100,
                "filter": "any",
                "sort": {
                    "tag": "",
                    "desc": false
                },
                "tag": "",
                "search": "",
                "scrollPos": 0
            },
            "Jukebox": {
                "active": "Song",
                "views": {
                    "Song": {
                        "offset": 0,
                        "limit": 100,
                        "filter": "any",
                        "sort": {
                            "tag": "",
                            "desc": false
                        },
                        "tag": "",
                        "search": "",
                        "scrollPos": 0
                    },
                    "Album": {
                        "offset": 0,
                        "limit": 100,
                        "filter": "any",
                        "sort": {
                            "tag": "",
                            "desc": false
                        },
                        "tag": "",
                        "search": "",
                        "scrollPos": 0
                    }
                }
            }
        }
    },
    "Browse": {
        "active": "Database",
        "tabs": {
            "Filesystem": {
                "offset": 0,
                "limit": 100,
                "filter": "/",
                "sort": {
                    "tag": "",
                    "desc": false
                },
                "tag": "dir",
                "search": "",
                "scrollPos": 0
            },
            "Playlist": {
                "active": "List",
                "views": {
                    "List": {
                        "offset": 0,
                        "limit": 100,
                        "filter": "",
                        "sort": {
                            "tag": "Name",
                            "desc": false
                        },
                        "tag": "",
                        "search": "",
                        "scrollPos": 0
                    },
                    "Detail": {
                        "offset": 0,
                        "limit": 100,
                        "filter": "any",
                        "sort": {
                            "tag": "",
                            "desc": false
                        },
                        "tag": "",
                        "search": "",
                        "scrollPos": 0
                    }
                }
            },
            "Database": {
                "active": "AlbumList",
                "views": {
                    "TagList": {
                        "offset": 0,
                        "limit": 100,
                        "filter": "any",
                        "sort": {
                            "tag": "",
                            "desc": false
                        },
                        "tag": "Album",
                        "search": "",
                        "scrollPos": 0
                    },
                    "AlbumList": {
                        "offset": 0,
                        "limit": 100,
                        "filter": "any",
                        "sort": {
                            "tag": "",
                            "desc": false
                        },
                        "tag": "Album",
                        "search": "",
                        "scrollPos": 0
                    },
                    "AlbumDetail": {
                        "offset": 0,
                        "limit": 100,
                        "filter": "",
                        "sort": {
                            "tag": "",
                            "desc": false
                        },
                        "tag": "",
                        "search": "",
                        "scrollPos": 0
                    }
                }
            },
            "Radio": {
                "active": "Favorites",
                "views": {
                    "Favorites": {
                        "offset": 0,
                        "limit": 100,
                        "filter": "any",
                        "sort": {
                            "tag": "Name",
                            "desc": false
                        },
                        "tag": "",
                        "search": "",
                        "scrollPos": 0
                    },
                    "Webradiodb": {
                        "offset": 0,
                        "limit": 100,
                        "filter": "any",
                        "sort": {
                            "tag": "Name",
                            "desc": false
                        },
                        "tag": "",
                        "search": "",
                        "scrollPos": 0
                    }
                }
            }
        }
    },
    "Search": {
        "offset": 0,
        "limit": 100,
        "filter": "any",
        "sort": {
            "tag": "Title",
            "desc": false
        },
        "tag": "",
        "search": "",
        "scrollPos": 0
    }
};

app.id = 'Home';
app.current = {
    "card": "Home",
    "tab": undefined,
    "view": undefined,
    "offset": 0,
    "limit": 100,
    "filter": "",
    "search": "",
    "sort": {
        "tag": "",
        "desc": false
    },
    "tag": "",
    "scrollPos": 0
};

app.last = {
    "card": undefined,
    "tab": undefined,
    "view": undefined,
    "offset": 0,
    "limit": 100,
    "filter": "",
    "search": "",
    "sort": {
        "tag": "",
        "desc": false
    },
    "tag": "",
    "scrollPos": 0
};
app.goto = false;

//features
const features = {
    "featBinarylimit": true,
    "featCacert": false,
    "featConsumeOneshot": false,
    "featFingerprint": false,
    "featHome": true,
    "featLibrary": false,
    "featLocalPlayback": false,
    "featLyrics": false,
    "featMediaSession": false,
    "featMounts": true,
    "featNeighbors": true,
    "featPartitions": true,
    "featPlaylistDirAuto": false,
    "featPlaylists": true,
    "featScripting": true,
    "featSmartpls": true,
    "featSmartplsAvailable": true,
    "featStickers": false,
    "featTags": true,
    "featTimer": true
};

//keyboard shortcuts
const keymap = {
    "playback": {"order": 0, "desc": "Playback"},
        " ": {"order": 1, "cmd": "clickPlay", "options": [], "desc": "Toggle play / pause", "key": "space_bar"},
        "s": {"order": 2, "cmd": "clickStop", "options": [], "desc": "Stop playing"},
        "ArrowLeft": {"order": 3, "cmd": "clickPrev", "options": [], "desc": "Previous song", "key": "keyboard_arrow_left"},
        "ArrowRight": {"order": 4, "cmd": "clickNext", "options": [], "desc": "Next song", "key": "keyboard_arrow_right"},
        "-": {"order": 5, "cmd": "volumeStep", "options": ["down"], "desc": "Volume down"},
        "+": {"order": 6, "cmd": "volumeStep", "options": ["up"], "desc": "Volume up"},
        "r": {"order": 7, "cmd": "togglePlaymode", "options": ["random"], "desc": "Toggle random"},
        "c": {"order": 8, "cmd": "togglePlaymode", "options": ["consume"], "desc": "Toggle consume"},
        "p": {"order": 9, "cmd": "togglePlaymode", "options": ["repeat"], "desc": "Toggle repeat"},
        "i": {"order": 9, "cmd": "togglePlaymode", "options": ["single"], "desc": "Toggle single mode"},
    "modals": {"order": 200, "desc": "Dialogs"},
        "A": {"order": 207, "cmd": "showAddToPlaylist", "options": ["stream", []], "desc": "Add stream"},
        "C": {"order": 207, "cmd": "openModal", "options": ["modalConnection"], "desc": "Open MPD connection"},
        "D": {"order": 207, "cmd": "clickTitle", "options": [], "desc": "Open current song details"},
        "G": {"order": 207, "cmd": "openModal", "options": ["modalTrigger"], "desc": "Open trigger", "feature": "featScripting"},
        "I": {"order": 207, "cmd": "openModal", "options": ["modalTimer"], "desc": "Open timer", "feature": "featTimer"},
        "L": {"order": 207, "cmd": "loginOrLogout", "options": [], "desc": "Login or logout", "feature": "featSession"},
        "M": {"order": 207, "cmd": "openModal", "options": ["modalMaintenance"], "desc": "Open maintenance"},
        "N": {"order": 207, "cmd": "openModal", "options": ["modalNotifications"], "desc": "Open notifications"},
        "O": {"order": 207, "cmd": "openModal", "options": ["modalMounts"], "desc": "Open mounts", "feature": "featMounts"},
        "P": {"order": 207, "cmd": "openModal", "options": ["modalPartitions"], "desc": "Open partitions", "feature": "featPartitions"},
        "Q": {"order": 207, "cmd": "openModal", "options": ["modalPlayback"], "desc": "Open playback settings"},
        "S": {"order": 207, "cmd": "showListScriptModal", "options": [], "desc": "Open scripts", "feature": "featScripting"},
        "T": {"order": 207, "cmd": "openModal", "options": ["modalSettings"], "desc": "Open settings"},
        "V": {"order": 207, "cmd": "showListVariablesModal", "options": [], "desc": "Open variables", "feature": "featScripting"},
        "?": {"order": 207, "cmd": "openModal", "options": ["modalAbout"], "desc": "Open about"},
        
    "navigation": {"order": 300, "desc": "Navigation"},
        "0": {"order": 301, "cmd": "appGoto", "options": ["Home"], "desc": "Show home", "feature": "featHome"},
        "1": {"order": 302, "cmd": "appGoto", "options": ["Playback"], "desc": "Show playback"},
        "2": {"order": 303, "cmd": "appGoto", "options": ["Queue", "Current"], "desc": "Show queue"},
        "3": {"order": 304, "cmd": "appGoto", "options": ["Queue", "LastPlayed"], "desc": "Show last played"},
        "4": {"order": 305, "cmd": "gotoJukebox", "options": [], "desc": "Show jukebox queue"},
        "5": {"order": 306, "cmd": "appGoto", "options": ["Browse", "Database"], "desc": "Show browse database", "feature": "featTags"},
        "6": {"order": 307, "cmd": "appGoto", "options": ["Browse", "Playlists"], "desc": "Show browse playlists", "feature": "featPlaylists"},
        "7": {"order": 308, "cmd": "appGoto", "options": ["Browse", "Filesystem"], "desc": "Show browse filesystem"},
        "8": {"order": 308, "cmd": "appGoto", "options": ["Browse", "Radio"], "desc": "Show browse webradio"},
        "9": {"order": 309, "cmd": "appGoto", "options": ["Search"], "desc": "Show search"},
        "/": {"order": 310, "cmd": "focusSearch", "options": [], "desc": "Focus search"}
};

//cache often accessed dom elements
const domCache = {};
domCache.body = document.querySelector('body');
domCache.counter = elGetById('counter');
domCache.footer = document.querySelector('footer');
domCache.main = document.querySelector('main');
domCache.progress = elGetById('footerProgress');
domCache.progressBar = elGetById('footerProgressBar');
domCache.progressPos = elGetById('footerProgressPos');
domCache.volumeBar = elGetById('volumeBar');

//Get BSN object references for fast access
const uiElements = {};
//all modals
for (const m of document.querySelectorAll('.modal')) {
    uiElements[m.id] = BSN.Modal.getInstance(m);
    m.addEventListener('shown.bs.modal', function(event) {
        focusFirstInput(event.target);
    }, false);
}
//other directly accessed BSN objects
uiElements.modalHomeIconLigatureDropdown = BSN.Dropdown.getInstance(elGetById('modalHomeIconLigatureBtn'));
uiElements.modalMountsNeighborsDropdown = BSN.Dropdown.getInstance(elGetById('modalMountsNeighborsBtn'));

const LUAfunctions = {
    "json.decode": {
        "desc": "Parses a Json string to a Lua table.",
        "func": "local data = json.decode(str)",
        "feat": ""
    },
    "json.encode": {
        "desc": "Encodes a Lua table as Json string.",
        "func": "local str = json.encode(data)",
        "feat": ""
    },
    "mympd.cache_cover_write": {
        "desc": "Write a file for the cover cache.",
        "func": "local rc, filename = mympd.cache_cover_write(src, uri)",
        "feat": ""
    },
    "mympd.cache_lyrics_write": {
        "desc": "Write the lyrics entry object to the lyrics cache.",
        "func": "local rc, filename = mympd.cache_lyrics_write(json.encode(entry), song_uri)",
        "feat": ""
    },
    "mympd.cache_thumbs_write": {
        "desc": "Write a file for the thumbs cache.",
        "func": "local rc, filename =  = mympd.cache_thumbs_write(src, uri)",
        "feat": ""
    },
    "mympd.dialog": {
        "desc": "Returns an Jsonrpc response for a script dialog.",
        "func": "return mympd.dialog(title, data, callback)",
        "feat": ""
    },
    "mympd.hash_md5": {
        "desc": "MD5 hash of string.",
        "func": "local hash = mympd.hash_md5(string)",
        "feat": ""
    },
    "mympd.hash_sha1": {
        "desc": "SHA1 hash of string.",
        "func": "local hash = mympd.hash_sha1(string)",
        "feat": ""
    },
    "mympd.hash_sha256": {
        "desc": "SHA256 hash of string.",
        "func": "local hash = mympd.hash_sha256(string)",
        "feat": ""
    },
    "mympd.http_client": {
        "desc": "HTTP client",
        "func": "local rc, code, headers, body = mympd.http_client(method, uri, extra_headers, payload)",
        "feat": ""
    },
    "mympd.http_download": {
        "desc": "HTTP download",
        "func": "local rc, code, headers = mympd.http_download(uri, extra_headers, out)",
        "feat": ""
    },
    "mympd.http_jsonrpc_error": {
        "desc": "Sends a JSONRPC 2.0 error.",
        "func": "return mympd.http_jsonrpc_error(method, msg)",
        "feat": ""
    },
    "mympd.http_jsonrpc_response": {
        "desc": "Sends a JSONRPC 2.0 response.",
        "func": "return mympd.http_jsonrpc_response(obj)",
        "feat": ""
    },
    "mympd.http_jsonrpc_warn": {
        "desc": "Sends a JSONRPC 2.0 warning.",
        "func": "return mympd.http_jsonrpc_warn(method, msg)",
        "feat": ""
    },
    "mympd.http_redirect": {
        "desc": "Sends a HTTP redirect.",
        "func": "return mympd.http_reply(location)",
        "feat": ""
    },
    "mympd.http_reply": {
        "desc": "Sends a HTTP reply.",
        "func": "return mympd.http_reply(status, header, body)",
        "feat": ""
    },
    "mympd.http_serve_file": {
        "desc": "Serves a file from the filesystem. Only files from the diskcache are allowed.",
        "func": "return mympd.http_serve_file(file)",
        "feat": ""
    },
    "mympd.init": {
        "desc": "Initializes the global lua table mympd_state.",
        "func": "mympd.init()",
        "feat": ""
    },
    "mympd.log": {
        "desc": "Logs messages to the myMPD log.",
        "func": "mympd.log(loglevel, message)",
        "feat": ""
    },
    "mympd.tmp_file": {
        "desc": "Generates a random tmp filename for the misc cache.",
        "func": "local tmp_file = mympd.tmp_file()",
        "feat": ""
    },
    "mympd.urldecode": {
        "desc": "URL decodes a string.",
        "func": "local decoded = mympd.urlencode(string, false)",
        "feat": ""
    },
    "mympd.update_mtime": {
        "desc":	"Updates the timestamp of a file.",
        "func": "local rc = mympd.update_mtime(filename)",
        "feat": ""
    },
    "mympd.urlencode": {
        "desc":	"URL encodes a string.",
        "func": "local encoded = mympd.urlencode(string)",
        "feat": ""
    },
    "mympd.os_capture": {
        "desc": "Executes a system command and capture its output.",
        "func": "local output = mympd.os_capture(command)",
        "feat": ""
    },
    "mympd.gpio_blink": {
        "desc": "Blinks a GPIO with given timeout and interval.",
        "func": "local rc = mympd.gpio_blink(gpio, timeout_ms, interval_ms)",
        "feat": "featMygpiod"
    },
    "mympd.gpio_get": {
        "desc": "Returns the active state of a GPIO.",
        "func": "local rc = mympd.gpio_get(gpio)",
        "feat": "featMygpiod"
    },
    "mympd.gpio_set": {
        "desc": "Sets the active state of a GPIO.",
        "func": "local rc = mygpio_gpio_set(mympd.mygpiod_socket, gpio, value)",
        "feat": "featMygpiod"
    },
    "mympd.gpio_toggle": {
        "desc": "Toggles the active state of a GPIO.",
        "func": "local rc = mygpio_gpio_toggle(mympd.mygpiod_socket, gpio)",
        "feat": "featMygpiod"
    },
    "mympd.notify_client": {
        "desc": "Sends a notification to the client that started this script.",
        "func": "mympd.notify_client(severity, message)",
        "feat": ""
    },
    "mympd.notify_partition": {
        "desc": "Sends a notification to all clients in the current partition.",
        "func": "mympd.notify_partition(severity, message)",
        "feat": ""
    },
    "mympd.splitlines": {
        "desc": "Split a multiline string in lines.",
        "func": "local lines = mympd.splitlines(str)",
        "feat": ""
    },
    "mympd.trim": {
        "desc": "Trims a string.",
        "func": "local trimed = mympd.trim(str)",
        "feat": ""
    },
    "mympd.sleep": {
        "desc": "Sleeps number of milliseconds.",
        "func": "sleep(ms)",
        "feat": ""
    },
    "mympd.read_file": {
        "desc": "Read an ascii file.",
        "func": "local content = mympd.read_file(path)",
        "feat": ""
    }
};

const typeFriendly = {
    'album': 'Album',
    'dir': 'Directory',
    'externalLink': 'External link',
    'openExternalLink': 'External link',
    'modal': 'Modal',
    'openModal': 'Modal',
    'plist': 'Playlist',
    'script': 'Script',
    'execScriptFromOptions': 'Script',
    'search': 'Search',
    'smartpls': 'Smart playlist',
    'song': 'Song',
    'stream': 'Stream',
    'view': 'View',
    'appGoto': 'View',
    'webradio': 'Webradio',
    'viewSettings': 'View settings',
    'disc': 'Disc',
    'work': 'Work'
};

const friendlyActions = {
    'appendPlayQueueAlbum': 'Append to queue and play',
    'appendPlayQueue': 'Append to queue and play',
    'appendQueueAlbum': 'Append to queue',
    'appendQueue': 'Append to queue',
    'appGoto': 'Goto view',
    'execScriptFromOptions': 'Execute script',
    'homeIconGoto': 'Show',
    'insertAfterCurrentQueueAlbum': 'Insert after current playing song',
    'insertAfterCurrentQueue': 'Insert after current playing song',
    'openExternalLink': 'Open external link',
    'openModal': 'Open modal',
    'replacePlayQueueAlbum': 'Replace queue and play',
    'replacePlayQueue': 'Replace queue and play',
    'replaceQueueAlbum': 'Replace queue',
    'replaceQueue': 'Replace queue'
};

const bgImageValues = [
    {"value": "", "text": "None"},
    {"value": "/assets/mympd-background-dark.svg", "text": "Default image dark"},
    {"value": "/assets/mympd-background-light.svg", "text": "Default image light"}
];