Make Settings & Settings Page

This commit is contained in:
Vendicated 2022-08-31 04:07:16 +02:00
parent cb288e204d
commit 98cb301df5
No known key found for this signature in database
GPG key ID: EC781ADFB93EFFA3
18 changed files with 330 additions and 43 deletions

22
src/utils/IpcEvents.ts Normal file
View file

@ -0,0 +1,22 @@
type Enum<T extends Record<string, string>> = {
[k in keyof T]: T[k];
} & { [v in keyof T as T[v]]: v; };
function strEnum<T extends Record<string, string>>(obj: T): T {
const o = {} as T;
for (const key in obj) {
o[key] = obj[key] as any;
o[obj[key]] = key as any;
};
return o;
}
export default strEnum({
QUICK_CSS_UPDATE: "VencordQuickCssUpdate",
GET_QUICK_CSS: "VencordGetQuickCss",
GET_SETTINGS_DIR: "VencordGetSettingsDir",
GET_SETTINGS: "VencordGetSettings",
SET_SETTINGS: "VencordSetSettings",
OPEN_EXTERNAL: "VencordOpenExternal",
OPEN_PATH: "VencordOpenPath",
} as const);

View file

@ -1,3 +0,0 @@
export const IPC_QUICK_CSS_UPDATE = "VencordQuickCssUpdate";
export const IPC_GET_QUICK_CSS = "VencordGetQuickCss";
export const IPC_GET_SETTINGS_DIR = "VencordGetSettingsDir";

61
src/utils/misc.tsx Normal file
View file

@ -0,0 +1,61 @@
import { React } from "../webpack";
/**
* Makes a lazy function. On first call, the value is computed.
* On subsequent calls, the same computed value will be returned
* @param factory Factory function
*/
export function lazy<T>(factory: () => T): () => T {
let cache: T;
return () => {
return cache ?? (cache = factory());
};
}
/**
* Await a promise
* @param factory Factory
* @param fallbackValue The fallback value that will be used until the promise resolved
* @returns A state that will either be null or the result of the promise
*/
export function useAwaiter<T>(factory: () => Promise<T>, fallbackValue: T | null = null): T | null {
const [res, setRes] = React.useState<T | null>(fallbackValue);
React.useEffect(() => {
factory().then(setRes);
}, []);
return res;
}
/**
* A lazy component. The factory method is called on first render. For example useful
* for const Component = LazyComponent(() => findByDisplayName("...").default)
* @param factory Function returning a Component
* @returns Result of factory function
*/
export function LazyComponent<T = any>(factory: () => React.ComponentType<T>) {
return (props: T) => {
const Component = React.useMemo(factory, []);
return <Component {...props} />;
};
}
/**
* Recursively merges defaults into an object and returns the same object
* @param obj Object
* @param defaults Defaults
* @returns obj
*/
export function mergeDefaults<T>(obj: T, defaults: T): T {
for (const key in defaults) {
const v = defaults[key];
if (typeof v === "object" && !Array.isArray(v)) {
obj[key] ??= {} as any;
mergeDefaults(obj[key], v);
} else {
obj[key] ??= v;
}
}
return obj;
}

View file

@ -1,6 +1,6 @@
import { WEBPACK_CHUNK } from './constants';
import Logger from "./logger";
import { _initWebpack } from "./webpack";
import { _initWebpack } from "../webpack";
let webpackChunk: any[];
@ -83,9 +83,13 @@ function patchPush() {
const lastCode = code;
try {
const newCode = code.replace(replacement.match, replacement.replace);
const newMod = (0, eval)(`// Webpack Module ${id} - Patched by ${[...patchedBy].join(", ")}\n${newCode}\n//# sourceURL=WebpackModule${id}`);
code = newCode;
mod = newMod;
if (newCode === code) {
logger.warn(`Patch by ${patch.plugin} had no effect: ${replacement.match}`);
} else {
const newMod = (0, eval)(`// Webpack Module ${id} - Patched by ${[...patchedBy].join(", ")}\n${newCode}\n//# sourceURL=WebpackModule${id}`);
code = newCode;
mod = newMod;
}
} catch (err) {
logger.error("Failed to apply patch of", patch.plugin, err);
code = lastCode;

View file

@ -1,6 +1,8 @@
import IpcEvents from "./IpcEvents";
document.addEventListener("DOMContentLoaded", async () => {
const style = document.createElement("style");
document.head.appendChild(style);
VencordNative.handleQuickCssUpdate((css: string) => style.innerText = css);
style.innerText = await VencordNative.getQuickCss();
VencordNative.ipc.on(IpcEvents.QUICK_CSS_UPDATE, (_, css: string) => style.innerText = css);
style.innerText = await VencordNative.ipc.invoke(IpcEvents.GET_QUICK_CSS);
});

View file

@ -20,6 +20,8 @@ export interface Plugin {
author: string;
start?(): void;
patches?: Patch[];
dependencies?: string[],
required?: boolean;
}
// @ts-ignore lole

View file

@ -1,105 +0,0 @@
import { startAll } from "../plugins";
import Logger from "./logger";
let webpackCache: typeof window.webpackChunkdiscord_app;
export const subscriptions = new Map<FilterFn, CallbackFn>();
export const listeners = new Set<CallbackFn>();
type FilterFn = (mod: any) => boolean;
type CallbackFn = (mod: any) => void;
export let Common: {
React: typeof import("react"),
FluxDispatcher: any;
UserStore: any;
} = {} as any;
export function _initWebpack(instance: typeof window.webpackChunkdiscord_app) {
if (webpackCache !== void 0) throw "no.";
webpackCache = instance.push([[Symbol()], {}, (r) => r.c]);
instance.pop();
// Abandon Hope All Ye Who Enter Here
let started = false;
waitFor("getCurrentUser", x => Common.UserStore = x);
waitFor(["dispatch", "subscribe"], x => {
Common.FluxDispatcher = x;
const cb = () => {
console.info("Connection open");
x.unsubscribe("CONNECTION_OPEN", cb);
startAll();
};
x.subscribe("CONNECTION_OPEN", cb);
});
waitFor("useState", x => Common.React = x);
}
export function find(filter: FilterFn, getDefault = true) {
if (typeof filter !== "function")
throw new Error("Invalid filter. Expected a function got", filter);
for (const key in webpackCache) {
const mod = webpackCache[key];
if (mod?.exports && filter(mod.exports))
return mod.exports;
if (mod?.exports?.default && filter(mod.exports.default))
return getDefault ? mod.exports.default : mod.exports;
}
return null;
}
export function findAll(filter: FilterFn, getDefault = true) {
if (typeof filter !== "function") throw new Error("Invalid filter. Expected a function got", filter);
const ret = [] as any[];
for (const key in webpackCache) {
const mod = webpackCache[key];
if (mod?.exports && filter(mod.exports)) ret.push(mod.exports);
if (mod?.exports?.default && filter(mod.exports.default)) ret.push(getDefault ? mod.exports.default : mod.exports);
}
return ret;
}
export const filters = {
byProps: (props: string[]): FilterFn =>
props.length === 1
? m => m[props[0]] !== void 0
: m => props.every(p => m[p] !== void 0),
byDisplayName: (deezNuts: string): FilterFn => m => m.default?.displayName === deezNuts
};
export function findByProps(...props: string[]) {
return find(filters.byProps(props));
}
export function findAllByProps(...props: string[]) {
return findAll(filters.byProps(props));
}
export function findByDisplayName(deezNuts: string) {
return find(filters.byDisplayName(deezNuts));
}
export function waitFor(filter: string | string[] | FilterFn, callback: CallbackFn) {
if (typeof filter === "string") filter = filters.byProps([filter]);
else if (Array.isArray(filter)) filter = filters.byProps(filter);
else if (typeof filter !== "function") throw new Error("filter must be a string, string[] or function, got", filter);
const existing = find(filter!);
if (existing) return void callback(existing);
subscriptions.set(filter, callback);
}
export function addListener(callback: CallbackFn) {
listeners.add(callback);
}
export function removeListener(callback: CallbackFn) {
listeners.delete(callback);
}