mirror of
https://github.com/Vendicated/Vencord.git
synced 2025-02-24 23:38:32 +00:00
- Removes the option to disable update notifications. Users really should not be outdated, so this option was never good. To disable notifications, turn on auto update - Enables auto update by default. Users keep complaining about issues while being outdated, so this should help - Update Notification now opens Updater in a modal to remove dependency on Settings patch. This makes it slightly more failsafe, it's unlikely that both modals and our settings patch break
256 lines
8.2 KiB
TypeScript
256 lines
8.2 KiB
TypeScript
/*
|
|
* 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 "@shared/debounce";
|
|
import { SettingsStore as SettingsStoreClass } from "@shared/SettingsStore";
|
|
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 {
|
|
autoUpdate: boolean;
|
|
autoUpdateNotification: boolean,
|
|
useQuickCss: boolean;
|
|
enableReactDevtools: boolean;
|
|
themeLinks: string[];
|
|
enabledThemes: string[];
|
|
frameless: boolean;
|
|
transparent: boolean;
|
|
winCtrlQ: boolean;
|
|
macosVibrancyStyle:
|
|
| "content"
|
|
| "fullscreen-ui"
|
|
| "header"
|
|
| "hud"
|
|
| "menu"
|
|
| "popover"
|
|
| "selection"
|
|
| "sidebar"
|
|
| "titlebar"
|
|
| "tooltip"
|
|
| "under-page"
|
|
| "window"
|
|
| undefined;
|
|
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 = {
|
|
autoUpdate: true,
|
|
autoUpdateNotification: true,
|
|
useQuickCss: true,
|
|
themeLinks: [],
|
|
enabledThemes: [],
|
|
enableReactDevtools: false,
|
|
frameless: false,
|
|
transparent: false,
|
|
winCtrlQ: false,
|
|
macosVibrancyStyle: undefined,
|
|
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
|
|
}
|
|
};
|
|
|
|
const settings = VencordNative.settings.get();
|
|
mergeDefaults(settings, DefaultSettings);
|
|
|
|
const saveSettingsOnFrequentAction = debounce(async () => {
|
|
if (Settings.cloud.settingsSync && Settings.cloud.authenticated) {
|
|
await putCloudSettings();
|
|
delete localStorage.Vencord_settingsDirty;
|
|
}
|
|
}, 60_000);
|
|
|
|
|
|
export const SettingsStore = new SettingsStoreClass(settings, {
|
|
readOnly: true,
|
|
getDefaultValue({
|
|
target,
|
|
key,
|
|
path
|
|
}) {
|
|
const v = target[key];
|
|
if (!plugins) return v; // plugins not initialised yet. this means this path was reached by being called on the top level
|
|
|
|
if (path === "plugins" && key in plugins)
|
|
return target[key] = {
|
|
enabled: plugins[key].required ?? plugins[key].enabledByDefault ?? false
|
|
};
|
|
|
|
// 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?.[key];
|
|
if (!setting) return v;
|
|
|
|
if ("default" in setting)
|
|
// normal setting with a default value
|
|
return (target[key] = setting.default);
|
|
|
|
if (setting.type === OptionType.SELECT) {
|
|
const def = setting.options.find(o => o.default);
|
|
if (def)
|
|
target[key] = def.value;
|
|
return def?.value;
|
|
}
|
|
}
|
|
}
|
|
return v;
|
|
}
|
|
});
|
|
|
|
SettingsStore.addGlobalChangeListener((_, path) => {
|
|
SettingsStore.plain.cloud.settingsSyncVersion = Date.now();
|
|
localStorage.Vencord_settingsDirty = true;
|
|
saveSettingsOnFrequentAction();
|
|
VencordNative.settings.set(SettingsStore.plain, path);
|
|
});
|
|
|
|
/**
|
|
* 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 = SettingsStore.store;
|
|
|
|
/**
|
|
* 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(() => ({}), {});
|
|
|
|
React.useEffect(() => {
|
|
if (paths) {
|
|
paths.forEach(p => SettingsStore.addChangeListener(p, forceUpdate));
|
|
return () => paths.forEach(p => SettingsStore.removeChangeListener(p, forceUpdate));
|
|
} else {
|
|
SettingsStore.addGlobalChangeListener(forceUpdate);
|
|
return () => SettingsStore.removeGlobalChangeListener(forceUpdate);
|
|
}
|
|
}, []);
|
|
|
|
return SettingsStore.store;
|
|
}
|
|
|
|
export function migratePluginSettings(name: string, ...oldNames: string[]) {
|
|
const { plugins } = SettingsStore.plain;
|
|
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];
|
|
SettingsStore.markAsChanged();
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
export function definePluginSettings<
|
|
Def extends SettingsDefinition,
|
|
Checks extends SettingsChecks<Def>,
|
|
PrivateSettings extends object = {}
|
|
>(def: Def, checks?: Checks) {
|
|
const definedSettings: DefinedSettings<Def, Checks, PrivateSettings> = {
|
|
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 ?? {} as any,
|
|
pluginName: "",
|
|
|
|
withPrivateSettings<T extends object>() {
|
|
return this as DefinedSettings<Def, Checks, T>;
|
|
}
|
|
};
|
|
|
|
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;
|
|
};
|