Files
youtube-music/config/dynamic.js
2023-08-29 17:22:38 +09:00

215 lines
5.8 KiB
JavaScript

const { ipcRenderer, ipcMain } = require('electron');
const defaultConfig = require('./defaults');
const { getOptions, setOptions, setMenuOptions } = require('./plugins');
const { sendToFront } = require('../providers/app-controls');
const activePlugins = {};
/**
* [!IMPORTANT!]
* The method is **sync** in the main process and **async** in the renderer process.
*/
module.exports.getActivePlugins
= process.type === 'renderer'
? async () => ipcRenderer.invoke('get-active-plugins')
: () => activePlugins;
if (process.type === 'browser') {
ipcMain.handle('get-active-plugins', this.getActivePlugins);
}
/**
* [!IMPORTANT!]
* The method is **sync** in the main process and **async** in the renderer process.
*/
module.exports.isActive
= process.type === 'renderer'
? async (plugin) =>
plugin in (await ipcRenderer.invoke('get-active-plugins'))
: (plugin) => plugin in activePlugins;
/**
* This class is used to create a dynamic synced config for plugins.
*
* [!IMPORTANT!]
* The methods are **sync** in the main process and **async** in the renderer process.
*
* @param {string} name - The name of the plugin.
* @param {boolean} [options.enableFront] - Whether the config should be available in front.js. Default: false.
* @param {object} [options.initialOptions] - The initial options for the plugin. Default: loaded from store.
*
* @example
* const { PluginConfig } = require("../../config/dynamic");
* const config = new PluginConfig("plugin-name", { enableFront: true });
* module.exports = { ...config };
*
* // or
*
* module.exports = (win, options) => {
* const config = new PluginConfig("plugin-name", {
* enableFront: true,
* initialOptions: options,
* });
* setupMyPlugin(win, config);
* };
*/
module.exports.PluginConfig = class PluginConfig {
#name;
#config;
#defaultConfig;
#enableFront;
#subscribers = {};
#allSubscribers = [];
constructor(name, { enableFront = false, initialOptions = undefined } = {}) {
const pluginDefaultConfig = defaultConfig.plugins[name] || {};
const pluginConfig = initialOptions || getOptions(name) || {};
this.#name = name;
this.#enableFront = enableFront;
this.#defaultConfig = pluginDefaultConfig;
this.#config = { ...pluginDefaultConfig, ...pluginConfig };
if (this.#enableFront) {
this.#setupFront();
}
activePlugins[name] = this;
}
get = (option) => this.#config[option];
set = (option, value) => {
this.#config[option] = value;
this.#onChange(option);
this.#save();
};
toggle = (option) => {
this.#config[option] = !this.#config[option];
this.#onChange(option);
this.#save();
};
getAll = () => ({ ...this.#config });
setAll = (options) => {
if (!options || typeof options !== 'object') {
throw new Error('Options must be an object.');
}
let changed = false;
for (const [key, value] of Object.entries(options)) {
if (this.#config[key] !== value) {
this.#config[key] = value;
this.#onChange(key, false);
changed = true;
}
}
if (changed) {
for (const fn of this.#allSubscribers) {
fn(this.#config);
}
}
this.#save();
};
getDefaultConfig = () => this.#defaultConfig;
/**
* Use this method to set an option and restart the app if `appConfig.restartOnConfigChange === true`
*
* Used for options that require a restart to take effect.
*/
setAndMaybeRestart = (option, value) => {
this.#config[option] = value;
setMenuOptions(this.#name, this.#config);
this.#onChange(option);
};
subscribe = (valueName, fn) => {
this.#subscribers[valueName] = fn;
};
subscribeAll = (fn) => {
this.#allSubscribers.push(fn);
};
/** Called only from back */
#save() {
setOptions(this.#name, this.#config);
}
#onChange(valueName, single = true) {
this.#subscribers[valueName]?.(this.#config[valueName]);
if (single) {
for (const fn of this.#allSubscribers) {
fn(this.#config);
}
}
}
#setupFront() {
const ignoredMethods = ['subscribe', 'subscribeAll'];
if (process.type === 'renderer') {
for (const [fnName, fn] of Object.entries(this)) {
if (typeof fn !== 'function' || fn.name in ignoredMethods) {
return;
}
this[fnName] = async (...args) => await ipcRenderer.invoke(
`${this.#name}-config-${fnName}`,
...args,
);
this.subscribe = (valueName, fn) => {
if (valueName in this.#subscribers) {
console.error(`Already subscribed to ${valueName}`);
}
this.#subscribers[valueName] = fn;
ipcRenderer.on(
`${this.#name}-config-changed-${valueName}`,
(_, value) => {
fn(value);
},
);
ipcRenderer.send(`${this.#name}-config-subscribe`, valueName);
};
this.subscribeAll = (fn) => {
ipcRenderer.on(`${this.#name}-config-changed`, (_, value) => {
fn(value);
});
ipcRenderer.send(`${this.#name}-config-subscribe-all`);
};
}
} else if (process.type === 'browser') {
for (const [fnName, fn] of Object.entries(this)) {
if (typeof fn !== 'function' || fn.name in ignoredMethods) {
return;
}
ipcMain.handle(`${this.#name}-config-${fnName}`, (_, ...args) => fn(...args));
}
ipcMain.on(`${this.#name}-config-subscribe`, (_, valueName) => {
this.subscribe(valueName, (value) => {
sendToFront(`${this.#name}-config-changed-${valueName}`, value);
});
});
ipcMain.on(`${this.#name}-config-subscribe-all`, () => {
this.subscribeAll((value) => {
sendToFront(`${this.#name}-config-changed`, value);
});
});
}
}
};