mirror of
https://github.com/System-End/Vencord.git
synced 2026-04-19 18:35:13 +00:00
useSettings: add prefix/wildcard settings path matching (#3783)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
parent
f87a513150
commit
52e76a9cfc
4 changed files with 65 additions and 11 deletions
|
|
@ -183,8 +183,21 @@ export function useSettings(paths?: UseSettings<Settings>[]) {
|
|||
|
||||
useEffect(() => {
|
||||
if (paths) {
|
||||
paths.forEach(p => SettingsStore.addChangeListener(p, forceUpdate));
|
||||
return () => paths.forEach(p => SettingsStore.removeChangeListener(p, forceUpdate));
|
||||
paths.forEach(p => {
|
||||
if (p.endsWith(".*")) {
|
||||
SettingsStore.addPrefixChangeListener(p.slice(0, -2), forceUpdate);
|
||||
} else {
|
||||
SettingsStore.addChangeListener(p, forceUpdate);
|
||||
}
|
||||
});
|
||||
|
||||
return () => paths.forEach(p => {
|
||||
if (p.endsWith(".*")) {
|
||||
SettingsStore.removePrefixChangeListener(p.slice(0, -2), forceUpdate);
|
||||
} else {
|
||||
SettingsStore.removeChangeListener(p, forceUpdate);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
SettingsStore.addGlobalChangeListener(forceUpdate);
|
||||
return () => SettingsStore.removeGlobalChangeListener(forceUpdate);
|
||||
|
|
@ -234,9 +247,11 @@ export function definePluginSettings<
|
|||
if (!definedSettings.pluginName) throw new Error("Cannot access settings before plugin is initialized");
|
||||
return PlainSettings.plugins[definedSettings.pluginName] as any;
|
||||
},
|
||||
use: settings => useSettings(
|
||||
settings?.map(name => `plugins.${definedSettings.pluginName}.${name}`) as UseSettings<Settings>[]
|
||||
).plugins[definedSettings.pluginName] as any,
|
||||
use: settings => useSettings((
|
||||
settings
|
||||
? settings.map(name => `plugins.${definedSettings.pluginName}.${name}`)
|
||||
: [`plugins.${definedSettings.pluginName}.*`]
|
||||
) as UseSettings<Settings>[]).plugins[definedSettings.pluginName] as any,
|
||||
def,
|
||||
checks: checks ?? {} as any,
|
||||
pluginName: "",
|
||||
|
|
@ -256,7 +271,7 @@ type ResolveUseSettings<T extends object> = {
|
|||
Key extends string
|
||||
? T[Key] extends Record<string, unknown>
|
||||
// @ts-expect-error "Type instantiation is excessively deep and possibly infinite"
|
||||
? UseSettings<T[Key]> extends string ? `${Key}.${UseSettings<T[Key]>}` : never
|
||||
? `${Key}.*` | (ResolveUseSettings<T[Key]> extends Record<string, string> ? `${Key}.${ResolveUseSettings<T[Key]>[keyof T[Key]]}` : never)
|
||||
: Key
|
||||
: never;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -69,7 +69,7 @@ function makeDummyUser(user: { username: string; id?: string; avatar?: string; }
|
|||
}
|
||||
|
||||
export default function PluginModal({ plugin, onRestartNeeded, onClose, transitionState }: PluginModalProps) {
|
||||
const pluginSettings = useSettings().plugins[plugin.name];
|
||||
const pluginSettings = useSettings([`plugins.${plugin.name}.*`]).plugins[plugin.name];
|
||||
const hasSettings = Boolean(pluginSettings && plugin.options && !isObjectEmpty(plugin.options));
|
||||
|
||||
const [authors, setAuthors] = useState<Partial<User>[]>([]);
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ export function openNotificationSettingsModal() {
|
|||
}
|
||||
|
||||
function NotificationSettings() {
|
||||
const settings = useSettings().notifications;
|
||||
const settings = useSettings(["notifications.*"]).notifications;
|
||||
|
||||
return (
|
||||
<div style={{ padding: "1em 0" }}>
|
||||
|
|
|
|||
|
|
@ -10,7 +10,12 @@ export const SYM_IS_PROXY = Symbol("SettingsStore.isProxy");
|
|||
export const SYM_GET_RAW_TARGET = Symbol("SettingsStore.getRawTarget");
|
||||
|
||||
// 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}`
|
||||
type ResolvePropDeep<T, P> =
|
||||
P extends `${infer Pre}.*` ?
|
||||
Pre extends keyof T
|
||||
? T[Pre][keyof T[Pre]]
|
||||
: any
|
||||
: P extends `${infer Pre}.${infer Suf}`
|
||||
? Pre extends keyof T
|
||||
? ResolvePropDeep<T[Pre], Suf>
|
||||
: any
|
||||
|
|
@ -42,6 +47,7 @@ interface ProxyContext<T extends object = any> {
|
|||
*/
|
||||
export class SettingsStore<T extends object> {
|
||||
private pathListeners = new Map<string, Set<(newData: any) => void>>();
|
||||
private prefixListeners = new Map<string, Set<(newData: any, path: string) => void>>();
|
||||
private globalListeners = new Set<(newData: T, path: string) => void>();
|
||||
private readonly proxyContexts = new WeakMap<any, ProxyContext<T>>();
|
||||
|
||||
|
|
@ -152,6 +158,13 @@ export class SettingsStore<T extends object> {
|
|||
return new Proxy(object, this.proxyHandler);
|
||||
}
|
||||
|
||||
private notifyPrefixListeners(pathString: string, pathElements: string[], value: any) {
|
||||
for (let i = 1; i <= pathElements.length; i++) {
|
||||
const prefix = pathElements.slice(0, i).join(".");
|
||||
this.prefixListeners.get(prefix)?.forEach(cb => cb(value, pathString));
|
||||
}
|
||||
}
|
||||
|
||||
private notifyListeners(pathStr: string, value: any, root: T) {
|
||||
const paths = pathStr.split(".");
|
||||
|
||||
|
|
@ -172,6 +185,7 @@ export class SettingsStore<T extends object> {
|
|||
}
|
||||
|
||||
this.pathListeners.get(pathStr)?.forEach(cb => cb(value));
|
||||
this.notifyPrefixListeners(pathStr, paths, value);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -203,6 +217,7 @@ export class SettingsStore<T extends object> {
|
|||
}
|
||||
|
||||
this.pathListeners.get(pathToNotify)?.forEach(cb => cb(v));
|
||||
this.notifyPrefixListeners(pathToNotify, path, v);
|
||||
}
|
||||
|
||||
this.markAsChanged();
|
||||
|
|
@ -229,8 +244,6 @@ export class SettingsStore<T extends object> {
|
|||
* ```js
|
||||
* Setting.store.foo.baz = "hi"
|
||||
* ```
|
||||
* @param path
|
||||
* @param cb
|
||||
*/
|
||||
public addChangeListener<P extends LiteralUnion<keyof T, string>>(
|
||||
path: P,
|
||||
|
|
@ -241,6 +254,20 @@ export class SettingsStore<T extends object> {
|
|||
this.pathListeners.set(path as string, listeners);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a prefix change listener that will fire whenever a setting matching the specified prefix is changed.
|
||||
* For example if prefix is `"foo"`, the listener will fire on
|
||||
* ```js
|
||||
* Setting.store.foo.bar = "hi"
|
||||
* Setting.store.foo.baz = "hi"
|
||||
* ```
|
||||
*/
|
||||
public addPrefixChangeListener<P extends string>(prefix: P, cb: (data: ResolvePropDeep<T, P>, path: string) => void) {
|
||||
const listeners = this.prefixListeners.get(prefix) ?? new Set();
|
||||
listeners.add(cb);
|
||||
this.prefixListeners.set(prefix, listeners);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a global listener
|
||||
* @see {@link addGlobalChangeListener}
|
||||
|
|
@ -261,6 +288,18 @@ export class SettingsStore<T extends object> {
|
|||
if (!listeners.size) this.pathListeners.delete(path as string);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a prefix listener
|
||||
* @see {@link addPrefixChangeListener}
|
||||
*/
|
||||
public removePrefixChangeListener(prefix: string, cb: (data: any, path: string) => void) {
|
||||
const listeners = this.prefixListeners.get(prefix);
|
||||
if (!listeners) return;
|
||||
|
||||
listeners.delete(cb);
|
||||
if (!listeners.size) this.prefixListeners.delete(prefix);
|
||||
}
|
||||
|
||||
/**
|
||||
* Call all global change listeners
|
||||
*/
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue