Settings: Implement plugin options defaults (#117)
This commit is contained in:
parent
287173458f
commit
b66903cf52
|
@ -2,6 +2,7 @@ import plugins from "plugins";
|
||||||
import IpcEvents from "../utils/IpcEvents";
|
import IpcEvents from "../utils/IpcEvents";
|
||||||
import { React } from "../webpack/common";
|
import { React } from "../webpack/common";
|
||||||
import { mergeDefaults } from "../utils/misc";
|
import { mergeDefaults } from "../utils/misc";
|
||||||
|
import { OptionType } from "../utils/types";
|
||||||
|
|
||||||
export interface Settings {
|
export interface Settings {
|
||||||
notifyAboutUpdates: boolean;
|
notifyAboutUpdates: boolean;
|
||||||
|
@ -30,9 +31,6 @@ for (const plugin in plugins) {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
var settings = JSON.parse(VencordNative.ipc.sendSync(IpcEvents.GET_SETTINGS)) as Settings;
|
var settings = JSON.parse(VencordNative.ipc.sendSync(IpcEvents.GET_SETTINGS)) as Settings;
|
||||||
for (const key in DefaultSettings) {
|
|
||||||
settings[key] ??= DefaultSettings[key];
|
|
||||||
}
|
|
||||||
mergeDefaults(settings, DefaultSettings);
|
mergeDefaults(settings, DefaultSettings);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("Corrupt settings file. ", err);
|
console.error("Corrupt settings file. ", err);
|
||||||
|
@ -42,24 +40,52 @@ try {
|
||||||
type SubscriptionCallback = ((newValue: any, path: string) => void) & { _path?: string; };
|
type SubscriptionCallback = ((newValue: any, path: string) => void) & { _path?: string; };
|
||||||
const subscriptions = new Set<SubscriptionCallback>();
|
const subscriptions = new Set<SubscriptionCallback>();
|
||||||
|
|
||||||
|
// Wraps the passed settings object in a Proxy to nicely handle change listeners and default values
|
||||||
function makeProxy(settings: Settings, root = settings, path = ""): Settings {
|
function makeProxy(settings: Settings, root = settings, path = ""): Settings {
|
||||||
return new Proxy(settings, {
|
return new Proxy(settings, {
|
||||||
get(target, p: string) {
|
get(target, p: string) {
|
||||||
const v = target[p];
|
const v = target[p];
|
||||||
|
|
||||||
|
// using "in" is important in the following cases to properly handle falsy or nullish values
|
||||||
|
if (!(p in target)) {
|
||||||
|
// Since the property is not set, check if this is a plugin's setting and if so, try to resolve
|
||||||
|
// the default value.
|
||||||
|
if (path.startsWith("plugins.")) {
|
||||||
|
const plugin = path.slice("plugins.".length);
|
||||||
|
if (plugin in plugins) {
|
||||||
|
const setting = plugins[plugin].options?.[p];
|
||||||
|
if (!setting) return v;
|
||||||
|
if ("default" in setting)
|
||||||
|
// normal setting with a default value
|
||||||
|
return setting.default;
|
||||||
|
if (setting.type === OptionType.SELECT)
|
||||||
|
return setting.options.find(o => o.default)?.value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Recursively proxy Objects with the updated property path
|
||||||
if (typeof v === "object" && !Array.isArray(v) && v !== null)
|
if (typeof v === "object" && !Array.isArray(v) && v !== null)
|
||||||
return makeProxy(v, root, `${path}${path && "."}${p}`);
|
return makeProxy(v, root, `${path}${path && "."}${p}`);
|
||||||
|
|
||||||
|
// primitive or similar, no need to proxy further
|
||||||
return v;
|
return v;
|
||||||
},
|
},
|
||||||
|
|
||||||
set(target, p: string, v) {
|
set(target, p: string, v) {
|
||||||
|
// avoid unnecessary updates to React Components and other listeners
|
||||||
if (target[p] === v) return true;
|
if (target[p] === v) return true;
|
||||||
|
|
||||||
target[p] = v;
|
target[p] = v;
|
||||||
|
// Call any listeners that are listening to a setting of this path
|
||||||
const setPath = `${path}${path && "."}${p}`;
|
const setPath = `${path}${path && "."}${p}`;
|
||||||
for (const subscription of subscriptions) {
|
for (const subscription of subscriptions) {
|
||||||
if (!subscription._path || subscription._path === setPath) {
|
if (!subscription._path || subscription._path === setPath) {
|
||||||
subscription(v, setPath);
|
subscription(v, setPath);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// And don't forget to persist the settings!
|
||||||
VencordNative.ipc.invoke(IpcEvents.SET_SETTINGS, JSON.stringify(root, null, 4));
|
VencordNative.ipc.invoke(IpcEvents.SET_SETTINGS, JSON.stringify(root, null, 4));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -70,6 +96,9 @@ function makeProxy(settings: Settings, root = settings, path = ""): Settings {
|
||||||
* Same as {@link Settings} but unproxied. You should treat this as readonly,
|
* Same as {@link Settings} but unproxied. You should treat this as readonly,
|
||||||
* as modifying properties on this will not save to disk or call settings
|
* as modifying properties on this will not save to disk or call settings
|
||||||
* listeners.
|
* listeners.
|
||||||
|
* WARNING: default values specified in plugin.options will not be ensured here. In other words,
|
||||||
|
* settings for which you specified a default value may be uninitialised. If you need proper
|
||||||
|
* handling for default values, use {@link Settings}
|
||||||
*/
|
*/
|
||||||
export const PlainSettings = settings;
|
export const PlainSettings = settings;
|
||||||
/**
|
/**
|
||||||
|
@ -78,6 +107,7 @@ export const PlainSettings = settings;
|
||||||
* This recursively proxies objects. If you need the object non proxied, use {@link PlainSettings}
|
* This recursively proxies objects. If you need the object non proxied, use {@link PlainSettings}
|
||||||
*/
|
*/
|
||||||
export const Settings = makeProxy(settings);
|
export const Settings = makeProxy(settings);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Settings hook for React components. Returns a smart settings
|
* Settings hook for React components. Returns a smart settings
|
||||||
* object that automagically triggers a rerender if any properties
|
* object that automagically triggers a rerender if any properties
|
||||||
|
|
Loading…
Reference in a new issue