Initial Settings UI work

This commit is contained in:
Vendicated 2023-04-09 00:49:47 +02:00
parent 8b68eef9a7
commit 7e0532444d
No known key found for this signature in database
GPG key ID: A1DC0CFB5615D905
16 changed files with 169 additions and 37 deletions

View file

@ -46,6 +46,8 @@ pnpm start
# Or package
pnpm package
# Or only build the pacman target
pnpm package --linux pacman
# Or package to a directory only
pnpm package:dir
```

View file

@ -9,7 +9,7 @@
"author": "Vendicated <vendicated@riseup.net>",
"main": "dist/js/main.js",
"scripts": {
"build": "tsx scripts/build.mts",
"build": "tsx scripts/build/build.mts",
"package": "pnpm build && electron-builder",
"package:dir": "pnpm build && electron-builder --dir",
"start": "pnpm build && electron .",

View file

@ -36,6 +36,11 @@ await Promise.all([
entryPoints: ["src/renderer/index.ts"],
outfile: "dist/js/renderer.js",
format: "iife",
inject: ["./scripts/build/injectReact.mjs"],
jsxFactory: "VencordCreateElement",
jsxFragment: "VencordFragment",
// Work around https://github.com/evanw/esbuild/issues/2460
tsconfig: "./scripts/build/tsconfig.esbuild.json"
})
]);

View file

@ -0,0 +1,3 @@
export const VencordFragment = /* #__PURE__*/ Symbol.for("react.fragment");
export let VencordCreateElement =
(...args) => (VencordCreateElement = Vencord.Webpack.Common.React.createElement)(...args);

View file

@ -0,0 +1,7 @@
// Work around https://github.com/evanw/esbuild/issues/2460
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"jsx": "react"
}
}

View file

@ -11,5 +11,5 @@ function spawn(bin: string, args: string[]) {
cpSpawn(join("node_modules", ".bin", bin + EXT), args, OPTS);
}
spawn("tsx", ["scripts/build.mts", "--", "--watch"]);
spawn("tsx", ["scripts/build/build.mts", "--", "--watch"]);
spawn("electron", ["."]);

View file

@ -1,17 +1,13 @@
import { app, BrowserWindow } from 'electron';
import { createMainWindow } from "./mainWindow";
import { createSplashWindow } from "./splash";
import { join } from "path";
import { DATA_DIR, VENCORD_FILES_DIR } from "./constants";
import { once } from "../shared/utils/once";
import { ensureVencordFiles } from "./utils/vencordLoader";
import { ICON_PATH } from "../shared/paths";
import { once } from "../shared/utils/once";
import { DATA_DIR, VENCORD_FILES_DIR } from "./constants";
import "./ipc";
import { createMainWindow } from "./mainWindow";
import { Settings } from "./settings";
import { createSplashWindow } from "./splash";
import { ensureVencordFiles } from "./utils/vencordLoader";
// Make the Vencord files use our DATA_DIR
process.env.VENCORD_USER_DATA_DIR = DATA_DIR;

View file

@ -1,23 +1,12 @@
import { readFileSync, writeFileSync } from "fs";
import { join } from "path";
import type { Settings as TSettings } from "shared/settings";
import { makeChangeListenerProxy } from "shared/utils/makeChangeListenerProxy";
import { DATA_DIR } from "./constants";
const SETTINGS_FILE = join(DATA_DIR, "settings.json");
interface Settings {
maximized?: boolean;
minimized?: boolean;
windowBounds?: {
x: number;
y: number;
width: number;
height: number;
};
discordBranch?: "stable" | "canary" | "ptb";
openLinksWithElectron?: boolean;
}
export let PlainSettings = {} as Settings;
export let PlainSettings = {} as TSettings;
try {
const content = readFileSync(SETTINGS_FILE, "utf8");
try {
@ -27,21 +16,14 @@ try {
}
} catch { }
function makeSettingsProxy(settings: Settings) {
return new Proxy(settings, {
set(target, prop, value) {
Reflect.set(target, prop, value);
writeFileSync(SETTINGS_FILE, JSON.stringify(target, null, 4));
return true;
}
});
}
const makeSettingsProxy = (settings: TSettings) => makeChangeListenerProxy(
settings,
target => writeFileSync(SETTINGS_FILE, JSON.stringify(target, null, 4))
);
export let Settings = makeSettingsProxy(PlainSettings);
export function setSettings(settings: Settings) {
export function setSettings(settings: TSettings) {
writeFileSync(SETTINGS_FILE, JSON.stringify(settings, null, 4));
PlainSettings = settings;
Settings = makeSettingsProxy(settings);

View file

@ -1,3 +1,4 @@
import "./fixes";
import "./ui/patchSettings";
console.log("read if cute :3");

28
src/renderer/settings.ts Normal file
View file

@ -0,0 +1,28 @@
import type { Settings as TSettings } from "shared/settings";
import { makeChangeListenerProxy } from "shared/utils/makeChangeListenerProxy";
import { Common } from "./vencord";
const signals = new Set<() => void>();
export const PlainSettings = VencordDesktop.settings.get() as TSettings;
export const Settings = makeChangeListenerProxy(PlainSettings, s => {
VencordDesktop.settings.set(s);
signals.forEach(fn => fn());
});
export function useSettings() {
const [, update] = Common.React.useReducer(x => x + 1, 0);
Common.React.useEffect(() => {
signals.add(update);
return () => signals.delete(update);
}, []);
return Settings;
}
export function getValueAndOnChange(key: keyof TSettings) {
return {
value: Settings[key] as any,
onChange: (value: any) => Settings[key] = value
};
}

View file

@ -0,0 +1,36 @@
import { getValueAndOnChange, useSettings } from "renderer/settings";
import { Common } from "../vencord";
export default function SettingsUi() {
const Settings = useSettings();
const { Forms: { FormSection, FormText, FormDivider, FormSwitch, FormTitle }, Text, Select } = Common;
return (
<FormSection>
<Text variant="heading-lg/semibold" style={{ color: "var(--header-primary)" }} tag="h2">
Vencord Desktop Settings
</Text>
<FormTitle>Discord Branch</FormTitle>
<Select
placeholder="Stable"
options={[
{ label: "Stable", value: "stable", default: true },
{ label: "Canary", value: "canary" },
{ label: "PTB", value: "ptb" },
]}
closeOnSelect={true}
select={v => Settings.discordBranch = v}
isSelected={v => v === Settings.discordBranch}
serialize={s => s}
/>
<FormSwitch
{...getValueAndOnChange("openLinksWithElectron")}
note={"This will open links in a new window instead of your WebBrowser"}
>
Open Links in app
</FormSwitch>
</FormSection>
);
}

View file

@ -0,0 +1,15 @@
import { monkeyPatch } from "../../shared/utils/monkeyPatch";
import { Common, plugins } from "../vencord";
import Settings from "./Settings";
monkeyPatch(plugins.Settings, "makeSettingsCategories", function (this: unknown, original, { ID }: { ID: Record<string, unknown>; }) {
const cats = original.call(this, { ID });
cats.splice(1, 0, {
section: "VencordDesktop",
label: "Desktop Settings",
element: Settings,
onClick: () => Common.SettingsRouter.open("VencordDesktop")
});
return cats;
});

12
src/renderer/vencord.ts Normal file
View file

@ -0,0 +1,12 @@
// TODO: Taaaips when?
const { Webpack, Plugins } = Vencord;
const { Common } = Webpack;
const { plugins } = Plugins;
export {
Webpack,
Common,
Plugins,
plugins
};

12
src/shared/settings.d.ts vendored Normal file
View file

@ -0,0 +1,12 @@
export interface Settings {
maximized?: boolean;
minimized?: boolean;
windowBounds?: {
x: number;
y: number;
width: number;
height: number;
};
discordBranch?: "stable" | "canary" | "ptb";
openLinksWithElectron?: boolean;
}

View file

@ -0,0 +1,20 @@
export function makeChangeListenerProxy<T extends object>(object: T, onChange: (object: T) => void, _root = object): T {
return new Proxy(object, {
get(target, key) {
const v = target[key];
if (typeof v === "object" && !Array.isArray(v) && v !== null)
return makeChangeListenerProxy(v, onChange, _root);
return v;
},
set(target, key, value) {
if (target[key] === value) return true;
Reflect.set(target, key, value);
onChange(_root);
return true;
},
});
}

View file

@ -0,0 +1,13 @@
type Func = (...args: any[]) => any;
export function monkeyPatch<O extends object>(object: O, key: keyof O, replacement: (original: Func, ...args: any[]) => any): void {
const original = object[key] as Func;
const replacer = object[key] = function (this: unknown, ...args: any[]) {
return replacement.call(this, original, ...args);
} as any;
Object.defineProperties(replacer, Object.getOwnPropertyDescriptors(original));
replacer.toString = () => original.toString();
replacer.$$original = original;
}