/* * Copyright (C) 2024-2025 Yomitan Authors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ import {fetchJson} from '../../core/fetch-utilities.js'; import {log} from '../../core/log.js'; import {querySelectorNotNull} from '../../dom/query-selector.js'; export class RecommendedSettingsController { /** * @param {import('./settings-controller.js').SettingsController} settingsController */ constructor(settingsController) { /** @type {import('./settings-controller.js').SettingsController} */ this._settingsController = settingsController; /** @type {HTMLElement} */ this._recommendedSettingsModal = querySelectorNotNull(document, '#recommended-settings-modal'); /** @type {HTMLInputElement} */ this._languageSelect = querySelectorNotNull(document, '#language-select'); /** @type {HTMLInputElement} */ this._applyButton = querySelectorNotNull(document, '#recommended-settings-apply-button'); /** @type {Map} */ this._recommendedSettings = new Map(); } /** */ async prepare() { this._languageSelect.addEventListener('change', this._onLanguageSelectChanged.bind(this), false); this._applyButton.addEventListener('click', this._onApplyButtonClicked.bind(this), false); } /** * @param {Event} _e */ async _onLanguageSelectChanged(_e) { const setLanguage = this._languageSelect.value; if (typeof setLanguage !== 'string') { return; } const recommendedSettings = await this._getRecommendedSettings(setLanguage); if (typeof recommendedSettings !== 'undefined') { const settingsList = querySelectorNotNull(document, '#recommended-settings-list'); settingsList.innerHTML = ''; this._recommendedSettings = new Map(); for (const [index, setting] of recommendedSettings.entries()) { this._recommendedSettings.set(index.toString(), setting); const {description} = setting; const template = this._settingsController.instantiateTemplate('recommended-settings-list-item'); // Render label this._renderLabel(template, setting); // Render description const descriptionElement = querySelectorNotNull(template, '.settings-item-description'); if (description !== 'undefined') { descriptionElement.textContent = description; } // Render checkbox const checkbox = /** @type {HTMLInputElement} */ (querySelectorNotNull(template, 'input[type="checkbox"]')); checkbox.value = index.toString(); settingsList.append(template); } this._recommendedSettingsModal.hidden = false; } } /** * * @param {string} language * @returns {Promise} */ async _getRecommendedSettings(language) { if (typeof this._recommendedSettingsByLanguage === 'undefined') { /** @type {import('settings-controller').RecommendedSettingsByLanguage} */ this._recommendedSettingsByLanguage = await fetchJson('/data/recommended-settings.json'); } return this._recommendedSettingsByLanguage[language]; } /** * @param {MouseEvent} e */ _onApplyButtonClicked(e) { e.preventDefault(); /** @type {NodeListOf} */ const enabledCheckboxes = querySelectorNotNull(document, '#recommended-settings-list').querySelectorAll('input[type="checkbox"]:checked'); if (enabledCheckboxes.length > 0) { const modifications = []; for (const checkbox of enabledCheckboxes) { const index = checkbox.value; const setting = this._recommendedSettings.get(index); if (typeof setting === 'undefined') { continue; } modifications.push(setting.modification); } void this._settingsController.modifyProfileSettings(modifications).then( (results) => { for (const result of results) { if (Object.hasOwn(result, 'error')) { log.error(new Error(`Failed to apply recommended setting: ${JSON.stringify(result)}`)); } } }, ); void this._settingsController.refresh(); } this._recommendedSettingsModal.hidden = true; } /** * @param {Element} template * @param {import('settings-controller').RecommendedSetting} setting */ _renderLabel(template, setting) { const label = querySelectorNotNull(template, '.settings-item-label'); const {modification} = setting; switch (modification.action) { case 'set': { const {path, value} = modification; const pathCodeElement = document.createElement('code'); pathCodeElement.textContent = path; const valueCodeElement = document.createElement('code'); valueCodeElement.textContent = JSON.stringify(value, null, 2); label.appendChild(document.createTextNode('Setting ')); label.appendChild(pathCodeElement); label.appendChild(document.createTextNode(' = ')); label.appendChild(valueCodeElement); break; } case 'delete': { const {path} = modification; const pathCodeElement = document.createElement('code'); pathCodeElement.textContent = path; label.appendChild(document.createTextNode('Deleting ')); label.appendChild(pathCodeElement); break; } case 'swap': { const {path1, path2} = modification; const path1CodeElement = document.createElement('code'); path1CodeElement.textContent = path1; const path2CodeElement = document.createElement('code'); path2CodeElement.textContent = path2; label.appendChild(document.createTextNode('Swapping ')); label.appendChild(path1CodeElement); label.appendChild(document.createTextNode(' and ')); label.appendChild(path2CodeElement); break; } case 'splice': { const {path, start, deleteCount, items} = modification; const pathCodeElement = document.createElement('code'); pathCodeElement.textContent = path; label.appendChild(document.createTextNode('Splicing ')); label.appendChild(pathCodeElement); label.appendChild(document.createTextNode(` at ${start} deleting ${deleteCount} items and inserting ${items.length} items`)); break; } case 'push': { const {path, items} = modification; const pathCodeElement = document.createElement('code'); pathCodeElement.textContent = path; label.appendChild(document.createTextNode(`Pushing ${items.length} items to `)); label.appendChild(pathCodeElement); break; } default: { log.error(new Error(`Unknown modification: ${modification}`)); } } } }