rewrite settings api to use SettingsStore class (#2257)
Co-authored-by: Nuckyz <61953774+Nuckyz@users.noreply.github.com>
This commit is contained in:
parent
7190437e92
commit
9aa205b5ec
16 changed files with 336 additions and 180 deletions
|
@ -26,6 +26,7 @@ import { debounce } from "../src/utils";
|
||||||
import { EXTENSION_BASE_URL } from "../src/utils/web-metadata";
|
import { EXTENSION_BASE_URL } from "../src/utils/web-metadata";
|
||||||
import { getTheme, Theme } from "../src/utils/discord";
|
import { getTheme, Theme } from "../src/utils/discord";
|
||||||
import { getThemeInfo } from "../src/main/themes";
|
import { getThemeInfo } from "../src/main/themes";
|
||||||
|
import { Settings } from "../src/Vencord";
|
||||||
|
|
||||||
// Discord deletes this so need to store in variable
|
// Discord deletes this so need to store in variable
|
||||||
const { localStorage } = window;
|
const { localStorage } = window;
|
||||||
|
@ -96,8 +97,15 @@ window.VencordNative = {
|
||||||
},
|
},
|
||||||
|
|
||||||
settings: {
|
settings: {
|
||||||
get: () => localStorage.getItem("VencordSettings") || "{}",
|
get: () => {
|
||||||
set: async (s: string) => localStorage.setItem("VencordSettings", s),
|
try {
|
||||||
|
return JSON.parse(localStorage.getItem("VencordSettings") || "{}");
|
||||||
|
} catch (e) {
|
||||||
|
console.error("Failed to parse settings from localStorage: ", e);
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
},
|
||||||
|
set: async (s: Settings) => localStorage.setItem("VencordSettings", JSON.stringify(s)),
|
||||||
getSettingsDir: async () => "LocalStorage"
|
getSettingsDir: async () => "LocalStorage"
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
@ -4,11 +4,12 @@
|
||||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { PluginIpcMappings } from "@main/ipcPlugins";
|
||||||
|
import type { UserThemeHeader } from "@main/themes";
|
||||||
import { IpcEvents } from "@utils/IpcEvents";
|
import { IpcEvents } from "@utils/IpcEvents";
|
||||||
import { IpcRes } from "@utils/types";
|
import { IpcRes } from "@utils/types";
|
||||||
|
import type { Settings } from "api/Settings";
|
||||||
import { ipcRenderer } from "electron";
|
import { ipcRenderer } from "electron";
|
||||||
import { PluginIpcMappings } from "main/ipcPlugins";
|
|
||||||
import type { UserThemeHeader } from "main/themes";
|
|
||||||
|
|
||||||
function invoke<T = any>(event: IpcEvents, ...args: any[]) {
|
function invoke<T = any>(event: IpcEvents, ...args: any[]) {
|
||||||
return ipcRenderer.invoke(event, ...args) as Promise<T>;
|
return ipcRenderer.invoke(event, ...args) as Promise<T>;
|
||||||
|
@ -46,8 +47,8 @@ export default {
|
||||||
},
|
},
|
||||||
|
|
||||||
settings: {
|
settings: {
|
||||||
get: () => sendSync<string>(IpcEvents.GET_SETTINGS),
|
get: () => sendSync<Settings>(IpcEvents.GET_SETTINGS),
|
||||||
set: (settings: string) => invoke<void>(IpcEvents.SET_SETTINGS, settings),
|
set: (settings: Settings, pathToNotify?: string) => invoke<void>(IpcEvents.SET_SETTINGS, settings, pathToNotify),
|
||||||
getSettingsDir: () => invoke<string>(IpcEvents.GET_SETTINGS_DIR),
|
getSettingsDir: () => invoke<string>(IpcEvents.GET_SETTINGS_DIR),
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { SettingsStore as SettingsStoreClass } from "@shared/SettingsStore";
|
||||||
import { debounce } from "@utils/debounce";
|
import { debounce } from "@utils/debounce";
|
||||||
import { localStorage } from "@utils/localStorage";
|
import { localStorage } from "@utils/localStorage";
|
||||||
import { Logger } from "@utils/Logger";
|
import { Logger } from "@utils/Logger";
|
||||||
|
@ -52,7 +53,6 @@ export interface Settings {
|
||||||
| "under-page"
|
| "under-page"
|
||||||
| "window"
|
| "window"
|
||||||
| undefined;
|
| undefined;
|
||||||
macosTranslucency: boolean | undefined;
|
|
||||||
disableMinSize: boolean;
|
disableMinSize: boolean;
|
||||||
winNativeTitleBar: boolean;
|
winNativeTitleBar: boolean;
|
||||||
plugins: {
|
plugins: {
|
||||||
|
@ -88,8 +88,6 @@ const DefaultSettings: Settings = {
|
||||||
frameless: false,
|
frameless: false,
|
||||||
transparent: false,
|
transparent: false,
|
||||||
winCtrlQ: false,
|
winCtrlQ: false,
|
||||||
// Replaced by macosVibrancyStyle
|
|
||||||
macosTranslucency: undefined,
|
|
||||||
macosVibrancyStyle: undefined,
|
macosVibrancyStyle: undefined,
|
||||||
disableMinSize: false,
|
disableMinSize: false,
|
||||||
winNativeTitleBar: false,
|
winNativeTitleBar: false,
|
||||||
|
@ -110,13 +108,8 @@ const DefaultSettings: Settings = {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
try {
|
const settings = VencordNative.settings.get();
|
||||||
var settings = JSON.parse(VencordNative.settings.get()) as Settings;
|
|
||||||
mergeDefaults(settings, DefaultSettings);
|
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 () => {
|
const saveSettingsOnFrequentAction = debounce(async () => {
|
||||||
if (Settings.cloud.settingsSync && Settings.cloud.authenticated) {
|
if (Settings.cloud.settingsSync && Settings.cloud.authenticated) {
|
||||||
|
@ -125,76 +118,52 @@ const saveSettingsOnFrequentAction = debounce(async () => {
|
||||||
}
|
}
|
||||||
}, 60_000);
|
}, 60_000);
|
||||||
|
|
||||||
type SubscriptionCallback = ((newValue: any, path: string) => void) & { _paths?: Array<string>; };
|
|
||||||
const subscriptions = new Set<SubscriptionCallback>();
|
|
||||||
|
|
||||||
const proxyCache = {} as Record<string, any>;
|
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
|
||||||
|
|
||||||
// Wraps the passed settings object in a Proxy to nicely handle change listeners and default values
|
if (path === "plugins" && key in plugins)
|
||||||
function makeProxy(settings: any, root = settings, path = ""): Settings {
|
return target[key] = {
|
||||||
return proxyCache[path] ??= new Proxy(settings, {
|
enabled: plugins[key].required ?? plugins[key].enabledByDefault ?? false
|
||||||
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
|
// Since the property is not set, check if this is a plugin's setting and if so, try to resolve
|
||||||
// the default value.
|
// the default value.
|
||||||
if (path.startsWith("plugins.")) {
|
if (path.startsWith("plugins.")) {
|
||||||
const plugin = path.slice("plugins.".length);
|
const plugin = path.slice("plugins.".length);
|
||||||
if (plugin in plugins) {
|
if (plugin in plugins) {
|
||||||
const setting = plugins[plugin].options?.[p];
|
const setting = plugins[plugin].options?.[key];
|
||||||
if (!setting) return v;
|
if (!setting) return v;
|
||||||
|
|
||||||
if ("default" in setting)
|
if ("default" in setting)
|
||||||
// normal setting with a default value
|
// normal setting with a default value
|
||||||
return (target[p] = setting.default);
|
return (target[key] = setting.default);
|
||||||
|
|
||||||
if (setting.type === OptionType.SELECT) {
|
if (setting.type === OptionType.SELECT) {
|
||||||
const def = setting.options.find(o => o.default);
|
const def = setting.options.find(o => o.default);
|
||||||
if (def)
|
if (def)
|
||||||
target[p] = def.value;
|
target[key] = def.value;
|
||||||
return def?.value;
|
return def?.value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return v;
|
return v;
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// Recursively proxy Objects with the updated property path
|
SettingsStore.addGlobalChangeListener((_, path) => {
|
||||||
if (typeof v === "object" && !Array.isArray(v) && v !== null)
|
SettingsStore.plain.cloud.settingsSyncVersion = Date.now();
|
||||||
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._paths || subscription._paths.includes(setPath)) {
|
|
||||||
subscription(v, setPath);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// And don't forget to persist the settings!
|
|
||||||
PlainSettings.cloud.settingsSyncVersion = Date.now();
|
|
||||||
localStorage.Vencord_settingsDirty = true;
|
localStorage.Vencord_settingsDirty = true;
|
||||||
saveSettingsOnFrequentAction();
|
saveSettingsOnFrequentAction();
|
||||||
VencordNative.settings.set(JSON.stringify(root, null, 4));
|
VencordNative.settings.set(SettingsStore.plain, path);
|
||||||
return true;
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Same as {@link Settings} but unproxied. You should treat this as readonly,
|
* Same as {@link Settings} but unproxied. You should treat this as readonly,
|
||||||
|
@ -210,7 +179,7 @@ export const PlainSettings = settings;
|
||||||
* the updated settings to disk.
|
* the updated settings to disk.
|
||||||
* 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 = SettingsStore.store;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Settings hook for React components. Returns a smart settings
|
* Settings hook for React components. Returns a smart settings
|
||||||
|
@ -223,45 +192,21 @@ export const Settings = makeProxy(settings);
|
||||||
export function useSettings(paths?: UseSettings<Settings>[]) {
|
export function useSettings(paths?: UseSettings<Settings>[]) {
|
||||||
const [, forceUpdate] = React.useReducer(() => ({}), {});
|
const [, forceUpdate] = React.useReducer(() => ({}), {});
|
||||||
|
|
||||||
if (paths) {
|
|
||||||
(forceUpdate as SubscriptionCallback)._paths = paths;
|
|
||||||
}
|
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
subscriptions.add(forceUpdate);
|
if (paths) {
|
||||||
return () => void subscriptions.delete(forceUpdate);
|
paths.forEach(p => SettingsStore.addChangeListener(p, forceUpdate));
|
||||||
|
return () => paths.forEach(p => SettingsStore.removeChangeListener(p, forceUpdate));
|
||||||
|
} else {
|
||||||
|
SettingsStore.addGlobalChangeListener(forceUpdate);
|
||||||
|
return () => SettingsStore.removeGlobalChangeListener(forceUpdate);
|
||||||
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return Settings;
|
return SettingsStore.store;
|
||||||
}
|
|
||||||
|
|
||||||
// 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) {
|
|
||||||
if (path) {
|
|
||||||
((onUpdate as SubscriptionCallback)._paths ??= []).push(path);
|
|
||||||
}
|
|
||||||
|
|
||||||
subscriptions.add(onUpdate);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function migratePluginSettings(name: string, ...oldNames: string[]) {
|
export function migratePluginSettings(name: string, ...oldNames: string[]) {
|
||||||
const { plugins } = settings;
|
const { plugins } = SettingsStore.plain;
|
||||||
if (name in plugins) return;
|
if (name in plugins) return;
|
||||||
|
|
||||||
for (const oldName of oldNames) {
|
for (const oldName of oldNames) {
|
||||||
|
@ -269,7 +214,7 @@ export function migratePluginSettings(name: string, ...oldNames: string[]) {
|
||||||
logger.info(`Migrating settings from old name ${oldName} to ${name}`);
|
logger.info(`Migrating settings from old name ${oldName} to ${name}`);
|
||||||
plugins[name] = plugins[oldName];
|
plugins[name] = plugins[oldName];
|
||||||
delete plugins[oldName];
|
delete plugins[oldName];
|
||||||
VencordNative.settings.set(JSON.stringify(settings, null, 4));
|
SettingsStore.markAsChanged();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,6 +22,7 @@ import { Flex } from "@components/Flex";
|
||||||
import { DeleteIcon } from "@components/Icons";
|
import { DeleteIcon } from "@components/Icons";
|
||||||
import { Link } from "@components/Link";
|
import { Link } from "@components/Link";
|
||||||
import PluginModal from "@components/PluginSettings/PluginModal";
|
import PluginModal from "@components/PluginSettings/PluginModal";
|
||||||
|
import type { UserThemeHeader } from "@main/themes";
|
||||||
import { openInviteModal } from "@utils/discord";
|
import { openInviteModal } from "@utils/discord";
|
||||||
import { Margins } from "@utils/margins";
|
import { Margins } from "@utils/margins";
|
||||||
import { classes } from "@utils/misc";
|
import { classes } from "@utils/misc";
|
||||||
|
@ -30,7 +31,6 @@ import { showItemInFolder } from "@utils/native";
|
||||||
import { useAwaiter } from "@utils/react";
|
import { useAwaiter } from "@utils/react";
|
||||||
import { findByPropsLazy, findLazy } from "@webpack";
|
import { findByPropsLazy, findLazy } from "@webpack";
|
||||||
import { Button, Card, Forms, React, showToast, TabBar, TextArea, useEffect, useRef, useState } from "@webpack/common";
|
import { Button, Card, Forms, React, showToast, TabBar, TextArea, useEffect, useRef, useState } from "@webpack/common";
|
||||||
import { UserThemeHeader } from "main/themes";
|
|
||||||
import type { ComponentType, Ref, SyntheticEvent } from "react";
|
import type { ComponentType, Ref, SyntheticEvent } from "react";
|
||||||
|
|
||||||
import { AddonCard } from "./AddonCard";
|
import { AddonCard } from "./AddonCard";
|
||||||
|
|
|
@ -50,14 +50,6 @@ function VencordSettings() {
|
||||||
const isMac = navigator.platform.toLowerCase().startsWith("mac");
|
const isMac = navigator.platform.toLowerCase().startsWith("mac");
|
||||||
const needsVibrancySettings = IS_DISCORD_DESKTOP && isMac;
|
const needsVibrancySettings = IS_DISCORD_DESKTOP && isMac;
|
||||||
|
|
||||||
// One-time migration of the old setting to the new one if necessary.
|
|
||||||
React.useEffect(() => {
|
|
||||||
if (settings.macosTranslucency === true && !settings.macosVibrancyStyle) {
|
|
||||||
settings.macosVibrancyStyle = "sidebar";
|
|
||||||
settings.macosTranslucency = undefined;
|
|
||||||
}
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const Switches: Array<false | {
|
const Switches: Array<false | {
|
||||||
key: KeysOfType<typeof settings, boolean>;
|
key: KeysOfType<typeof settings, boolean>;
|
||||||
title: string;
|
title: string;
|
||||||
|
@ -164,7 +156,7 @@ function VencordSettings() {
|
||||||
options={[
|
options={[
|
||||||
// Sorted from most opaque to most transparent
|
// Sorted from most opaque to most transparent
|
||||||
{
|
{
|
||||||
label: "No vibrancy", default: !settings.macosTranslucency, value: undefined
|
label: "No vibrancy", value: undefined
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "Under Page (window tinting)",
|
label: "Under Page (window tinting)",
|
||||||
|
@ -191,9 +183,8 @@ function VencordSettings() {
|
||||||
value: "header"
|
value: "header"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "Sidebar (old value for transparent windows)",
|
label: "Sidebar",
|
||||||
value: "sidebar",
|
value: "sidebar"
|
||||||
default: settings.macosTranslucency
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "Tooltip",
|
label: "Tooltip",
|
||||||
|
|
|
@ -19,7 +19,8 @@
|
||||||
import { app, protocol, session } from "electron";
|
import { app, protocol, session } from "electron";
|
||||||
import { join } from "path";
|
import { join } from "path";
|
||||||
|
|
||||||
import { ensureSafePath, getSettings } from "./ipcMain";
|
import { ensureSafePath } from "./ipcMain";
|
||||||
|
import { RendererSettings } from "./settings";
|
||||||
import { IS_VANILLA, THEMES_DIR } from "./utils/constants";
|
import { IS_VANILLA, THEMES_DIR } from "./utils/constants";
|
||||||
import { installExt } from "./utils/extensions";
|
import { installExt } from "./utils/extensions";
|
||||||
|
|
||||||
|
@ -55,7 +56,7 @@ if (IS_VESKTOP || !IS_VANILLA) {
|
||||||
});
|
});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (getSettings().enableReactDevtools)
|
if (RendererSettings.store.enableReactDevtools)
|
||||||
installExt("fmkadmapgofadopljbjfkapdkoienihi")
|
installExt("fmkadmapgofadopljbjfkapdkoienihi")
|
||||||
.then(() => console.info("[Vencord] Installed React Developer Tools"))
|
.then(() => console.info("[Vencord] Installed React Developer Tools"))
|
||||||
.catch(err => console.error("[Vencord] Failed to install React Developer Tools", err));
|
.catch(err => console.error("[Vencord] Failed to install React Developer Tools", err));
|
||||||
|
|
|
@ -18,22 +18,21 @@
|
||||||
|
|
||||||
import "./updater";
|
import "./updater";
|
||||||
import "./ipcPlugins";
|
import "./ipcPlugins";
|
||||||
|
import "./settings";
|
||||||
|
|
||||||
import { debounce } from "@utils/debounce";
|
import { debounce } from "@utils/debounce";
|
||||||
import { IpcEvents } from "@utils/IpcEvents";
|
import { IpcEvents } from "@utils/IpcEvents";
|
||||||
import { Queue } from "@utils/Queue";
|
|
||||||
import { BrowserWindow, ipcMain, shell, systemPreferences } from "electron";
|
import { BrowserWindow, ipcMain, shell, systemPreferences } from "electron";
|
||||||
import { FSWatcher, mkdirSync, readFileSync, watch } from "fs";
|
import { FSWatcher, mkdirSync, watch, writeFileSync } from "fs";
|
||||||
import { open, readdir, readFile, writeFile } from "fs/promises";
|
import { open, readdir, readFile } from "fs/promises";
|
||||||
import { join, normalize } from "path";
|
import { join, normalize } from "path";
|
||||||
|
|
||||||
import monacoHtml from "~fileContent/monacoWin.html;base64";
|
import monacoHtml from "~fileContent/monacoWin.html;base64";
|
||||||
|
|
||||||
import { getThemeInfo, stripBOM, UserThemeHeader } from "./themes";
|
import { getThemeInfo, stripBOM, UserThemeHeader } from "./themes";
|
||||||
import { ALLOWED_PROTOCOLS, QUICKCSS_PATH, SETTINGS_DIR, SETTINGS_FILE, THEMES_DIR } from "./utils/constants";
|
import { ALLOWED_PROTOCOLS, QUICKCSS_PATH, THEMES_DIR } from "./utils/constants";
|
||||||
import { makeLinksOpenExternally } from "./utils/externalLinks";
|
import { makeLinksOpenExternally } from "./utils/externalLinks";
|
||||||
|
|
||||||
mkdirSync(SETTINGS_DIR, { recursive: true });
|
|
||||||
mkdirSync(THEMES_DIR, { recursive: true });
|
mkdirSync(THEMES_DIR, { recursive: true });
|
||||||
|
|
||||||
export function ensureSafePath(basePath: string, path: string) {
|
export function ensureSafePath(basePath: string, path: string) {
|
||||||
|
@ -71,22 +70,6 @@ function getThemeData(fileName: string) {
|
||||||
return readFile(safePath, "utf-8");
|
return readFile(safePath, "utf-8");
|
||||||
}
|
}
|
||||||
|
|
||||||
export function readSettings() {
|
|
||||||
try {
|
|
||||||
return readFileSync(SETTINGS_FILE, "utf-8");
|
|
||||||
} catch {
|
|
||||||
return "{}";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getSettings(): typeof import("@api/Settings").Settings {
|
|
||||||
try {
|
|
||||||
return JSON.parse(readSettings());
|
|
||||||
} catch {
|
|
||||||
return {} as any;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ipcMain.handle(IpcEvents.OPEN_QUICKCSS, () => shell.openPath(QUICKCSS_PATH));
|
ipcMain.handle(IpcEvents.OPEN_QUICKCSS, () => shell.openPath(QUICKCSS_PATH));
|
||||||
|
|
||||||
ipcMain.handle(IpcEvents.OPEN_EXTERNAL, (_, url) => {
|
ipcMain.handle(IpcEvents.OPEN_EXTERNAL, (_, url) => {
|
||||||
|
@ -101,12 +84,10 @@ ipcMain.handle(IpcEvents.OPEN_EXTERNAL, (_, url) => {
|
||||||
shell.openExternal(url);
|
shell.openExternal(url);
|
||||||
});
|
});
|
||||||
|
|
||||||
const cssWriteQueue = new Queue();
|
|
||||||
const settingsWriteQueue = new Queue();
|
|
||||||
|
|
||||||
ipcMain.handle(IpcEvents.GET_QUICK_CSS, () => readCss());
|
ipcMain.handle(IpcEvents.GET_QUICK_CSS, () => readCss());
|
||||||
ipcMain.handle(IpcEvents.SET_QUICK_CSS, (_, css) =>
|
ipcMain.handle(IpcEvents.SET_QUICK_CSS, (_, css) =>
|
||||||
cssWriteQueue.push(() => writeFile(QUICKCSS_PATH, css))
|
writeFileSync(QUICKCSS_PATH, css)
|
||||||
);
|
);
|
||||||
|
|
||||||
ipcMain.handle(IpcEvents.GET_THEMES_DIR, () => THEMES_DIR);
|
ipcMain.handle(IpcEvents.GET_THEMES_DIR, () => THEMES_DIR);
|
||||||
|
@ -117,13 +98,6 @@ ipcMain.handle(IpcEvents.GET_THEME_SYSTEM_VALUES, () => ({
|
||||||
"os-accent-color": `#${systemPreferences.getAccentColor?.() || ""}`
|
"os-accent-color": `#${systemPreferences.getAccentColor?.() || ""}`
|
||||||
}));
|
}));
|
||||||
|
|
||||||
ipcMain.handle(IpcEvents.GET_SETTINGS_DIR, () => SETTINGS_DIR);
|
|
||||||
ipcMain.on(IpcEvents.GET_SETTINGS, e => e.returnValue = readSettings());
|
|
||||||
|
|
||||||
ipcMain.handle(IpcEvents.SET_SETTINGS, (_, s) => {
|
|
||||||
settingsWriteQueue.push(() => writeFile(SETTINGS_FILE, s));
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
export function initIpc(mainWindow: BrowserWindow) {
|
export function initIpc(mainWindow: BrowserWindow) {
|
||||||
let quickCssWatcher: FSWatcher | undefined;
|
let quickCssWatcher: FSWatcher | undefined;
|
||||||
|
|
|
@ -20,7 +20,8 @@ import { onceDefined } from "@utils/onceDefined";
|
||||||
import electron, { app, BrowserWindowConstructorOptions, Menu } from "electron";
|
import electron, { app, BrowserWindowConstructorOptions, Menu } from "electron";
|
||||||
import { dirname, join } from "path";
|
import { dirname, join } from "path";
|
||||||
|
|
||||||
import { getSettings, initIpc } from "./ipcMain";
|
import { initIpc } from "./ipcMain";
|
||||||
|
import { RendererSettings } from "./settings";
|
||||||
import { IS_VANILLA } from "./utils/constants";
|
import { IS_VANILLA } from "./utils/constants";
|
||||||
|
|
||||||
console.log("[Vencord] Starting up...");
|
console.log("[Vencord] Starting up...");
|
||||||
|
@ -41,8 +42,7 @@ require.main!.filename = join(asarPath, discordPkg.main);
|
||||||
app.setAppPath(asarPath);
|
app.setAppPath(asarPath);
|
||||||
|
|
||||||
if (!IS_VANILLA) {
|
if (!IS_VANILLA) {
|
||||||
const settings = getSettings();
|
const settings = RendererSettings.store;
|
||||||
|
|
||||||
// Repatch after host updates on Windows
|
// Repatch after host updates on Windows
|
||||||
if (process.platform === "win32") {
|
if (process.platform === "win32") {
|
||||||
require("./patchWin32Updater");
|
require("./patchWin32Updater");
|
||||||
|
@ -84,13 +84,11 @@ if (!IS_VANILLA) {
|
||||||
options.backgroundColor = "#00000000";
|
options.backgroundColor = "#00000000";
|
||||||
}
|
}
|
||||||
|
|
||||||
const needsVibrancy = process.platform === "darwin" || (settings.macosVibrancyStyle || settings.macosTranslucency);
|
const needsVibrancy = process.platform === "darwin" && settings.macosVibrancyStyle;
|
||||||
|
|
||||||
if (needsVibrancy) {
|
if (needsVibrancy) {
|
||||||
options.backgroundColor = "#00000000";
|
options.backgroundColor = "#00000000";
|
||||||
if (settings.macosTranslucency) {
|
if (settings.macosVibrancyStyle) {
|
||||||
options.vibrancy = "sidebar";
|
|
||||||
} else if (settings.macosVibrancyStyle) {
|
|
||||||
options.vibrancy = settings.macosVibrancyStyle;
|
options.vibrancy = settings.macosVibrancyStyle;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
53
src/main/settings.ts
Normal file
53
src/main/settings.ts
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
/*
|
||||||
|
* Vencord, a Discord client mod
|
||||||
|
* Copyright (c) 2024 Vendicated and contributors
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type { Settings } from "@api/Settings";
|
||||||
|
import { SettingsStore } from "@shared/SettingsStore";
|
||||||
|
import { IpcEvents } from "@utils/IpcEvents";
|
||||||
|
import { ipcMain } from "electron";
|
||||||
|
import { mkdirSync, readFileSync, writeFileSync } from "fs";
|
||||||
|
|
||||||
|
import { NATIVE_SETTINGS_FILE, SETTINGS_DIR, SETTINGS_FILE } from "./utils/constants";
|
||||||
|
|
||||||
|
mkdirSync(SETTINGS_DIR, { recursive: true });
|
||||||
|
|
||||||
|
function readSettings<T = object>(name: string, file: string): Partial<T> {
|
||||||
|
try {
|
||||||
|
return JSON.parse(readFileSync(file, "utf-8"));
|
||||||
|
} catch (err: any) {
|
||||||
|
if (err?.code !== "ENOENT")
|
||||||
|
console.error(`Failed to read ${name} settings`, err);
|
||||||
|
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const RendererSettings = new SettingsStore(readSettings<Settings>("renderer", SETTINGS_FILE));
|
||||||
|
|
||||||
|
RendererSettings.addGlobalChangeListener(() => {
|
||||||
|
try {
|
||||||
|
writeFileSync(SETTINGS_FILE, JSON.stringify(RendererSettings.plain, null, 4));
|
||||||
|
} catch (e) {
|
||||||
|
console.error("Failed to write renderer settings", e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
ipcMain.handle(IpcEvents.GET_SETTINGS_DIR, () => SETTINGS_DIR);
|
||||||
|
ipcMain.on(IpcEvents.GET_SETTINGS, e => e.returnValue = RendererSettings.plain);
|
||||||
|
|
||||||
|
ipcMain.handle(IpcEvents.SET_SETTINGS, (_, data: Settings, pathToNotify?: string) => {
|
||||||
|
RendererSettings.setData(data, pathToNotify);
|
||||||
|
});
|
||||||
|
|
||||||
|
export const NativeSettings = new SettingsStore(readSettings("native", NATIVE_SETTINGS_FILE));
|
||||||
|
|
||||||
|
NativeSettings.addGlobalChangeListener(() => {
|
||||||
|
try {
|
||||||
|
writeFileSync(NATIVE_SETTINGS_FILE, JSON.stringify(NativeSettings.plain, null, 4));
|
||||||
|
} catch (e) {
|
||||||
|
console.error("Failed to write native settings", e);
|
||||||
|
}
|
||||||
|
});
|
|
@ -28,6 +28,7 @@ export const SETTINGS_DIR = join(DATA_DIR, "settings");
|
||||||
export const THEMES_DIR = join(DATA_DIR, "themes");
|
export const THEMES_DIR = join(DATA_DIR, "themes");
|
||||||
export const QUICKCSS_PATH = join(SETTINGS_DIR, "quickCss.css");
|
export const QUICKCSS_PATH = join(SETTINGS_DIR, "quickCss.css");
|
||||||
export const SETTINGS_FILE = join(SETTINGS_DIR, "settings.json");
|
export const SETTINGS_FILE = join(SETTINGS_DIR, "settings.json");
|
||||||
|
export const NATIVE_SETTINGS_FILE = join(SETTINGS_DIR, "native-settings.json");
|
||||||
export const ALLOWED_PROTOCOLS = [
|
export const ALLOWED_PROTOCOLS = [
|
||||||
"https:",
|
"https:",
|
||||||
"http:",
|
"http:",
|
||||||
|
|
|
@ -4,14 +4,14 @@
|
||||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { RendererSettings } from "@main/settings";
|
||||||
import { app } from "electron";
|
import { app } from "electron";
|
||||||
import { getSettings } from "main/ipcMain";
|
|
||||||
|
|
||||||
app.on("browser-window-created", (_, win) => {
|
app.on("browser-window-created", (_, win) => {
|
||||||
win.webContents.on("frame-created", (_, { frame }) => {
|
win.webContents.on("frame-created", (_, { frame }) => {
|
||||||
frame.once("dom-ready", () => {
|
frame.once("dom-ready", () => {
|
||||||
if (frame.url.startsWith("https://open.spotify.com/embed/")) {
|
if (frame.url.startsWith("https://open.spotify.com/embed/")) {
|
||||||
const settings = getSettings().plugins?.FixSpotifyEmbeds;
|
const settings = RendererSettings.store.plugins?.FixSpotifyEmbeds;
|
||||||
if (!settings?.enabled) return;
|
if (!settings?.enabled) return;
|
||||||
|
|
||||||
frame.executeJavaScript(`
|
frame.executeJavaScript(`
|
||||||
|
|
|
@ -4,14 +4,14 @@
|
||||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { RendererSettings } from "@main/settings";
|
||||||
import { app } from "electron";
|
import { app } from "electron";
|
||||||
import { getSettings } from "main/ipcMain";
|
|
||||||
|
|
||||||
app.on("browser-window-created", (_, win) => {
|
app.on("browser-window-created", (_, win) => {
|
||||||
win.webContents.on("frame-created", (_, { frame }) => {
|
win.webContents.on("frame-created", (_, { frame }) => {
|
||||||
frame.once("dom-ready", () => {
|
frame.once("dom-ready", () => {
|
||||||
if (frame.url.startsWith("https://www.youtube.com/")) {
|
if (frame.url.startsWith("https://www.youtube.com/")) {
|
||||||
const settings = getSettings().plugins?.FixYoutubeEmbeds;
|
const settings = RendererSettings.store.plugins?.FixYoutubeEmbeds;
|
||||||
if (!settings?.enabled) return;
|
if (!settings?.enabled) return;
|
||||||
|
|
||||||
frame.executeJavaScript(`
|
frame.executeJavaScript(`
|
||||||
|
|
182
src/shared/SettingsStore.ts
Normal file
182
src/shared/SettingsStore.ts
Normal file
|
@ -0,0 +1,182 @@
|
||||||
|
/*
|
||||||
|
* Vencord, a Discord client mod
|
||||||
|
* Copyright (c) 2024 Vendicated and contributors
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { LiteralUnion } from "type-fest";
|
||||||
|
|
||||||
|
// 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 `${infer Pre}.${infer Suf}`
|
||||||
|
? Pre extends keyof T
|
||||||
|
? ResolvePropDeep<T[Pre], Suf>
|
||||||
|
: any
|
||||||
|
: P extends keyof T
|
||||||
|
? T[P]
|
||||||
|
: any;
|
||||||
|
|
||||||
|
interface SettingsStoreOptions {
|
||||||
|
readOnly?: boolean;
|
||||||
|
getDefaultValue?: (data: {
|
||||||
|
target: any;
|
||||||
|
key: string;
|
||||||
|
root: any;
|
||||||
|
path: string;
|
||||||
|
}) => any;
|
||||||
|
}
|
||||||
|
|
||||||
|
// merges the SettingsStoreOptions type into the class
|
||||||
|
export interface SettingsStore<T extends object> extends SettingsStoreOptions { }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The SettingsStore allows you to easily create a mutable store that
|
||||||
|
* has support for global and path-based change listeners.
|
||||||
|
*/
|
||||||
|
export class SettingsStore<T extends object> {
|
||||||
|
private pathListeners = new Map<string, Set<(newData: any) => void>>();
|
||||||
|
private globalListeners = new Set<(newData: T, path: string) => void>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The store object. Making changes to this object will trigger the applicable change listeners
|
||||||
|
*/
|
||||||
|
public declare store: T;
|
||||||
|
/**
|
||||||
|
* The plain data. Changes to this object will not trigger any change listeners
|
||||||
|
*/
|
||||||
|
public declare plain: T;
|
||||||
|
|
||||||
|
public constructor(plain: T, options: SettingsStoreOptions = {}) {
|
||||||
|
this.plain = plain;
|
||||||
|
this.store = this.makeProxy(plain);
|
||||||
|
Object.assign(this, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
private makeProxy(object: any, root: T = object, path: string = "") {
|
||||||
|
const self = this;
|
||||||
|
|
||||||
|
return new Proxy(object, {
|
||||||
|
get(target, key: string) {
|
||||||
|
let v = target[key];
|
||||||
|
|
||||||
|
if (!(key in target) && self.getDefaultValue) {
|
||||||
|
v = self.getDefaultValue({
|
||||||
|
target,
|
||||||
|
key,
|
||||||
|
root,
|
||||||
|
path
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof v === "object" && v !== null && !Array.isArray(v))
|
||||||
|
return self.makeProxy(v, root, `${path}${path && "."}${key}`);
|
||||||
|
|
||||||
|
return v;
|
||||||
|
},
|
||||||
|
set(target, key: string, value) {
|
||||||
|
if (target[key] === value) return true;
|
||||||
|
|
||||||
|
Reflect.set(target, key, value);
|
||||||
|
const setPath = `${path}${path && "."}${key}`;
|
||||||
|
|
||||||
|
self.globalListeners.forEach(cb => cb(value, setPath));
|
||||||
|
self.pathListeners.get(setPath)?.forEach(cb => cb(value));
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the data of the store.
|
||||||
|
* This will update this.store and this.plain (and old references to them will be stale! Avoid storing them in variables)
|
||||||
|
*
|
||||||
|
* Additionally, all global listeners (and those for pathToNotify, if specified) will be called with the new data
|
||||||
|
* @param value New data
|
||||||
|
* @param pathToNotify Optional path to notify instead of globally. Used to transfer path via ipc
|
||||||
|
*/
|
||||||
|
public setData(value: T, pathToNotify?: string) {
|
||||||
|
if (this.readOnly) throw new Error("SettingsStore is read-only");
|
||||||
|
|
||||||
|
this.plain = value;
|
||||||
|
this.store = this.makeProxy(value);
|
||||||
|
|
||||||
|
if (pathToNotify) {
|
||||||
|
let v = value;
|
||||||
|
|
||||||
|
const path = pathToNotify.split(".");
|
||||||
|
for (const p of path) {
|
||||||
|
if (!v) {
|
||||||
|
console.warn(
|
||||||
|
`Settings#setData: Path ${pathToNotify} does not exist in new data. Not dispatching update`
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
v = v[p];
|
||||||
|
}
|
||||||
|
|
||||||
|
this.pathListeners.get(pathToNotify)?.forEach(cb => cb(v));
|
||||||
|
}
|
||||||
|
|
||||||
|
this.markAsChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a global change listener, that will fire whenever any setting is changed
|
||||||
|
*
|
||||||
|
* @param data The new data. This is either the new value set on the path, or the new root object if it was changed
|
||||||
|
* @param path The path of the setting that was changed. Empty string if the root object was changed
|
||||||
|
*/
|
||||||
|
public addGlobalChangeListener(cb: (data: any, path: string) => void) {
|
||||||
|
this.globalListeners.add(cb);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a scoped change listener that will fire whenever a setting matching the specified path is changed.
|
||||||
|
*
|
||||||
|
* For example if path is `"foo.bar"`, the listener will fire on
|
||||||
|
* ```js
|
||||||
|
* Setting.store.foo.bar = "hi"
|
||||||
|
* ```
|
||||||
|
* but not on
|
||||||
|
* ```js
|
||||||
|
* Setting.store.foo.baz = "hi"
|
||||||
|
* ```
|
||||||
|
* @param path
|
||||||
|
* @param cb
|
||||||
|
*/
|
||||||
|
public addChangeListener<P extends LiteralUnion<keyof T, string>>(
|
||||||
|
path: P,
|
||||||
|
cb: (data: ResolvePropDeep<T, P>) => void
|
||||||
|
) {
|
||||||
|
const listeners = this.pathListeners.get(path as string) ?? new Set();
|
||||||
|
listeners.add(cb);
|
||||||
|
this.pathListeners.set(path as string, listeners);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove a global listener
|
||||||
|
* @see {@link addGlobalChangeListener}
|
||||||
|
*/
|
||||||
|
public removeGlobalChangeListener(cb: (data: any, path: string) => void) {
|
||||||
|
this.globalListeners.delete(cb);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove a scoped listener
|
||||||
|
* @see {@link addChangeListener}
|
||||||
|
*/
|
||||||
|
public removeChangeListener(path: LiteralUnion<keyof T, string>, cb: (data: any) => void) {
|
||||||
|
const listeners = this.pathListeners.get(path as string);
|
||||||
|
if (!listeners) return;
|
||||||
|
|
||||||
|
listeners.delete(cb);
|
||||||
|
if (!listeners.size) this.pathListeners.delete(path as string);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Call all global change listeners
|
||||||
|
*/
|
||||||
|
public markAsChanged() {
|
||||||
|
this.globalListeners.forEach(cb => cb(this.plain, ""));
|
||||||
|
}
|
||||||
|
}
|
|
@ -16,7 +16,7 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { addSettingsListener, Settings } from "@api/Settings";
|
import { Settings, SettingsStore } from "@api/Settings";
|
||||||
|
|
||||||
|
|
||||||
let style: HTMLStyleElement;
|
let style: HTMLStyleElement;
|
||||||
|
@ -81,10 +81,10 @@ document.addEventListener("DOMContentLoaded", () => {
|
||||||
initThemes();
|
initThemes();
|
||||||
|
|
||||||
toggle(Settings.useQuickCss);
|
toggle(Settings.useQuickCss);
|
||||||
addSettingsListener("useQuickCss", toggle);
|
SettingsStore.addChangeListener("useQuickCss", toggle);
|
||||||
|
|
||||||
addSettingsListener("themeLinks", initThemes);
|
SettingsStore.addChangeListener("themeLinks", initThemes);
|
||||||
addSettingsListener("enabledThemes", initThemes);
|
SettingsStore.addChangeListener("enabledThemes", initThemes);
|
||||||
|
|
||||||
if (!IS_WEB)
|
if (!IS_WEB)
|
||||||
VencordNative.quickCss.addThemeChangeListener(initThemes);
|
VencordNative.quickCss.addThemeChangeListener(initThemes);
|
||||||
|
|
|
@ -36,14 +36,14 @@ export async function importSettings(data: string) {
|
||||||
|
|
||||||
if ("settings" in parsed && "quickCss" in parsed) {
|
if ("settings" in parsed && "quickCss" in parsed) {
|
||||||
Object.assign(PlainSettings, parsed.settings);
|
Object.assign(PlainSettings, parsed.settings);
|
||||||
await VencordNative.settings.set(JSON.stringify(parsed.settings, null, 4));
|
await VencordNative.settings.set(parsed.settings);
|
||||||
await VencordNative.quickCss.set(parsed.quickCss);
|
await VencordNative.quickCss.set(parsed.quickCss);
|
||||||
} else
|
} else
|
||||||
throw new Error("Invalid Settings. Is this even a Vencord Settings file?");
|
throw new Error("Invalid Settings. Is this even a Vencord Settings file?");
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function exportSettings({ minify }: { minify?: boolean; } = {}) {
|
export async function exportSettings({ minify }: { minify?: boolean; } = {}) {
|
||||||
const settings = JSON.parse(VencordNative.settings.get());
|
const settings = VencordNative.settings.get();
|
||||||
const quickCss = await VencordNative.quickCss.get();
|
const quickCss = await VencordNative.quickCss.get();
|
||||||
return JSON.stringify({ settings, quickCss }, null, minify ? undefined : 4);
|
return JSON.stringify({ settings, quickCss }, null, minify ? undefined : 4);
|
||||||
}
|
}
|
||||||
|
@ -137,7 +137,7 @@ export async function putCloudSettings(manual?: boolean) {
|
||||||
|
|
||||||
const { written } = await res.json();
|
const { written } = await res.json();
|
||||||
PlainSettings.cloud.settingsSyncVersion = written;
|
PlainSettings.cloud.settingsSyncVersion = written;
|
||||||
VencordNative.settings.set(JSON.stringify(PlainSettings, null, 4));
|
VencordNative.settings.set(PlainSettings);
|
||||||
|
|
||||||
cloudSettingsLogger.info("Settings uploaded to cloud successfully");
|
cloudSettingsLogger.info("Settings uploaded to cloud successfully");
|
||||||
|
|
||||||
|
@ -222,7 +222,7 @@ export async function getCloudSettings(shouldNotify = true, force = false) {
|
||||||
|
|
||||||
// sync with server timestamp instead of local one
|
// sync with server timestamp instead of local one
|
||||||
PlainSettings.cloud.settingsSyncVersion = written;
|
PlainSettings.cloud.settingsSyncVersion = written;
|
||||||
VencordNative.settings.set(JSON.stringify(PlainSettings, null, 4));
|
VencordNative.settings.set(PlainSettings);
|
||||||
|
|
||||||
cloudSettingsLogger.info("Settings loaded from cloud successfully");
|
cloudSettingsLogger.info("Settings loaded from cloud successfully");
|
||||||
if (shouldNotify)
|
if (shouldNotify)
|
||||||
|
|
|
@ -11,7 +11,7 @@
|
||||||
"esnext.asynciterable",
|
"esnext.asynciterable",
|
||||||
"esnext.symbol"
|
"esnext.symbol"
|
||||||
],
|
],
|
||||||
"module": "commonjs",
|
"module": "esnext",
|
||||||
"moduleResolution": "node",
|
"moduleResolution": "node",
|
||||||
"strict": true,
|
"strict": true,
|
||||||
"noImplicitAny": false,
|
"noImplicitAny": false,
|
||||||
|
@ -20,13 +20,15 @@
|
||||||
|
|
||||||
"baseUrl": "./src/",
|
"baseUrl": "./src/",
|
||||||
"paths": {
|
"paths": {
|
||||||
|
"@main/*": ["./main/*"],
|
||||||
"@api/*": ["./api/*"],
|
"@api/*": ["./api/*"],
|
||||||
"@components/*": ["./components/*"],
|
"@components/*": ["./components/*"],
|
||||||
"@utils/*": ["./utils/*"],
|
"@utils/*": ["./utils/*"],
|
||||||
|
"@shared/*": ["./shared/*"],
|
||||||
"@webpack/types": ["./webpack/common/types"],
|
"@webpack/types": ["./webpack/common/types"],
|
||||||
"@webpack/common": ["./webpack/common"],
|
"@webpack/common": ["./webpack/common"],
|
||||||
"@webpack": ["./webpack/webpack"]
|
"@webpack": ["./webpack/webpack"]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"include": ["src/**/*"]
|
"include": ["src/**/*", "browser/**/*", "scripts/**/*"]
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue