Improve SettingsStore, add disableMinSize listener

This commit is contained in:
Vendicated 2023-04-10 01:43:47 +02:00
parent edfeca15ce
commit 0c77dbec92
No known key found for this signature in database
GPG key ID: A1DC0CFB5615D905
5 changed files with 58 additions and 18 deletions

View file

@ -39,8 +39,8 @@ ipcMain.on(IpcEvents.GET_VERSION, e => {
e.returnValue = app.getVersion(); e.returnValue = app.getVersion();
}); });
ipcMain.handle(IpcEvents.SET_SETTINGS, (_, settings) => { ipcMain.handle(IpcEvents.SET_SETTINGS, (_, settings: typeof Settings.store, path?: string) => {
Settings.setData(settings); Settings.setData(settings, path);
}); });
ipcMain.handle(IpcEvents.RELAUNCH, () => { ipcMain.handle(IpcEvents.RELAUNCH, () => {

View file

@ -212,6 +212,12 @@ function initWindowBoundsListeners(win: BrowserWindow) {
win.on("move", saveBounds); 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() { export function createMainWindow() {
const win = (mainWin = new BrowserWindow({ const win = (mainWin = new BrowserWindow({
show: false, show: false,
@ -241,6 +247,7 @@ export function createMainWindow() {
initTray(win); initTray(win);
initMenuBar(win); initMenuBar(win);
makeLinksOpenExternally(win); makeLinksOpenExternally(win);
initSettingsListeners(win);
const subdomain = const subdomain =
Settings.store.discordBranch === "canary" || Settings.store.discordBranch === "ptb" Settings.store.discordBranch === "canary" || Settings.store.discordBranch === "ptb"

View file

@ -29,7 +29,7 @@ export const VencordDesktopNative = {
}, },
settings: { settings: {
get: () => sendSync<Settings>(IpcEvents.GET_SETTINGS), get: () => sendSync<Settings>(IpcEvents.GET_SETTINGS),
set: (settings: Settings) => invoke<void>(IpcEvents.SET_SETTINGS, settings) set: (settings: Settings, path?: string) => invoke<void>(IpcEvents.SET_SETTINGS, settings, path)
}, },
win: { win: {
focus: () => invoke<void>(IpcEvents.FOCUS) focus: () => invoke<void>(IpcEvents.FOCUS)

View file

@ -11,6 +11,7 @@ import { Common } from "./vencord";
export const PlainSettings = VencordDesktopNative.settings.get() as TSettings; export const PlainSettings = VencordDesktopNative.settings.get() as TSettings;
export const Settings = new SettingsStore(PlainSettings); export const Settings = new SettingsStore(PlainSettings);
Settings.addGlobalChangeListener((o, p) => VencordDesktopNative.settings.set(o, p));
export function useSettings() { export function useSettings() {
const [, update] = Common.React.useReducer(x => x + 1, 0); const [, update] = Common.React.useReducer(x => x + 1, 0);

View file

@ -4,13 +4,24 @@
* Copyright (c) 2023 Vendicated and Vencord contributors * 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<T, P> = P extends `${infer Pre}.${infer Suf}`
? Pre extends keyof T
? ResolvePropDeep<T[Pre], Suf>
: any
: P extends keyof T
? T[P]
: any;
/** /**
* The SettingsStore allows you to easily create a mutable store that * The SettingsStore allows you to easily create a mutable store that
* has support for global and path-based change listeners. * has support for global and path-based change listeners.
*/ */
export class SettingsStore<T extends object> { export class SettingsStore<T extends object> {
private pathListeners = new Map<string, Set<(newData: unknown) => void>>(); private pathListeners = new Map<string, Set<(newData: any) => void>>();
private globalListeners = new Set<(newData: T) => void>(); private globalListeners = new Set<(newData: T, path: string) => void>();
/** /**
* The store object. Making changes to this object will trigger the applicable change listeners * The store object. Making changes to this object will trigger the applicable change listeners
@ -44,7 +55,7 @@ export class SettingsStore<T extends object> {
Reflect.set(target, key, value); Reflect.set(target, key, value);
const setPath = `${path}${path && "."}${key}`; 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)); self.pathListeners.get(setPath)?.forEach(cb => cb(value));
return true; return true;
@ -56,20 +67,38 @@ export class SettingsStore<T extends object> {
* Set the data of the store. * 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) * 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 * Additionally, all global listeners (or those for pathToNotify, if specified) will be called with the new data
* @param value * @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.plain = value;
this.store = this.makeProxy(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 * 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); this.globalListeners.add(cb);
} }
@ -87,17 +116,20 @@ export class SettingsStore<T extends object> {
* @param path * @param path
* @param cb * @param cb
*/ */
public addChangeListener(path: string, cb: (data: any) => void) { public addChangeListener<P extends LiteralUnion<keyof T, string>>(
const listeners = this.pathListeners.get(path) ?? new Set(); path: P,
cb: (data: ResolvePropDeep<T, P>) => void
) {
const listeners = this.pathListeners.get(path as string) ?? new Set();
listeners.add(cb); listeners.add(cb);
this.pathListeners.set(path, listeners); this.pathListeners.set(path as string, listeners);
} }
/** /**
* Remove a global listener * Remove a global listener
* @see {@link addGlobalChangeListener} * @see {@link addGlobalChangeListener}
*/ */
public removeGlobalChangeListener(cb: (store: T) => void) { public removeGlobalChangeListener(cb: (data: T, path: string) => void) {
this.globalListeners.delete(cb); this.globalListeners.delete(cb);
} }
@ -105,11 +137,11 @@ export class SettingsStore<T extends object> {
* Remove a scoped listener * Remove a scoped listener
* @see {@link addChangeListener} * @see {@link addChangeListener}
*/ */
public removeChangeListener(path: string, cb: (data: any) => void) { public removeChangeListener(path: LiteralUnion<keyof T, string>, cb: (data: any) => void) {
const listeners = this.pathListeners.get(path); const listeners = this.pathListeners.get(path as string);
if (!listeners) return; if (!listeners) return;
listeners.delete(cb); listeners.delete(cb);
if (!listeners.size) this.pathListeners.delete(path); if (!listeners.size) this.pathListeners.delete(path as string);
} }
} }