"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 modalSettings_js */
/**
* Initialization function for the settings elements
* @returns {void}
*/
function initModalSettings() {
// cache for the form field containers
const forms = {};
// create the fields
createForm(settingsFields, 'modalSettings', forms);
createForm(settingsWebuiFields, 'modalSettings', forms);
createForm(settingsPartitionFields, 'modalSettings', forms);
createForm(settingsLocalFields, 'modalSettings', forms);
// initialize myMPD custom elements
initElements(elGetById('modalSettings'));
//set featWhence feature detection for default actions
for (const sel of ['modalSettingsClickQuickPlayInput', 'modalSettingsClickFilesystemPlaylistInput',
'modalSettingsClickPlaylistInput', 'modalSettingsClickSongInput',
'modalSettingsClickRadioFavoritesInput'])
{
const options = document.querySelectorAll('#' + sel + ' > option');
for (const opt of options) {
if (opt.value === 'insert' ||
opt.value === 'play')
{
opt.classList.add('featWhence');
}
}
}
elGetById('modalSettings').addEventListener('show.bs.modal', function () {
getSettings(function(obj) {
if (parseSettings(obj) === true) {
cleanupModalId('modalSettings');
populateSettingsFrm();
uiElements.modalSettings.show();
}
});
});
}
/**
* Change eventhandler for the locale select
* @param {Event} event change event
* @returns {void}
*/
//eslint-disable-next-line no-unused-vars
function eventChangeLocale(event) {
const value = getSelectValue(event.target);
warnLocale(value);
}
/**
* Change eventhandler for the theme select
* @param {Event} event change event
* @returns {void}
*/
//eslint-disable-next-line no-unused-vars
function eventChangeTheme(event) {
const value = getSelectValue(event.target);
const bgImageEl = elGetById('modalSettingsBgImageInput');
const bgImageValue = getData(bgImageEl, 'value');
if (value === 'light') {
elGetById('modalSettingsBgColorInput').value = '#ffffff';
if (bgImageValue.indexOf('/assets/') === 0) {
bgImageEl.value = getBgImageText('/assets/mympd-background-light.svg');
setData(bgImageEl, 'value', '/assets/mympd-background-light.svg');
}
}
else {
//dark is the default
elGetById('modalSettingsBgColorInput').value = '#060708';
if (bgImageValue.indexOf('/assets/') === 0) {
bgImageEl.value = getBgImageText('/assets/mympd-background-dark.svg');
setData(bgImageEl, 'value', '/assets/mympd-background-dark.svg');
}
}
toggleThemeInputs(value);
}
/**
* Shows or hides the background color and image inputs
* @param {string} theme theme name
* @returns {void}
*/
function toggleThemeInputs(theme) {
if (theme === 'auto') {
elGetById('modalSettingsBgColorInput').parentNode.parentNode.classList.add('d-none');
elGetById('modalSettingsBgImageInput').parentNode.parentNode.classList.add('d-none');
}
else {
elGetById('modalSettingsBgColorInput').parentNode.parentNode.classList.remove('d-none');
elGetById('modalSettingsBgImageInput').parentNode.parentNode.classList.remove('d-none');
}
}
/**
* Sets the text for the bgImage input
* @param {string} value bgImage value
* @returns {string} bgImage text
*/
function getBgImageText(value) {
if (value === '') {
return tn('None');
}
for (const key of bgImageValues) {
if (key.value === value) {
return key.text;
}
}
return value;
}
/**
* Gets the background images list and populates the select element
* @returns {void}
*/
function getBgImageList() {
const list = elGetById('modalSettingsBgImageInput');
getImageList(list, bgImageValues, 'backgrounds');
}
/**
* Populates the settings modal
* @returns {void}
*/
function populateSettingsFrm() {
addTagListSelect('modalSettingsBrowseDatabaseAlbumListSortInput', 'tagListAlbum');
jsonToForm(settings, settingsFields, 'modalSettings');
jsonToForm(settings.webuiSettings, settingsWebuiFields, 'modalSettings');
jsonToForm(settings.partition, settingsPartitionFields, 'modalSettings');
jsonToForm(localSettings, settingsLocalFields, 'modalSettings');
// feedback
toggleBtnGroupValueId('modalSettingsFeedbackGroup', settings.webuiSettings.feedback);
// background image select
getBgImageList();
const bgImageInput = elGetById('modalSettingsBgImageInput');
setData(bgImageInput, 'value', settings.webuiSettings.bgImage);
bgImageInput.value = getBgImageText(settings.webuiSettings.bgImage);
// home
elGetById('modalSettingsStartupViewInput').options[0].classList.add('featHome');
// theme
toggleThemeInputs(settings.webuiSettings.theme);
//locales
const localeList = elGetById('modalSettingsLocaleInput');
elClear(localeList);
for (const l in i18n) {
localeList.appendChild(
elCreateTextTn('option', {"value": l}, i18n[l].desc)
);
if (l === settings.webuiSettings.locale) {
localeList.lastChild.setAttribute('selected', 'selected');
}
}
warnLocale(settings.webuiSettings.locale);
// web notifications - check permission
const btnNotifyWeb = elGetById('modalSettingsNotifyWebInput');
elHideId('modalSettingsNotifyWebWarn');
if (notificationsSupported()) {
if (Notification.permission !== 'granted') {
if (settings.webuiSettings.notifyWeb === true) {
elShowId('modalSettingsNotifyWebWarn');
}
settings.webuiSettings.notifyWeb = false;
}
if (Notification.permission === 'denied') {
elShowId('modalSettingsNotifyWebWarn');
}
toggleBtnChk(btnNotifyWeb, settings.webuiSettings.notifyWeb);
elEnable(btnNotifyWeb);
}
else {
elDisable(btnNotifyWeb);
toggleBtnChk(btnNotifyWeb, false);
}
// media session support
const btnMediaSession = elGetById('modalSettingsMediaSessionInput');
if (features.featMediaSession === false) {
elShowId('modalSettingsMediaSessionInputWarn');
elDisable(btnMediaSession);
toggleBtnChk(btnMediaSession, false);
}
else {
elHideId('modalSettingsMediaSessionInputWarn');
elEnable(btnMediaSession);
}
// smart playlists
if (settings.features.featPlaylists === true) {
elEnableId('modalSettingsSmartplsInput');
toggleBtnChkCollapseId('modalSettingsSmartplsInput', 'modalSettingsSmartplsCollapse', settings.smartpls);
elHideId('modalSettingsSmartplsWarn');
}
else {
elDisableId('modalSettingsSmartplsInput');
toggleBtnChkCollapseId('modalSettingsSmartplsInput', 'modalSettingsSmartplsCollapse', false);
elShowId('modalSettingsSmartplsWarn');
}
addTagListSelect('modalSettingsSmartplsSortInput', 'tagList');
elGetById('modalSettingsSmartplsSortInput').value = settings.smartplsSort;
// seconds to hours
elGetById('modalSettingsSmartplsIntervalInput').value = settings.smartplsInterval / 60 / 60;
// lyrics
if (features.featLibrary === false) {
//lyrics need access to library
settings.webuiSettings.enableLyrics = false;
}
toggleBtnChkCollapseId('modalSettingsEnableLyricsInput', 'modalSettingsLyricsCollapse', settings.webuiSettings.enableLyrics);
// local playback
toggleBtnChkCollapseId('modalSettingsEnableLocalPlaybackInput', 'modalSettingsLocalPlaybackCollapse', settings.webuiSettings.enableLocalPlayback);
// tag multiselects
initTagMultiSelect('modalSettingsEnabledTagsInput', 'modalSettingsEnabledTagsList', settings.tagListMpd, settings.tagList);
initTagMultiSelect('modalSettingsSearchTagsInput', 'modalSettingsSearchTagsList', settings.tagList, settings.tagListSearch);
initTagMultiSelect('modalSettingsBrowseTagsInput', 'modalSettingsBrowseTagsList', settings.tagList, settings.tagListBrowse);
initTagMultiSelect('modalSettingsGeneratePlsTagsInput', 'modalSettingsGeneratePlsTagsList', settings.tagListBrowse, settings.smartplsGenerateTagList);
// handle features: show or hide warnings - use the settings object
setFeatureBtnId('modalSettingsEnableLyricsInput', settings.features.featLibrary);
setFeatureBtnId('modalSettingsEnableScriptingInput', settings.features.featScripting);
setFeatureBtnId('modalSettingsEnableMountsInput', settings.features.featMounts);
setFeatureBtnId('modalSettingsEnablePartitionsInput', settings.features.featPartitions);
}
/**
* Shows the warning for a disabled feature button (feature is not supported by the backend)
* @param {string} id button id
* @param {boolean} value true = enable button and hide warning
* false = disable button and show warning
* @returns {void}
*/
function setFeatureBtnId(id, value) {
if (value === true) {
elEnableId(id);
elHideId(id + 'Warn');
}
else {
elDisableId(id);
toggleBtnChkId(id, false);
elShowId(id + 'Warn');
}
}
/**
* Save localSettings in browsers localStorage
* @returns {boolean} true on success, else false
*/
function saveLocalSettings() {
try {
for (const key in localSettings) {
localStorage.setItem(key, localSettings[key]);
}
}
catch(err) {
logError('Can not save settings to localStorage: ' + err.message);
return false;
}
return true;
}
/**
* Saves the settings
* @param {Element} target triggering element
* @param {boolean} closeModal true = close modal, else not
* @returns {void}
*/
//eslint-disable-next-line no-unused-vars
function saveSettings(target, closeModal) {
cleanupModalId('modalSettings');
const settingsParams = {};
settingsParams.webuiSettings = {};
if (formToJson('modalSettings', settingsParams, settingsFields) === true &&
formToJson('modalSettings', settingsParams.webuiSettings, settingsWebuiFields) === true &&
formToJson('modalSettings', localSettings, settingsLocalFields) === true)
{
if (saveLocalSettings() === false) {
modalClose({"error": {"message": "Can not save browser specific settings to localStorage"}});
return;
}
// from hours to seconds
settingsParams.smartplsInterval = settingsParams.smartplsInterval * 60 * 60;
// manual fields
settingsParams.smartplsGenerateTagList = getTagMultiSelectValues(elGetById('modalSettingsGeneratePlsTagsList'), false);
settingsParams.tagList = getTagMultiSelectValues(elGetById('modalSettingsEnabledTagsList'), false);
settingsParams.tagListSearch = getTagMultiSelectValues(elGetById('modalSettingsSearchTagsList'), false);
settingsParams.tagListBrowse = getTagMultiSelectValues(elGetById('modalSettingsBrowseTagsList'), false);
settingsParams.webuiSettings.feedback = getBtnGroupValueId('modalSettingsFeedbackGroup');
btnWaiting(target, true);
if (closeModal === true) {
sendAPIpartition('default', 'MYMPD_API_SETTINGS_SET', settingsParams, saveSettingsClose, true);
}
else {
sendAPIpartition('default', 'MYMPD_API_SETTINGS_SET', settingsParams, saveSettingsApply, true);
}
}
}
/**
* Response handler for MYMPD_API_SETTINGS_SET that closes the modal
* @param {object} obj jsonrpc response
* @returns {void}
*/
function saveSettingsClose(obj) {
// modal is closed in the next close handler
if (modalApply(obj) === true) {
savePartitionSettings(true);
}
}
/**
* Response handler for MYMPD_API_SETTINGS_SET
* @param {object} obj jsonrpc response
* @returns {void}
*/
function saveSettingsApply(obj) {
if (modalApply(obj) === true) {
savePartitionSettings(false);
}
}
/**
* Saves the partition specific settings
* @param {boolean} closeModal true = close modal, else not
* @returns {void}
*/
function savePartitionSettings(closeModal) {
const settingsParams = {};
if (formToJson('modalSettings', settingsParams, settingsPartitionFields) === true) {
if (settingsParams.streamUri !== '' &&
settingsParams.mpdStreamPort === 0)
{
// use default stream port if stream uri is defined
console.log('reseting stream port');
settingsParams.mpdStreamPort = defaults["PARTITION_MPD_STREAM_PORT"];
}
if (closeModal === true) {
sendAPI('MYMPD_API_PARTITION_SAVE', settingsParams, savePartitionSettingsClose, true);
}
else {
sendAPI('MYMPD_API_PARTITION_SAVE', settingsParams, savePartitionSettingsApply, true);
}
}
}
/**
* Response handler for MYMPD_API_PARTITION_SAVE that closes the modal
* @param {object} obj jsonrpc response
* @returns {void}
*/
function savePartitionSettingsApply(obj) {
if (modalApply(obj) === true) {
getSettings(parseSettings);
}
}
/**
* Response handler for MYMPD_API_PARTITION_SAVE
* @param {object} obj jsonrpc response
* @returns {void}
*/
function savePartitionSettingsClose(obj) {
if (modalClose(obj) === true) {
getSettings(parseSettings);
}
}
/**
* Gets all selected tags from a tag multiselect
* @param {HTMLElement} taglist container of the checkboxes
* @param {boolean} translate true = translate the name, else not
* @returns {string} comma separated list of selected tags
*/
function getTagMultiSelectValues(taglist, translate) {
const values = [];
const chkBoxes = taglist.querySelectorAll('button');
for (let i = 0, j = chkBoxes.length; i < j; i++) {
if (chkBoxes[i].classList.contains('active')) {
if (translate === true) {
values.push(tn(chkBoxes[i].name));
}
else {
values.push(chkBoxes[i].name);
}
}
}
if (translate === true) {
return values.join(', ');
}
return values.join(',');
}
/**
* Initializes a tag multiselect
* @param {string} inputId input element id to initialize
* @param {string} listId list container element id to initialize
* @param {object} allTags all tags to list
* @param {object} enabledTags already enabled tags
* @returns {void}
*/
function initTagMultiSelect(inputId, listId, allTags, enabledTags) {
const values = [];
const list = elGetById(listId);
elClear(list);
for (let i = 0, j = allTags.length; i < j; i++) {
if (enabledTags.includes(allTags[i])) {
values.push(tn(allTags[i]));
}
const btn = elCreateEmpty('button', {"class": ["btn", "btn-secondary", "btn-xs", "mi", "mi-sm", "me-2"], "name": allTags[i]});
if (enabledTags.includes(allTags[i])) {
btn.classList.add('active');
btn.textContent = 'check';
}
else {
btn.textContent = 'radio_button_unchecked';
}
list.appendChild(
elCreateNodes('div', {"class": ["form-check"]}, [
btn,
elCreateTextTn('label', {"class": ["form-check-label"], "for": allTags[i]}, allTags[i])
])
);
}
const inputEl = elGetById(inputId);
inputEl.value = values.join(', ');
if (getData(inputEl, 'init') === true) {
return;
}
setData(inputEl, 'init', true);
elGetById(listId).addEventListener('click', function(event) {
event.stopPropagation();
event.preventDefault();
if (event.target.nodeName === 'BUTTON') {
toggleBtnChk(event.target, undefined);
event.target.parentNode.parentNode.parentNode.previousElementSibling.value =
getTagMultiSelectValues(event.target.parentNode.parentNode, true);
}
});
}
/**
* Event handler for the enable web notification button that requests the permission from the user
* @param {Event} event change event
* @returns {void}
*/
//eslint-disable-next-line no-unused-vars
function toggleBtnNotifyWeb(event) {
const btnNotifyWeb = event.target;
const notifyWebState = getBtnChkValue(btnNotifyWeb);
if (notificationsSupported() === false) {
toggleBtnChk(btnNotifyWeb, false);
settings.webuiSettings.notifyWeb = false;
return;
}
if (notifyWebState === true) {
toggleBtnChk(btnNotifyWeb, false);
settings.webuiSettings.notifyWeb = false;
elHideId('modalSettingsNotifyWebWarn');
return;
}
Notification.requestPermission(function (permission) {
if (permission === 'granted') {
toggleBtnChk(btnNotifyWeb, true);
settings.webuiSettings.notifyWeb = true;
elHideId('modalSettingsNotifyWebWarn');
}
else {
toggleBtnChk(btnNotifyWeb, false);
settings.webuiSettings.notifyWeb = false;
elShowId('modalSettingsNotifyWebWarn');
}
});
}
/**
* Shows the missing translations warning
* @param {string} value locale name
* @returns {void}
*/
function warnLocale(value) {
const warnEl = elGetById('modalSettingsMissingPhrasesWarn');
elClear(warnEl);
if (i18n[value].missingPhrases > 0) {
warnEl.appendChild(
elCreateTextTnNr('p', {}, 'Missing translations', i18n[value].missingPhrases)
);
warnEl.appendChild(
elCreateTextTn('a', {"class": ["alert-link", "external"], "target": "_blank",
"href": "https://github.com/jcorporation/myMPD/discussions/167"}, 'Help to improve myMPD')
);
elShow(warnEl);
}
else {
elHide(warnEl);
}
}