aboutsummaryrefslogtreecommitdiff
path: root/src/api/settings.ts
diff options
context:
space:
mode:
authorVendicated <vendicated@riseup.net>2023-05-06 01:36:00 +0200
committerVendicated <vendicated@riseup.net>2023-05-06 01:36:00 +0200
commit0d5e2d0696da494aee2126b4cadbca7e07066b89 (patch)
tree4a8159ba43f5f283e28101eb3d92e1f4f0b52035 /src/api/settings.ts
parent2834bed518a1fc1c384a93d599cc1b03555177c7 (diff)
downloadVencord-0d5e2d0696da494aee2126b4cadbca7e07066b89.tar.gz
Vencord-0d5e2d0696da494aee2126b4cadbca7e07066b89.tar.bz2
Vencord-0d5e2d0696da494aee2126b4cadbca7e07066b89.zip
[skip ci] Refactor utils
Diffstat (limited to 'src/api/settings.ts')
-rw-r--r--src/api/settings.ts283
1 files changed, 0 insertions, 283 deletions
diff --git a/src/api/settings.ts b/src/api/settings.ts
deleted file mode 100644
index 2329f94..0000000
--- a/src/api/settings.ts
+++ /dev/null
@@ -1,283 +0,0 @@
-/*
- * Vencord, a modification for Discord's desktop app
- * Copyright (c) 2022 Vendicated and contributors
- *
- * 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 { debounce } from "@utils/debounce";
-import { localStorage } from "@utils/localStorage";
-import Logger from "@utils/Logger";
-import { mergeDefaults } from "@utils/misc";
-import { putCloudSettings } from "@utils/settingsSync";
-import { DefinedSettings, OptionType, SettingsChecks, SettingsDefinition } from "@utils/types";
-import { React } from "@webpack/common";
-
-import plugins from "~plugins";
-
-const logger = new Logger("Settings");
-export interface Settings {
- notifyAboutUpdates: boolean;
- autoUpdate: boolean;
- autoUpdateNotification: boolean,
- useQuickCss: boolean;
- enableReactDevtools: boolean;
- themeLinks: string[];
- frameless: boolean;
- transparent: boolean;
- winCtrlQ: boolean;
- macosTranslucency: boolean;
- disableMinSize: boolean;
- winNativeTitleBar: boolean;
- plugins: {
- [plugin: string]: {
- enabled: boolean;
- [setting: string]: any;
- };
- };
-
- notifications: {
- timeout: number;
- position: "top-right" | "bottom-right";
- useNative: "always" | "never" | "not-focused";
- logLimit: number;
- };
-
- cloud: {
- authenticated: boolean;
- url: string;
- settingsSync: boolean;
- settingsSyncVersion: number;
- };
-}
-
-const DefaultSettings: Settings = {
- notifyAboutUpdates: true,
- autoUpdate: false,
- autoUpdateNotification: true,
- useQuickCss: true,
- themeLinks: [],
- enableReactDevtools: false,
- frameless: false,
- transparent: false,
- winCtrlQ: false,
- macosTranslucency: false,
- disableMinSize: false,
- winNativeTitleBar: false,
- plugins: {},
-
- notifications: {
- timeout: 5000,
- position: "bottom-right",
- useNative: "not-focused",
- logLimit: 50
- },
-
- cloud: {
- authenticated: false,
- url: "https://api.vencord.dev/",
- settingsSync: false,
- settingsSyncVersion: 0
- }
-};
-
-try {
- var settings = JSON.parse(VencordNative.settings.get()) as Settings;
- mergeDefaults(settings, DefaultSettings);
-} catch (err) {
- var settings = mergeDefaults({} as Settings, DefaultSettings);
- logger.error("An error occurred while loading the settings. Corrupt settings file?\n", err);
-}
-
-const saveSettingsOnFrequentAction = debounce(async () => {
- if (Settings.cloud.settingsSync && Settings.cloud.authenticated) {
- await putCloudSettings();
- delete localStorage.Vencord_settingsDirty;
- }
-}, 60_000);
-
-type SubscriptionCallback = ((newValue: any, path: string) => void) & { _path?: string; };
-const subscriptions = new Set<SubscriptionCallback>();
-
-const proxyCache = {} as Record<string, any>;
-
-// Wraps the passed settings object in a Proxy to nicely handle change listeners and default values
-function makeProxy(settings: any, root = settings, path = ""): Settings {
- return proxyCache[path] ??= new Proxy(settings, {
- get(target, p: string) {
- const v = target[p];
-
- // using "in" is important in the following cases to properly handle falsy or nullish values
- if (!(p in target)) {
- // Return empty for plugins with no settings
- if (path === "plugins" && p in plugins)
- return target[p] = makeProxy({
- enabled: plugins[p].required ?? plugins[p].enabledByDefault ?? false
- }, root, `plugins.${p}`);
-
- // 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 (target[p] = setting.default);
- if (setting.type === OptionType.SELECT) {
- const def = setting.options.find(o => o.default);
- if (def)
- target[p] = def.value;
- return def?.value;
- }
- }
- }
- return v;
- }
-
- // Recursively proxy Objects with the updated property path
- if (typeof v === "object" && !Array.isArray(v) && v !== null)
- return makeProxy(v, root, `${path}${path && "."}${p}`);
-
- // primitive or similar, no need to proxy further
- return v;
- },
-
- set(target, p: string, v) {
- // avoid unnecessary updates to React Components and other listeners
- if (target[p] === v) return true;
-
- target[p] = v;
- // Call any listeners that are listening to a setting of this path
- const setPath = `${path}${path && "."}${p}`;
- delete proxyCache[setPath];
- for (const subscription of subscriptions) {
- if (!subscription._path || subscription._path === setPath) {
- subscription(v, setPath);
- }
- }
- // And don't forget to persist the settings!
- PlainSettings.cloud.settingsSyncVersion = Date.now();
- localStorage.Vencord_settingsDirty = true;
- saveSettingsOnFrequentAction();
- VencordNative.settings.set(JSON.stringify(root, null, 4));
- return true;
- }
- });
-}
-
-/**
- * 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
- * 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;
-/**
- * A smart settings object. Altering props automagically saves
- * the updated settings to disk.
- * This recursively proxies objects. If you need the object non proxied, use {@link PlainSettings}
- */
-export const Settings = makeProxy(settings);
-
-/**
- * Settings hook for React components. Returns a smart settings
- * object that automagically triggers a rerender if any properties
- * are altered
- * @param paths An optional list of paths to whitelist for rerenders
- * @returns Settings
- */
-// TODO: Representing paths as essentially "string[].join('.')" wont allow dots in paths, change to "paths?: string[][]" later
-export function useSettings(paths?: UseSettings<Settings>[]) {
- const [, forceUpdate] = React.useReducer(() => ({}), {});
-
- const onUpdate: SubscriptionCallback = paths
- ? (value, path) => paths.includes(path as UseSettings<Settings>) && forceUpdate()
- : forceUpdate;
-
- React.useEffect(() => {
- subscriptions.add(onUpdate);
- return () => void subscriptions.delete(onUpdate);
- }, []);
-
- return Settings;
-}
-
-// Resolves a possibly nested prop in the form of "some.nested.prop" to type of T.some.nested.prop
-type ResolvePropDeep<T, P> = P extends "" ? T :
- P extends `${infer Pre}.${infer Suf}` ?
- Pre extends keyof T ? ResolvePropDeep<T[Pre], Suf> : never : P extends keyof T ? T[P] : never;
-
-/**
- * Add a settings listener that will be invoked whenever the desired setting is updated
- * @param path Path to the setting that you want to watch, for example "plugins.Unindent.enabled" will fire your callback
- * whenever Unindent is toggled. Pass an empty string to get notified for all changes
- * @param onUpdate Callback function whenever a setting matching path is updated. It gets passed the new value and the path
- * to the updated setting. This path will be the same as your path argument, unless it was an empty string.
- *
- * @example addSettingsListener("", (newValue, path) => console.log(`${path} is now ${newValue}`))
- * addSettingsListener("plugins.Unindent.enabled", v => console.log("Unindent is now", v ? "enabled" : "disabled"))
- */
-export function addSettingsListener<Path extends keyof Settings>(path: Path, onUpdate: (newValue: Settings[Path], path: Path) => void): void;
-export function addSettingsListener<Path extends string>(path: Path, onUpdate: (newValue: Path extends "" ? any : ResolvePropDeep<Settings, Path>, path: Path extends "" ? string : Path) => void): void;
-export function addSettingsListener(path: string, onUpdate: (newValue: any, path: string) => void) {
- (onUpdate as SubscriptionCallback)._path = path;
- subscriptions.add(onUpdate);
-}
-
-export function migratePluginSettings(name: string, ...oldNames: string[]) {
- const { plugins } = settings;
- if (name in plugins) return;
-
- for (const oldName of oldNames) {
- if (oldName in plugins) {
- logger.info(`Migrating settings from old name ${oldName} to ${name}`);
- plugins[name] = plugins[oldName];
- delete plugins[oldName];
- VencordNative.settings.set(JSON.stringify(settings, null, 4));
- break;
- }
- }
-}
-
-export function definePluginSettings<D extends SettingsDefinition, C extends SettingsChecks<D>>(def: D, checks?: C) {
- const definedSettings: DefinedSettings<D> = {
- get store() {
- if (!definedSettings.pluginName) throw new Error("Cannot access settings before plugin is initialized");
- return Settings.plugins[definedSettings.pluginName] as any;
- },
- use: settings => useSettings(
- settings?.map(name => `plugins.${definedSettings.pluginName}.${name}`) as UseSettings<Settings>[]
- ).plugins[definedSettings.pluginName] as any,
- def,
- checks: checks ?? {},
- pluginName: "",
- };
- return definedSettings;
-}
-
-type UseSettings<T extends object> = ResolveUseSettings<T>[keyof T];
-
-type ResolveUseSettings<T extends object> = {
- [Key in keyof T]:
- Key extends string
- ? T[Key] extends Record<string, unknown>
- // @ts-ignore "Type instantiation is excessively deep and possibly infinite"
- ? UseSettings<T[Key]> extends string ? `${Key}.${UseSettings<T[Key]>}` : never
- : Key
- : never;
-};