diff --git a/config/dynamic.js b/config/dynamic.js index 34cb0ac3..6f78187a 100644 --- a/config/dynamic.js +++ b/config/dynamic.js @@ -1,7 +1,8 @@ -const { ipcRenderer, ipcMain } = require("electron"); +const { ipcRenderer, ipcMain } = require('electron'); -const defaultConfig = require("./defaults"); -const { getOptions, setOptions, setMenuOptions } = require("./plugins"); +const defaultConfig = require('./defaults'); +const { getOptions, setOptions, setMenuOptions } = require('./plugins'); +const { sendToFront } = require('../providers/app-controls'); const activePlugins = {}; /** @@ -9,12 +10,12 @@ const activePlugins = {}; * 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") + process.type === 'renderer' + ? async () => ipcRenderer.invoke('get-active-plugins') : () => activePlugins; -if (process.type === "browser") { - ipcMain.handle("get-active-plugins", this.getActivePlugins); +if (process.type === 'browser') { + ipcMain.handle('get-active-plugins', this.getActivePlugins); } /** @@ -22,9 +23,9 @@ if (process.type === "browser") { * The method is **sync** in the main process and **async** in the renderer process. */ module.exports.isActive = - process.type === "renderer" + process.type === 'renderer' ? async (plugin) => - plugin in (await ipcRenderer.invoke("get-active-plugins")) + plugin in (await ipcRenderer.invoke('get-active-plugins')) : (plugin) => plugin in activePlugins; /** @@ -58,6 +59,9 @@ module.exports.PluginConfig = class PluginConfig { #defaultConfig; #enableFront; + #subscribers = {}; + #allSubscribers = []; + constructor(name, { enableFront = false, initialOptions = undefined } = {}) { const pluginDefaultConfig = defaultConfig.plugins[name] || {}; const pluginConfig = initialOptions || getOptions(name) || {}; @@ -80,11 +84,13 @@ module.exports.PluginConfig = class PluginConfig { 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(); }; @@ -93,7 +99,18 @@ module.exports.PluginConfig = class PluginConfig { }; setAll = (options) => { - this.#config = { ...this.#config, ...options }; + if (!options || typeof options !== 'object') + throw new Error('Options must be an object.'); + + let changed = false; + for (const [key, val] of Object.entries(options)) { + if (this.#config[key] !== val) { + this.#config[key] = val; + this.#onChange(key, false); + changed = true; + } + } + if (changed) this.#allSubscribers.forEach((fn) => fn(this.#config)); this.#save(); }; @@ -109,6 +126,15 @@ module.exports.PluginConfig = class PluginConfig { 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 */ @@ -116,24 +142,64 @@ module.exports.PluginConfig = class PluginConfig { setOptions(this.#name, this.#config); } + #onChange(valueName, single = true) { + this.#subscribers[valueName]?.(this.#config[valueName]); + if (single) this.#allSubscribers.forEach((fn) => fn(this.#config)); + } + #setupFront() { - if (process.type === "renderer") { + const ignoredMethods = ['subscribe', 'subscribeAll']; + + if (process.type === 'renderer') { for (const [fnName, fn] of Object.entries(this)) { - if (typeof fn !== "function") return; + if (typeof fn !== 'function' || fn.name in ignoredMethods) return; this[fnName] = async (...args) => { return 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") { + } else if (process.type === 'browser') { for (const [fnName, fn] of Object.entries(this)) { - if (typeof fn !== "function") return; + if (typeof fn !== 'function' || fn.name in ignoredMethods) return; ipcMain.handle(`${this.name}-config-${fnName}`, (_, ...args) => { return 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); + }); + }); } } };