Initial Settings UI work
This commit is contained in:
parent
8b68eef9a7
commit
7e0532444d
16 changed files with 169 additions and 37 deletions
|
@ -46,6 +46,8 @@ pnpm start
|
||||||
|
|
||||||
# Or package
|
# Or package
|
||||||
pnpm package
|
pnpm package
|
||||||
|
# Or only build the pacman target
|
||||||
|
pnpm package --linux pacman
|
||||||
# Or package to a directory only
|
# Or package to a directory only
|
||||||
pnpm package:dir
|
pnpm package:dir
|
||||||
```
|
```
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
"author": "Vendicated <vendicated@riseup.net>",
|
"author": "Vendicated <vendicated@riseup.net>",
|
||||||
"main": "dist/js/main.js",
|
"main": "dist/js/main.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "tsx scripts/build.mts",
|
"build": "tsx scripts/build/build.mts",
|
||||||
"package": "pnpm build && electron-builder",
|
"package": "pnpm build && electron-builder",
|
||||||
"package:dir": "pnpm build && electron-builder --dir",
|
"package:dir": "pnpm build && electron-builder --dir",
|
||||||
"start": "pnpm build && electron .",
|
"start": "pnpm build && electron .",
|
||||||
|
|
|
@ -36,6 +36,11 @@ await Promise.all([
|
||||||
entryPoints: ["src/renderer/index.ts"],
|
entryPoints: ["src/renderer/index.ts"],
|
||||||
outfile: "dist/js/renderer.js",
|
outfile: "dist/js/renderer.js",
|
||||||
format: "iife",
|
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"
|
||||||
})
|
})
|
||||||
]);
|
]);
|
||||||
|
|
3
scripts/build/injectReact.mjs
Normal file
3
scripts/build/injectReact.mjs
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
export const VencordFragment = /* #__PURE__*/ Symbol.for("react.fragment");
|
||||||
|
export let VencordCreateElement =
|
||||||
|
(...args) => (VencordCreateElement = Vencord.Webpack.Common.React.createElement)(...args);
|
7
scripts/build/tsconfig.esbuild.json
Normal file
7
scripts/build/tsconfig.esbuild.json
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
// Work around https://github.com/evanw/esbuild/issues/2460
|
||||||
|
{
|
||||||
|
"extends": "../../tsconfig.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"jsx": "react"
|
||||||
|
}
|
||||||
|
}
|
|
@ -11,5 +11,5 @@ function spawn(bin: string, args: string[]) {
|
||||||
cpSpawn(join("node_modules", ".bin", bin + EXT), args, OPTS);
|
cpSpawn(join("node_modules", ".bin", bin + EXT), args, OPTS);
|
||||||
}
|
}
|
||||||
|
|
||||||
spawn("tsx", ["scripts/build.mts", "--", "--watch"]);
|
spawn("tsx", ["scripts/build/build.mts", "--", "--watch"]);
|
||||||
spawn("electron", ["."]);
|
spawn("electron", ["."]);
|
||||||
|
|
|
@ -1,17 +1,13 @@
|
||||||
import { app, BrowserWindow } from 'electron';
|
import { app, BrowserWindow } from 'electron';
|
||||||
import { createMainWindow } from "./mainWindow";
|
|
||||||
import { createSplashWindow } from "./splash";
|
|
||||||
|
|
||||||
import { join } from "path";
|
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 { ICON_PATH } from "../shared/paths";
|
||||||
|
import { once } from "../shared/utils/once";
|
||||||
|
import { DATA_DIR, VENCORD_FILES_DIR } from "./constants";
|
||||||
import "./ipc";
|
import "./ipc";
|
||||||
|
import { createMainWindow } from "./mainWindow";
|
||||||
import { Settings } from "./settings";
|
import { Settings } from "./settings";
|
||||||
|
import { createSplashWindow } from "./splash";
|
||||||
|
import { ensureVencordFiles } from "./utils/vencordLoader";
|
||||||
|
|
||||||
// Make the Vencord files use our DATA_DIR
|
// Make the Vencord files use our DATA_DIR
|
||||||
process.env.VENCORD_USER_DATA_DIR = DATA_DIR;
|
process.env.VENCORD_USER_DATA_DIR = DATA_DIR;
|
||||||
|
|
|
@ -1,23 +1,12 @@
|
||||||
import { readFileSync, writeFileSync } from "fs";
|
import { readFileSync, writeFileSync } from "fs";
|
||||||
import { join } from "path";
|
import { join } from "path";
|
||||||
|
import type { Settings as TSettings } from "shared/settings";
|
||||||
|
import { makeChangeListenerProxy } from "shared/utils/makeChangeListenerProxy";
|
||||||
import { DATA_DIR } from "./constants";
|
import { DATA_DIR } from "./constants";
|
||||||
|
|
||||||
const SETTINGS_FILE = join(DATA_DIR, "settings.json");
|
const SETTINGS_FILE = join(DATA_DIR, "settings.json");
|
||||||
|
|
||||||
interface Settings {
|
export let PlainSettings = {} as TSettings;
|
||||||
maximized?: boolean;
|
|
||||||
minimized?: boolean;
|
|
||||||
windowBounds?: {
|
|
||||||
x: number;
|
|
||||||
y: number;
|
|
||||||
width: number;
|
|
||||||
height: number;
|
|
||||||
};
|
|
||||||
discordBranch?: "stable" | "canary" | "ptb";
|
|
||||||
openLinksWithElectron?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export let PlainSettings = {} as Settings;
|
|
||||||
try {
|
try {
|
||||||
const content = readFileSync(SETTINGS_FILE, "utf8");
|
const content = readFileSync(SETTINGS_FILE, "utf8");
|
||||||
try {
|
try {
|
||||||
|
@ -27,21 +16,14 @@ try {
|
||||||
}
|
}
|
||||||
} catch { }
|
} catch { }
|
||||||
|
|
||||||
function makeSettingsProxy(settings: Settings) {
|
const makeSettingsProxy = (settings: TSettings) => makeChangeListenerProxy(
|
||||||
return new Proxy(settings, {
|
settings,
|
||||||
set(target, prop, value) {
|
target => writeFileSync(SETTINGS_FILE, JSON.stringify(target, null, 4))
|
||||||
Reflect.set(target, prop, value);
|
);
|
||||||
|
|
||||||
writeFileSync(SETTINGS_FILE, JSON.stringify(target, null, 4));
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export let Settings = makeSettingsProxy(PlainSettings);
|
export let Settings = makeSettingsProxy(PlainSettings);
|
||||||
|
|
||||||
export function setSettings(settings: Settings) {
|
export function setSettings(settings: TSettings) {
|
||||||
writeFileSync(SETTINGS_FILE, JSON.stringify(settings, null, 4));
|
writeFileSync(SETTINGS_FILE, JSON.stringify(settings, null, 4));
|
||||||
PlainSettings = settings;
|
PlainSettings = settings;
|
||||||
Settings = makeSettingsProxy(settings);
|
Settings = makeSettingsProxy(settings);
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
import "./fixes";
|
import "./fixes";
|
||||||
|
import "./ui/patchSettings";
|
||||||
|
|
||||||
console.log("read if cute :3");
|
console.log("read if cute :3");
|
||||||
|
|
28
src/renderer/settings.ts
Normal file
28
src/renderer/settings.ts
Normal 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
|
||||||
|
};
|
||||||
|
}
|
36
src/renderer/ui/Settings.tsx
Normal file
36
src/renderer/ui/Settings.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
}
|
15
src/renderer/ui/patchSettings.ts
Normal file
15
src/renderer/ui/patchSettings.ts
Normal 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
12
src/renderer/vencord.ts
Normal 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
12
src/shared/settings.d.ts
vendored
Normal 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;
|
||||||
|
}
|
20
src/shared/utils/makeChangeListenerProxy.ts
Normal file
20
src/shared/utils/makeChangeListenerProxy.ts
Normal 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;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
13
src/shared/utils/monkeyPatch.ts
Normal file
13
src/shared/utils/monkeyPatch.ts
Normal 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;
|
||||||
|
}
|
Loading…
Reference in a new issue