parent
cb288e204d
commit
98cb301df5
@ -1,12 +1,33 @@
|
||||
import { IPC_QUICK_CSS_UPDATE, IPC_GET_QUICK_CSS } from './utils/ipcEvents';
|
||||
import { ipcRenderer } from 'electron';
|
||||
import IPC_EVENTS from './utils/IpcEvents';
|
||||
import { IpcRenderer, ipcRenderer } from 'electron';
|
||||
|
||||
export default {
|
||||
handleQuickCssUpdate(cb: (s: string) => void) {
|
||||
ipcRenderer.on(IPC_QUICK_CSS_UPDATE, (_, css) => {
|
||||
cb(css);
|
||||
});
|
||||
getVersions: () => process.versions,
|
||||
ipc: {
|
||||
send(event: string, ...args: any[]) {
|
||||
if (event in IPC_EVENTS) ipcRenderer.send(event, ...args);
|
||||
else throw new Error(`Event ${event} not allowed.`);
|
||||
},
|
||||
sendSync(event: string, ...args: any[]) {
|
||||
if (event in IPC_EVENTS) return ipcRenderer.sendSync(event, ...args);
|
||||
else throw new Error(`Event ${event} not allowed.`);
|
||||
},
|
||||
on(event: string, listener: Parameters<IpcRenderer["on"]>[1]) {
|
||||
if (event in IPC_EVENTS) ipcRenderer.on(event, listener);
|
||||
else throw new Error(`Event ${event} not allowed.`);
|
||||
},
|
||||
invoke(event: string, ...args: any[]) {
|
||||
if (event in IPC_EVENTS) return ipcRenderer.invoke(event, ...args);
|
||||
else throw new Error(`Event ${event} not allowed.`);
|
||||
}
|
||||
},
|
||||
getQuickCss: () => ipcRenderer.invoke(IPC_GET_QUICK_CSS) as Promise<string>,
|
||||
getVersions: () => process.versions
|
||||
require(mod: string) {
|
||||
const settings = ipcRenderer.sendSync(IPC_EVENTS.GET_SETTINGS);
|
||||
try {
|
||||
if (!JSON.parse(settings).unsafeRequire) throw "no";
|
||||
} catch {
|
||||
throw new Error("Unsafe require is not allowed. Enable it in settings and try again.");
|
||||
}
|
||||
return require(mod);
|
||||
}
|
||||
};
|
@ -0,0 +1,81 @@
|
||||
import plugins from "plugins";
|
||||
import IpcEvents from "../utils/IpcEvents";
|
||||
import { React } from "../webpack";
|
||||
import { mergeDefaults } from '../utils/misc';
|
||||
|
||||
interface Settings {
|
||||
unsafeRequire: boolean;
|
||||
plugins: {
|
||||
[plugin: string]: {
|
||||
enabled: boolean;
|
||||
[setting: string]: any;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
const DefaultSettings: Settings = {
|
||||
unsafeRequire: false,
|
||||
plugins: {}
|
||||
};
|
||||
|
||||
for (const plugin of plugins) {
|
||||
DefaultSettings.plugins[plugin.name] = {
|
||||
enabled: plugin.required ?? false
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
var settings = JSON.parse(VencordNative.ipc.sendSync(IpcEvents.GET_SETTINGS)) as Settings;
|
||||
for (const key in DefaultSettings) {
|
||||
settings[key] ??= DefaultSettings[key];
|
||||
}
|
||||
mergeDefaults(settings, DefaultSettings);
|
||||
} catch (err) {
|
||||
console.error("Corrupt settings file. ", err);
|
||||
var settings = mergeDefaults({} as Settings, DefaultSettings);
|
||||
}
|
||||
|
||||
const subscriptions = new Set<() => void>();
|
||||
|
||||
function makeProxy(settings: Settings, root = settings): Settings {
|
||||
return new Proxy(settings, {
|
||||
get(target, p) {
|
||||
const v = target[p];
|
||||
if (typeof v === "object" && !Array.isArray(v)) return makeProxy(v, root);
|
||||
return v;
|
||||
},
|
||||
set(target, p, v) {
|
||||
if (target[p] === v) return true;
|
||||
|
||||
target[p] = v;
|
||||
for (const subscription of subscriptions) {
|
||||
subscription();
|
||||
}
|
||||
VencordNative.ipc.invoke(IpcEvents.SET_SETTINGS, JSON.stringify(root));
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* A smart settings object. Altering props automagically saves
|
||||
* the updated settings to disk.
|
||||
*/
|
||||
export const Settings = makeProxy(settings);
|
||||
|
||||
/**
|
||||
* Settings hook for React components. Returns a smart settings
|
||||
* object that automagically triggers a rerender if any properties
|
||||
* are altered
|
||||
* @returns Settings
|
||||
*/
|
||||
export function useSettings() {
|
||||
const [, forceUpdate] = React.useReducer(x => ({}), {});
|
||||
|
||||
React.useEffect(() => {
|
||||
subscriptions.add(forceUpdate);
|
||||
return () => void subscriptions.delete(forceUpdate);
|
||||
}, []);
|
||||
|
||||
return Settings;
|
||||
}
|
@ -1,4 +1,84 @@
|
||||
import { lazy, LazyComponent, useAwaiter } from "../utils/misc";
|
||||
import { findByDisplayName, Forms } from '../webpack';
|
||||
import Plugins from 'plugins';
|
||||
import { useSettings } from "../api/settings";
|
||||
import { findByProps } from '../webpack/index';
|
||||
import IpcEvents from "../utils/IpcEvents";
|
||||
|
||||
// Lazy spam because this is ran before React is a thing. Todo: Fix that and clean this up lmao
|
||||
|
||||
const SwitchItem = LazyComponent<React.PropsWithChildren<{
|
||||
value: boolean;
|
||||
onChange: (v: boolean) => void;
|
||||
note?: string;
|
||||
tooltipNote?: string;
|
||||
disabled?: boolean;
|
||||
}>>(() => findByDisplayName("SwitchItem").default);
|
||||
|
||||
const getButton = lazy(() => findByProps("ButtonLooks", "default"));
|
||||
const Button = LazyComponent(() => getButton().default);
|
||||
const getFlex = lazy(() => findByDisplayName("Flex"));
|
||||
const Flex = LazyComponent(() => getFlex().default);
|
||||
const FlexChild = LazyComponent(() => getFlex().default.Child);
|
||||
const getMargins = lazy(() => findByProps("marginTop8", "marginBottom8"));
|
||||
|
||||
export default function Settings(props) {
|
||||
console.log(props);
|
||||
return (<p>Hi</p>);
|
||||
const settingsDir = useAwaiter(() => VencordNative.ipc.invoke(IpcEvents.GET_SETTINGS_DIR), "Loading...");
|
||||
const settings = useSettings();
|
||||
|
||||
return (
|
||||
<Forms.FormSection tag="h1" title="Vencord">
|
||||
<Forms.FormText>SettingsDir: {settingsDir}</Forms.FormText>
|
||||
<Flex className={getMargins().marginTop8 + " " + getMargins().marginBottom8}>
|
||||
<FlexChild>
|
||||
<Button
|
||||
onClick={() => VencordNative.ipc.invoke(IpcEvents.OPEN_PATH, settingsDir)}
|
||||
size={getButton().ButtonSizes.SMALL}
|
||||
disabled={settingsDir === "Loading..."}
|
||||
>
|
||||
Launch Directory
|
||||
</Button>
|
||||
</FlexChild>
|
||||
<FlexChild>
|
||||
<Button
|
||||
onClick={() => VencordNative.ipc.invoke(IpcEvents.OPEN_PATH, settingsDir + "/quickCss.css")}
|
||||
size={getButton().ButtonSizes.SMALL}
|
||||
disabled={settingsDir === "Loading..."}
|
||||
>
|
||||
Open QuickCSS File
|
||||
</Button>
|
||||
</FlexChild>
|
||||
</Flex>
|
||||
<Forms.FormTitle tag="h5">Settings</Forms.FormTitle>
|
||||
<SwitchItem
|
||||
value={settings.unsafeRequire}
|
||||
onChange={v => settings.unsafeRequire = v}
|
||||
note="Enables VencordNative.require. Useful for testing, very bad for security. Leave this off unless you need it."
|
||||
>
|
||||
Enable Ensafe Require
|
||||
</SwitchItem>
|
||||
<Forms.FormDivider />
|
||||
<Forms.FormTitle tag="h5">Plugins</Forms.FormTitle>
|
||||
{Plugins.map(p => (
|
||||
<SwitchItem
|
||||
disabled={p.required === true}
|
||||
key={p.name}
|
||||
value={settings.plugins[p.name].enabled}
|
||||
onChange={v => {
|
||||
settings.plugins[p.name].enabled = v;
|
||||
if (v) {
|
||||
p.dependencies?.forEach(d => {
|
||||
settings.plugins[d].enabled = true;
|
||||
});
|
||||
}
|
||||
}}
|
||||
note={p.description}
|
||||
tooltipNote={p.required ? "This plugin is required. Thus you cannot disable it." : undefined}
|
||||
>
|
||||
{p.name}
|
||||
</SwitchItem>
|
||||
))
|
||||
}
|
||||
</Forms.FormSection >
|
||||
);
|
||||
}
|
@ -1,25 +1,39 @@
|
||||
import { app, BrowserWindow, ipcMain } from "electron";
|
||||
import { fstat, watch } from "fs";
|
||||
import { open, readFile } from "fs/promises";
|
||||
import { app, BrowserWindow, ipcMain, shell } from "electron";
|
||||
import { readFileSync, watch } from "fs";
|
||||
import { open, readFile, writeFile } from "fs/promises";
|
||||
import { join } from 'path';
|
||||
import { IPC_GET_SETTINGS_DIR, IPC_GET_QUICK_CSS, IPC_QUICK_CSS_UPDATE } from './utils/ipcEvents';
|
||||
import IpcEvents from './utils/IpcEvents';
|
||||
|
||||
const DATA_DIR = join(app.getPath("userData"), "..", "Vencord");
|
||||
const SETTINGS_DIR = join(DATA_DIR, "settings");
|
||||
const QUICKCSS_PATH = join(SETTINGS_DIR, "quickCss.css");
|
||||
const SETTINGS_FILE = join(SETTINGS_DIR, "settings.json");
|
||||
|
||||
function readCss() {
|
||||
return readFile(QUICKCSS_PATH, "utf-8").catch(() => "");
|
||||
}
|
||||
|
||||
ipcMain.handle(IPC_GET_SETTINGS_DIR, () => SETTINGS_DIR);
|
||||
ipcMain.handle(IPC_GET_QUICK_CSS, () => readCss());
|
||||
function readSettings() {
|
||||
try {
|
||||
return readFileSync(SETTINGS_FILE, "utf-8");
|
||||
} catch {
|
||||
return "{}";
|
||||
}
|
||||
}
|
||||
|
||||
ipcMain.handle(IpcEvents.GET_SETTINGS_DIR, () => SETTINGS_DIR);
|
||||
ipcMain.handle(IpcEvents.GET_QUICK_CSS, () => readCss());
|
||||
// .on because we need Settings synchronously (ipcRenderer.sendSync)
|
||||
ipcMain.on(IpcEvents.GET_SETTINGS, (e) => e.returnValue = readSettings());
|
||||
ipcMain.handle(IpcEvents.SET_SETTINGS, (_, s) => void writeFile(SETTINGS_FILE, s));
|
||||
ipcMain.handle(IpcEvents.OPEN_PATH, (_, path) => shell.openPath(path));
|
||||
ipcMain.handle(IpcEvents.OPEN_EXTERNAL, (_, url) => shell.openExternal(url));
|
||||
|
||||
export function initIpc(mainWindow: BrowserWindow) {
|
||||
open(QUICKCSS_PATH, "a+").then(fd => {
|
||||
fd.close();
|
||||
watch(QUICKCSS_PATH, async () => {
|
||||
mainWindow.webContents.postMessage(IPC_QUICK_CSS_UPDATE, await readCss());
|
||||
mainWindow.webContents.postMessage(IpcEvents.QUICK_CSS_UPDATE, await readCss());
|
||||
});
|
||||
});
|
||||
}
|
||||
|
@ -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);
|
@ -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";
|
@ -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;
|
||||
}
|
@ -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);
|
||||
});
|
||||
|
Loading…
Reference in new issue