Rewrite settings proxy to proper store

This commit is contained in:
Vendicated 2023-04-10 01:04:41 +02:00
parent ddebb6563a
commit c2eaa9d35a
No known key found for this signature in database
GPG key ID: A1DC0CFB5615D905
8 changed files with 90 additions and 58 deletions

View file

@ -17,7 +17,8 @@
"start": "pnpm build && electron .",
"start:dev": "pnpm build --dev && electron .",
"start:watch": "tsx scripts/startWatch.mts",
"test": "echo \"Error: no test specified\" && exit 1",
"test": "pnpm lint && pnpm testTypes",
"testTypes": "tsc --noEmit",
"watch": "pnpm build --watch"
},
"devDependencies": {

View file

@ -63,7 +63,7 @@ async function createWindows() {
splash.destroy();
mainWin!.show();
if (Settings.maximized) {
if (Settings.store.maximized) {
mainWin!.maximize();
}
});

View file

@ -13,7 +13,7 @@ import { debounce } from "shared/utils/debounce";
import { IpcEvents } from "../shared/IpcEvents";
import { VENCORD_FILES_DIR, VENCORD_QUICKCSS_FILE } from "./constants";
import { mainWin } from "./mainWindow";
import { PlainSettings, setSettings } from "./settings";
import { Settings } from "./settings";
ipcMain.on(IpcEvents.GET_VENCORD_PRELOAD_FILE, e => {
e.returnValue = join(VENCORD_FILES_DIR, "preload.js");
@ -32,7 +32,7 @@ ipcMain.on(IpcEvents.GET_RENDERER_CSS_FILE, e => {
});
ipcMain.on(IpcEvents.GET_SETTINGS, e => {
e.returnValue = PlainSettings;
e.returnValue = Settings.plain;
});
ipcMain.on(IpcEvents.GET_VERSION, e => {
@ -40,7 +40,7 @@ ipcMain.on(IpcEvents.GET_VERSION, e => {
});
ipcMain.handle(IpcEvents.SET_SETTINGS, (_, settings) => {
setSettings(settings);
Settings.setData(settings);
});
ipcMain.handle(IpcEvents.RELAUNCH, () => {

View file

@ -78,7 +78,7 @@ function initTray(win: BrowserWindow) {
function initMenuBar(win: BrowserWindow) {
const isWindows = process.platform === "win32";
const wantCtrlQ = !isWindows || VencordSettings.winCtrlQ;
const wantCtrlQ = !isWindows || VencordSettings.store.winCtrlQ;
const menu = Menu.buildFromTemplate([
{
@ -174,7 +174,7 @@ function initMenuBar(win: BrowserWindow) {
}
function getWindowBoundsOptions(): BrowserWindowConstructorOptions {
const { x, y, width, height } = Settings.windowBounds ?? {};
const { x, y, width, height } = Settings.store.windowBounds ?? {};
const options = {
width: width ?? DEFAULT_WIDTH,
@ -186,7 +186,7 @@ function getWindowBoundsOptions(): BrowserWindowConstructorOptions {
options.y = y;
}
if (!Settings.disableMinSize) {
if (!Settings.store.disableMinSize) {
options.minWidth = MIN_WIDTH;
options.minHeight = MIN_HEIGHT;
}
@ -196,8 +196,8 @@ function getWindowBoundsOptions(): BrowserWindowConstructorOptions {
function initWindowBoundsListeners(win: BrowserWindow) {
const saveState = () => {
Settings.maximized = win.isMaximized();
Settings.minimized = win.isMinimized();
Settings.store.maximized = win.isMaximized();
Settings.store.minimized = win.isMinimized();
};
win.on("maximize", saveState);
@ -205,7 +205,7 @@ function initWindowBoundsListeners(win: BrowserWindow) {
win.on("unmaximize", saveState);
const saveBounds = () => {
Settings.windowBounds = win.getBounds();
Settings.store.windowBounds = win.getBounds();
};
win.on("resize", saveBounds);
@ -224,12 +224,12 @@ export function createMainWindow() {
preload: join(__dirname, "preload.js")
},
icon: ICON_PATH,
frame: VencordSettings.frameless !== true,
frame: VencordSettings.store.frameless !== true,
...getWindowBoundsOptions()
}));
win.on("close", e => {
if (isQuitting || Settings.minimizeToTray === false) return;
if (isQuitting || Settings.store.minimizeToTray === false) return;
e.preventDefault();
win.hide();
@ -243,7 +243,9 @@ export function createMainWindow() {
makeLinksOpenExternally(win);
const subdomain =
Settings.discordBranch === "canary" || Settings.discordBranch === "ptb" ? `${Settings.discordBranch}.` : "";
Settings.store.discordBranch === "canary" || Settings.store.discordBranch === "ptb"
? `${Settings.store.discordBranch}.`
: "";
win.loadURL(`https://${subdomain}discord.com/app`);

View file

@ -7,7 +7,7 @@
import { readFileSync, writeFileSync } from "fs";
import { join } from "path";
import type { Settings as TSettings } from "shared/settings";
import { makeChangeListenerProxy } from "shared/utils/makeChangeListenerProxy";
import { SettingsStore } from "shared/utils/makeChangeListenerProxy";
import { DATA_DIR, VENCORD_SETTINGS_FILE } from "./constants";
@ -24,23 +24,11 @@ function loadSettings<T extends object = any>(file: string, name: string) {
}
} catch {}
const makeSettingsProxy = (settings: T) =>
makeChangeListenerProxy(settings, target => writeFileSync(file, JSON.stringify(target, null, 4)));
const store = new SettingsStore(settings);
store.addGlobalChangeListener(o => writeFileSync(file, JSON.stringify(o, null, 4)));
return [settings, makeSettingsProxy] as const;
return store;
}
// eslint-disable-next-line prefer-const
let [PlainSettings, makeSettingsProxy] = loadSettings<TSettings>(SETTINGS_FILE, "VencordDesktop");
export let Settings = makeSettingsProxy(PlainSettings);
const [PlainVencordSettings, makeVencordSettingsProxy] = loadSettings<any>(VENCORD_SETTINGS_FILE, "Vencord");
export const VencordSettings = makeVencordSettingsProxy(PlainVencordSettings);
export function setSettings(settings: TSettings) {
writeFileSync(SETTINGS_FILE, JSON.stringify(settings, null, 4));
PlainSettings = settings;
Settings = makeSettingsProxy(settings);
}
export { PlainSettings, PlainVencordSettings };
export const Settings = loadSettings<TSettings>(SETTINGS_FILE, "Vencord Desktop");
export const VencordSettings = loadSettings<any>(VENCORD_SETTINGS_FILE, "Vencord");

View file

@ -25,7 +25,7 @@ export function makeLinksOpenExternally(win: BrowserWindow) {
switch (protocol) {
case "http:":
case "https:":
if (Settings.openLinksWithElectron) {
if (Settings.store.openLinksWithElectron) {
return { action: "allow" };
}
// eslint-disable-next-line no-fallthrough

View file

@ -5,26 +5,23 @@
*/
import type { Settings as TSettings } from "shared/settings";
import { makeChangeListenerProxy } from "shared/utils/makeChangeListenerProxy";
import { SettingsStore } from "shared/utils/makeChangeListenerProxy";
import { Common } from "./vencord";
const signals = new Set<() => void>();
export const PlainSettings = VencordDesktopNative.settings.get() as TSettings;
export const Settings = makeChangeListenerProxy(PlainSettings, s => {
VencordDesktopNative.settings.set(s);
signals.forEach(fn => fn());
});
export const Settings = new SettingsStore(PlainSettings);
export function useSettings() {
const [, update] = Common.React.useReducer(x => x + 1, 0);
Common.React.useEffect(() => {
signals.add(update);
return () => signals.delete(update);
Settings.addGlobalChangeListener(update);
return () => Settings.removeGlobalChangeListener(update);
}, []);
return Settings;
return Settings.store;
}
export function getValueAndOnChange(key: keyof TSettings) {

View file

@ -4,23 +4,67 @@
* Copyright (c) 2023 Vendicated and Vencord contributors
*/
export function makeChangeListenerProxy<T extends object>(object: T, onChange: (object: T) => void, _root = object): T {
return new Proxy(object, {
get(target, key) {
const v = target[key];
if (typeof v === "object" && !Array.isArray(v) && v !== null)
return makeChangeListenerProxy(v, onChange, _root);
export class SettingsStore<T extends object> {
public declare store: T;
private globalListeners = new Set<(newData: T) => void>();
private pathListeners = new Map<string, Set<(newData: unknown) => void>>();
return v;
},
public constructor(public plain: T) {
this.store = this.makeProxy(plain);
}
set(target, key, value) {
if (target[key] === value) return true;
private makeProxy(object: any, root: T = object, path: string = "") {
const self = this;
Reflect.set(target, key, value);
onChange(_root);
return new Proxy(object, {
get(target, key: string) {
const v = target[key];
return true;
}
});
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(root));
self.pathListeners.get(setPath)?.forEach(cb => cb(value));
return true;
}
});
}
public setData(value: T) {
this.plain = value;
this.store = this.makeProxy(value);
this.globalListeners.forEach(cb => cb(value));
}
public addGlobalChangeListener(cb: (store: T) => void) {
this.globalListeners.add(cb);
}
public addChangeListener(path: string, cb: (data: any) => void) {
const listeners = this.pathListeners.get(path) ?? new Set();
listeners.add(cb);
this.pathListeners.set(path, listeners);
}
public removeGlobalChangeListener(cb: (store: T) => void) {
this.globalListeners.delete(cb);
}
public removeChangeListener(path: string, cb: (data: any) => void) {
const listeners = this.pathListeners.get(path);
if (!listeners) return;
listeners.delete(cb);
if (!listeners.size) this.pathListeners.delete(path);
}
}