From 0c77dbec92cb6ec13bc127b218be4885d10c3c24 Mon Sep 17 00:00:00 2001 From: Vendicated Date: Mon, 10 Apr 2023 01:43:47 +0200 Subject: [PATCH] Improve SettingsStore, add disableMinSize listener --- src/main/ipc.ts | 4 +- src/main/mainWindow.ts | 7 ++++ src/preload/VencordDesktopNative.ts | 2 +- src/renderer/settings.ts | 1 + src/shared/utils/SettingsStore.ts | 62 ++++++++++++++++++++++------- 5 files changed, 58 insertions(+), 18 deletions(-) diff --git a/src/main/ipc.ts b/src/main/ipc.ts index 9d22673..81db39c 100644 --- a/src/main/ipc.ts +++ b/src/main/ipc.ts @@ -39,8 +39,8 @@ ipcMain.on(IpcEvents.GET_VERSION, e => { e.returnValue = app.getVersion(); }); -ipcMain.handle(IpcEvents.SET_SETTINGS, (_, settings) => { - Settings.setData(settings); +ipcMain.handle(IpcEvents.SET_SETTINGS, (_, settings: typeof Settings.store, path?: string) => { + Settings.setData(settings, path); }); ipcMain.handle(IpcEvents.RELAUNCH, () => { diff --git a/src/main/mainWindow.ts b/src/main/mainWindow.ts index 470079d..d6d8c37 100644 --- a/src/main/mainWindow.ts +++ b/src/main/mainWindow.ts @@ -212,6 +212,12 @@ function initWindowBoundsListeners(win: BrowserWindow) { win.on("move", saveBounds); } +function initSettingsListeners(win: BrowserWindow) { + Settings.addChangeListener("disableMinSize", disable => { + win.setMinimumSize(disable ? 1 : MIN_WIDTH, disable ? 1 : MIN_HEIGHT); + }); +} + export function createMainWindow() { const win = (mainWin = new BrowserWindow({ show: false, @@ -241,6 +247,7 @@ export function createMainWindow() { initTray(win); initMenuBar(win); makeLinksOpenExternally(win); + initSettingsListeners(win); const subdomain = Settings.store.discordBranch === "canary" || Settings.store.discordBranch === "ptb" diff --git a/src/preload/VencordDesktopNative.ts b/src/preload/VencordDesktopNative.ts index a62e5ac..2daaa23 100644 --- a/src/preload/VencordDesktopNative.ts +++ b/src/preload/VencordDesktopNative.ts @@ -29,7 +29,7 @@ export const VencordDesktopNative = { }, settings: { get: () => sendSync(IpcEvents.GET_SETTINGS), - set: (settings: Settings) => invoke(IpcEvents.SET_SETTINGS, settings) + set: (settings: Settings, path?: string) => invoke(IpcEvents.SET_SETTINGS, settings, path) }, win: { focus: () => invoke(IpcEvents.FOCUS) diff --git a/src/renderer/settings.ts b/src/renderer/settings.ts index cae36d4..a60ba40 100644 --- a/src/renderer/settings.ts +++ b/src/renderer/settings.ts @@ -11,6 +11,7 @@ import { Common } from "./vencord"; export const PlainSettings = VencordDesktopNative.settings.get() as TSettings; export const Settings = new SettingsStore(PlainSettings); +Settings.addGlobalChangeListener((o, p) => VencordDesktopNative.settings.set(o, p)); export function useSettings() { const [, update] = Common.React.useReducer(x => x + 1, 0); diff --git a/src/shared/utils/SettingsStore.ts b/src/shared/utils/SettingsStore.ts index 1daae2d..bedeecd 100644 --- a/src/shared/utils/SettingsStore.ts +++ b/src/shared/utils/SettingsStore.ts @@ -4,13 +4,24 @@ * Copyright (c) 2023 Vendicated and Vencord contributors */ +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 = P extends `${infer Pre}.${infer Suf}` + ? Pre extends keyof T + ? ResolvePropDeep + : any + : P extends keyof T + ? T[P] + : any; + /** * The SettingsStore allows you to easily create a mutable store that * has support for global and path-based change listeners. */ export class SettingsStore { - private pathListeners = new Map void>>(); - private globalListeners = new Set<(newData: T) => void>(); + private pathListeners = new Map void>>(); + private globalListeners = new Set<(newData: T, path: string) => void>(); /** * The store object. Making changes to this object will trigger the applicable change listeners @@ -44,7 +55,7 @@ export class SettingsStore { Reflect.set(target, key, value); const setPath = `${path}${path && "."}${key}`; - self.globalListeners.forEach(cb => cb(root)); + self.globalListeners.forEach(cb => cb(root, setPath)); self.pathListeners.get(setPath)?.forEach(cb => cb(value)); return true; @@ -56,20 +67,38 @@ export class SettingsStore { * 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 will be called with the new data - * @param value + * Additionally, all global listeners (or 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) { + public setData(value: T, pathToNotify?: string) { this.plain = value; this.store = this.makeProxy(value); - this.globalListeners.forEach(cb => cb(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)); + } else { + this.globalListeners.forEach(cb => cb(value, "")); + } } /** * Add a global change listener, that will fire whenever any setting is changed */ - public addGlobalChangeListener(cb: (store: T) => void) { + public addGlobalChangeListener(cb: (data: T, path: string) => void) { this.globalListeners.add(cb); } @@ -87,17 +116,20 @@ export class SettingsStore { * @param path * @param cb */ - public addChangeListener(path: string, cb: (data: any) => void) { - const listeners = this.pathListeners.get(path) ?? new Set(); + public addChangeListener

>( + path: P, + cb: (data: ResolvePropDeep) => void + ) { + const listeners = this.pathListeners.get(path as string) ?? new Set(); listeners.add(cb); - this.pathListeners.set(path, listeners); + this.pathListeners.set(path as string, listeners); } /** * Remove a global listener * @see {@link addGlobalChangeListener} */ - public removeGlobalChangeListener(cb: (store: T) => void) { + public removeGlobalChangeListener(cb: (data: T, path: string) => void) { this.globalListeners.delete(cb); } @@ -105,11 +137,11 @@ export class SettingsStore { * Remove a scoped listener * @see {@link addChangeListener} */ - public removeChangeListener(path: string, cb: (data: any) => void) { - const listeners = this.pathListeners.get(path); + public removeChangeListener(path: LiteralUnion, 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); + if (!listeners.size) this.pathListeners.delete(path as string); } }