Files
SubMiner/vendor/yomitan/js/pages/settings/recommended-settings-controller.js

195 lines
8.1 KiB
JavaScript

/*
* 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 <https://www.gnu.org/licenses/>.
*/
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<string, import('settings-controller').RecommendedSetting>} */
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<import('settings-controller').RecommendedSetting[]>}
*/
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<HTMLInputElement>} */
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}`));
}
}
}
}