Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a807ae85fc | ||
|
|
cb2fb648b6 | ||
|
|
e265e70fb9 | ||
|
|
805b6fbcc4 | ||
|
|
591d380160 | ||
|
|
8f19d41d4c | ||
|
|
b4a2c41e74 | ||
|
|
7e0532444d | ||
|
|
8b68eef9a7 | ||
|
|
e3f973ff68 |
@@ -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
|
||||
```
|
||||
|
||||
25
package.json
25
package.json
@@ -1,31 +1,37 @@
|
||||
{
|
||||
"name": "VencordDesktop",
|
||||
"version": "0.1.3",
|
||||
"version": "0.1.4",
|
||||
"private": true,
|
||||
"description": "",
|
||||
"keywords": [],
|
||||
"homepage": "https://vencord.dev/",
|
||||
"license": "GPL-3.0",
|
||||
"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 .",
|
||||
"start:dev": "pnpm build --dev && electron .",
|
||||
"start:watch": "tsx scripts/startWatch.mts",
|
||||
"test": "echo \"Error: no test specified\" && exit 1",
|
||||
"watch": "pnpm build --watch"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^18.15.11",
|
||||
"@types/react": "^18.0.33",
|
||||
"electron": "^23.2.0",
|
||||
"electron-builder": "^23.6.0",
|
||||
"esbuild": "^0.17.14",
|
||||
"source-map-support": "^0.5.21",
|
||||
"tsx": "^3.12.6",
|
||||
"type-fest": "^3.8.0",
|
||||
"typescript": "^5.0.2"
|
||||
},
|
||||
"build": {
|
||||
"appId": "dev.vencord.desktop",
|
||||
"productName": "Vencord Desktop",
|
||||
"files": [
|
||||
"!*",
|
||||
"dist/js",
|
||||
@@ -40,8 +46,16 @@
|
||||
"deb",
|
||||
"tar.gz",
|
||||
"rpm",
|
||||
"AppImage"
|
||||
]
|
||||
"AppImage",
|
||||
"pacman"
|
||||
],
|
||||
"desktop": {
|
||||
"Name": "Vencord Desktop",
|
||||
"GenericName": "Internet Messenger",
|
||||
"Type": "Application",
|
||||
"Categories": "Network;InstantMessaging;Chat;",
|
||||
"Keywords": "discord;vencord;electron;chat;"
|
||||
}
|
||||
},
|
||||
"mac": {
|
||||
"category": "Network"
|
||||
@@ -50,7 +64,6 @@
|
||||
"allowToChangeInstallationDirectory": true,
|
||||
"include": "build/installer.nsh",
|
||||
"oneClick": false
|
||||
},
|
||||
"productName": "Vencord Desktop"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
620
pnpm-lock.yaml
generated
620
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -1,7 +1,9 @@
|
||||
import { BuildContext, BuildOptions, context } from "esbuild";
|
||||
|
||||
const isDev = process.argv.includes("--dev");
|
||||
|
||||
const CommonOpts: BuildOptions = {
|
||||
minify: true,
|
||||
minify: !isDev,
|
||||
bundle: true,
|
||||
sourcemap: "linked",
|
||||
logLevel: "info"
|
||||
@@ -13,6 +15,9 @@ const NodeCommonOpts: BuildOptions = {
|
||||
platform: "node",
|
||||
external: ["electron"],
|
||||
target: ["esnext"],
|
||||
define: {
|
||||
IS_DEV: JSON.stringify(isDev)
|
||||
}
|
||||
};
|
||||
|
||||
const contexts = [] as BuildContext[];
|
||||
@@ -33,9 +38,15 @@ await Promise.all([
|
||||
}),
|
||||
createContext({
|
||||
...CommonOpts,
|
||||
globalName: "VencordDesktop",
|
||||
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"
|
||||
})
|
||||
]);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
spawn("tsx", ["scripts/build.mts", "--", "--watch"]);
|
||||
spawn("tsx", ["scripts/build/build.mts", "--", "--watch", "--dev"]);
|
||||
spawn("electron", ["."]);
|
||||
|
||||
5
src/globals.d.ts
vendored
5
src/globals.d.ts
vendored
@@ -1,8 +1,11 @@
|
||||
declare global {
|
||||
export var VencordDesktop: typeof import("./preload/VencordDesktop").VencordDesktop;
|
||||
export var VencordDesktopNative: typeof import("preload/VencordDesktopNative").VencordDesktopNative;
|
||||
export var VencordDesktop: typeof import("renderer/index");
|
||||
// TODO
|
||||
export var Vencord: any;
|
||||
export var vcdLS: typeof localStorage;
|
||||
|
||||
export var IS_DEV: boolean;
|
||||
}
|
||||
|
||||
export { };
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import { app } from "electron";
|
||||
import { join } from "path";
|
||||
|
||||
export const DATA_DIR = process.env.VENCORD_USER_DATA_DIR ?? join(app.getPath("userData"), "VencordDesktop");
|
||||
export const VENCORD_FILES_DIR = join(DATA_DIR, "vencordDist");
|
||||
export const DATA_DIR = process.env.VENCORD_USER_DATA_DIR || join(app.getPath("userData"), "VencordDesktop");
|
||||
// needs to be inline require because of circular dependency
|
||||
// as otherwise "DATA_DIR" (which is used by ./settings) will be uninitialised
|
||||
export const VENCORD_FILES_DIR = require("./settings").Settings.vencordDir || join(DATA_DIR, "vencordDist");
|
||||
export const VENCORD_SETTINGS_DIR = join(DATA_DIR, "settings");
|
||||
export const VENCORD_QUICKCSS_FILE = join(VENCORD_SETTINGS_DIR, "quickCss.css");
|
||||
export const VENCORD_SETTINGS_FILE = join(VENCORD_SETTINGS_DIR, "settings.json");
|
||||
|
||||
@@ -1,22 +1,22 @@
|
||||
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 { ICON_PATH } from "../shared/paths";
|
||||
import { once } from "../shared/utils/once";
|
||||
import { DATA_DIR, VENCORD_FILES_DIR } from "./constants";
|
||||
import { createMainWindow } from "./mainWindow";
|
||||
import { Settings } from "./settings";
|
||||
import { createSplashWindow } from "./splash";
|
||||
import { ensureVencordFiles } from "./utils/vencordLoader";
|
||||
|
||||
import { ICON_PATH } from "../shared/paths";
|
||||
import "./ipc";
|
||||
import { Settings } from "./settings";
|
||||
if (IS_DEV) {
|
||||
require("source-map-support").install();
|
||||
}
|
||||
|
||||
// Make the Vencord files use our DATA_DIR
|
||||
process.env.VENCORD_USER_DATA_DIR = DATA_DIR;
|
||||
|
||||
const runVencordMain = once(() => require(join(VENCORD_FILES_DIR, "main.js")));
|
||||
const runVencordMain = once(() => require(join(VENCORD_FILES_DIR, "vencordDesktopMain.js")));
|
||||
|
||||
let mainWin: BrowserWindow | null = null;
|
||||
|
||||
|
||||
@@ -1,44 +1,68 @@
|
||||
import { app, ipcMain, shell } from "electron";
|
||||
import { readFileSync, watch } from "fs";
|
||||
import { app, dialog, ipcMain, shell } from "electron";
|
||||
import { existsSync, readFileSync, watch } from "fs";
|
||||
import { open, readFile } from "fs/promises";
|
||||
import { join } from "path";
|
||||
import { debounce } from "shared/utils/debounce";
|
||||
import { FOCUS, GET_RENDERER_SCRIPT, GET_RENDERER_STYLES, GET_SETTINGS, GET_VENCORD_PRELOAD_FILE, RELAUNCH, SET_SETTINGS, SHOW_ITEM_IN_FOLDER } from "../shared/IpcEvents";
|
||||
import { IpcEvents } from "../shared/IpcEvents";
|
||||
import { VENCORD_FILES_DIR, VENCORD_QUICKCSS_FILE } from "./constants";
|
||||
import { mainWin } from "./mainWindow";
|
||||
import { PlainSettings, setSettings } from "./settings";
|
||||
|
||||
ipcMain.on(GET_VENCORD_PRELOAD_FILE, e => {
|
||||
ipcMain.on(IpcEvents.GET_VENCORD_PRELOAD_FILE, e => {
|
||||
e.returnValue = join(VENCORD_FILES_DIR, "preload.js");
|
||||
});
|
||||
|
||||
ipcMain.on(GET_RENDERER_SCRIPT, e => {
|
||||
ipcMain.on(IpcEvents.GET_VENCORD_RENDERER_SCRIPT, e => {
|
||||
e.returnValue = readFileSync(join(VENCORD_FILES_DIR, "vencordDesktopRenderer.js"), "utf-8");
|
||||
});
|
||||
|
||||
ipcMain.on(IpcEvents.GET_RENDERER_SCRIPT, e => {
|
||||
e.returnValue = readFileSync(join(__dirname, "renderer.js"), "utf-8");
|
||||
});
|
||||
|
||||
ipcMain.handle(GET_RENDERER_STYLES, () => readFile(join(__dirname, "renderer.css"), "utf-8"));
|
||||
ipcMain.on(IpcEvents.GET_RENDERER_CSS_FILE, e => {
|
||||
e.returnValue = join(__dirname, "renderer.css");
|
||||
});
|
||||
|
||||
ipcMain.on(GET_SETTINGS, e => {
|
||||
ipcMain.on(IpcEvents.GET_SETTINGS, e => {
|
||||
e.returnValue = PlainSettings;
|
||||
});
|
||||
|
||||
ipcMain.handle(SET_SETTINGS, (_, settings) => {
|
||||
ipcMain.on(IpcEvents.GET_VERSION, e => {
|
||||
e.returnValue = app.getVersion();
|
||||
});
|
||||
|
||||
ipcMain.handle(IpcEvents.SET_SETTINGS, (_, settings) => {
|
||||
setSettings(settings);
|
||||
});
|
||||
|
||||
ipcMain.handle(RELAUNCH, () => {
|
||||
ipcMain.handle(IpcEvents.RELAUNCH, () => {
|
||||
app.relaunch();
|
||||
app.exit();
|
||||
});
|
||||
|
||||
ipcMain.handle(SHOW_ITEM_IN_FOLDER, (_, path) => {
|
||||
ipcMain.handle(IpcEvents.SHOW_ITEM_IN_FOLDER, (_, path) => {
|
||||
shell.showItemInFolder(path);
|
||||
});
|
||||
|
||||
ipcMain.handle(FOCUS, () => {
|
||||
ipcMain.handle(IpcEvents.FOCUS, () => {
|
||||
mainWin?.focus();
|
||||
});
|
||||
|
||||
ipcMain.handle(IpcEvents.SELECT_VENCORD_DIR, async () => {
|
||||
const res = await dialog.showOpenDialog(mainWin!, {
|
||||
properties: ["openDirectory"]
|
||||
});
|
||||
if (!res.filePaths.length) return "cancelled";
|
||||
|
||||
const dir = res.filePaths[0];
|
||||
for (const file of ["vencordDesktopMain.js", "preload.js", "vencordDesktopRenderer.js", "renderer.css"]) {
|
||||
if (!existsSync(join(dir, file))) return "invalid";
|
||||
}
|
||||
|
||||
return dir;
|
||||
});
|
||||
|
||||
function readCss() {
|
||||
return readFile(VENCORD_QUICKCSS_FILE, "utf-8").catch(() => "");
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -3,7 +3,6 @@ import { join } from "path";
|
||||
import { USER_AGENT, VENCORD_FILES_DIR } from "../constants";
|
||||
import { downloadFile, simpleGet } from "./http";
|
||||
|
||||
// TODO: Setting to switch repo
|
||||
const API_BASE = "https://api.github.com/repos/Vendicated/Vencord";
|
||||
|
||||
const FILES_TO_DOWNLOAD = [
|
||||
@@ -33,21 +32,17 @@ export async function downloadVencordFiles() {
|
||||
|
||||
await Promise.all(
|
||||
assets
|
||||
.filter(({ name }) => FILES_TO_DOWNLOAD.some(f => name.startsWith(f)))
|
||||
.filter(({ name }) =>
|
||||
FILES_TO_DOWNLOAD.some(f => name.startsWith(f))
|
||||
)
|
||||
.map(({ name, browser_download_url }) =>
|
||||
downloadFile(
|
||||
browser_download_url,
|
||||
join(
|
||||
VENCORD_FILES_DIR,
|
||||
name.replace(/vencordDesktop(\w)/, (_, c) => c.toLowerCase())
|
||||
)
|
||||
)
|
||||
downloadFile(browser_download_url, join(VENCORD_FILES_DIR, name))
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
export async function ensureVencordFiles() {
|
||||
if (existsSync(join(VENCORD_FILES_DIR, "main.js"))) return;
|
||||
if (existsSync(join(VENCORD_FILES_DIR, "vencordDesktopMain.js"))) return;
|
||||
mkdirSync(VENCORD_FILES_DIR, { recursive: true });
|
||||
|
||||
await downloadVencordFiles();
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
import { ipcRenderer } from "electron";
|
||||
import type { Settings } from "../main/settings";
|
||||
import { FOCUS, GET_SETTINGS, RELAUNCH, SET_SETTINGS, SHOW_ITEM_IN_FOLDER } from "../shared/IpcEvents";
|
||||
|
||||
export const VencordDesktop = {
|
||||
app: {
|
||||
relaunch: () => ipcRenderer.invoke(RELAUNCH)
|
||||
},
|
||||
fileManager: {
|
||||
showItemInFolder: (path: string) => ipcRenderer.invoke(SHOW_ITEM_IN_FOLDER, path)
|
||||
},
|
||||
settings: {
|
||||
get: () => ipcRenderer.sendSync(GET_SETTINGS),
|
||||
set: (settings: typeof Settings) => ipcRenderer.invoke(SET_SETTINGS, settings)
|
||||
},
|
||||
win: {
|
||||
focus: () => ipcRenderer.invoke(FOCUS)
|
||||
}
|
||||
}
|
||||
|
||||
31
src/preload/VencordDesktopNative.ts
Normal file
31
src/preload/VencordDesktopNative.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
import { ipcRenderer } from "electron";
|
||||
import type { Settings } from "shared/settings";
|
||||
import type { LiteralUnion } from "type-fest";
|
||||
import { IpcEvents } from "../shared/IpcEvents";
|
||||
|
||||
function invoke<T = any>(event: IpcEvents, ...args: any[]) {
|
||||
return ipcRenderer.invoke(event, ...args) as Promise<T>;
|
||||
}
|
||||
|
||||
function sendSync<T = any>(event: IpcEvents, ...args: any[]) {
|
||||
return ipcRenderer.sendSync(event, ...args) as T;
|
||||
}
|
||||
|
||||
export const VencordDesktopNative = {
|
||||
app: {
|
||||
relaunch: () => invoke<void>(IpcEvents.RELAUNCH),
|
||||
getVersion: () => sendSync<void>(IpcEvents.GET_VERSION)
|
||||
},
|
||||
fileManager: {
|
||||
showItemInFolder: (path: string) => invoke<void>(IpcEvents.SHOW_ITEM_IN_FOLDER, path),
|
||||
selectVencordDir: () => invoke<LiteralUnion<"cancelled" | "invalid", string>>(IpcEvents.SELECT_VENCORD_DIR),
|
||||
},
|
||||
settings: {
|
||||
get: () => sendSync<Settings>(IpcEvents.GET_SETTINGS),
|
||||
set: (settings: Settings) => invoke<void>(IpcEvents.SET_SETTINGS, settings)
|
||||
},
|
||||
win: {
|
||||
focus: () => invoke<void>(IpcEvents.FOCUS)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,36 @@
|
||||
import { contextBridge, ipcRenderer, webFrame } from "electron";
|
||||
import { GET_RENDERER_SCRIPT, GET_RENDERER_STYLES, GET_VENCORD_PRELOAD_FILE } from "../shared/IpcEvents";
|
||||
import { VencordDesktop } from "./VencordDesktop";
|
||||
import { readFileSync, watch } from "fs";
|
||||
import { IpcEvents } from "../shared/IpcEvents";
|
||||
import { VencordDesktopNative } from "./VencordDesktopNative";
|
||||
|
||||
contextBridge.exposeInMainWorld("VencordDesktop", VencordDesktop);
|
||||
contextBridge.exposeInMainWorld("VencordDesktopNative", VencordDesktopNative);
|
||||
|
||||
require(ipcRenderer.sendSync(GET_VENCORD_PRELOAD_FILE));
|
||||
require(ipcRenderer.sendSync(IpcEvents.GET_VENCORD_PRELOAD_FILE));
|
||||
|
||||
webFrame.executeJavaScript(ipcRenderer.sendSync(GET_RENDERER_SCRIPT));
|
||||
ipcRenderer.invoke(GET_RENDERER_STYLES).then(s => webFrame.insertCSS(s));
|
||||
webFrame.executeJavaScript(ipcRenderer.sendSync(IpcEvents.GET_VENCORD_RENDERER_SCRIPT));
|
||||
webFrame.executeJavaScript(ipcRenderer.sendSync(IpcEvents.GET_RENDERER_SCRIPT));
|
||||
|
||||
// #region css
|
||||
const rendererCss = ipcRenderer.sendSync(IpcEvents.GET_RENDERER_CSS_FILE);
|
||||
|
||||
const style = document.createElement("style");
|
||||
style.id = "vcd-css-core";
|
||||
style.textContent = readFileSync(rendererCss, "utf-8");
|
||||
|
||||
if (document.readyState === "complete") {
|
||||
document.documentElement.appendChild(style);
|
||||
} else {
|
||||
document.addEventListener("DOMContentLoaded", () => document.documentElement.appendChild(style), {
|
||||
once: true
|
||||
});
|
||||
}
|
||||
|
||||
if (IS_DEV) {
|
||||
// persistent means keep process running if watcher is the only thing still running
|
||||
// which we obviously don't want
|
||||
watch(rendererCss, { persistent: false }, () => {
|
||||
document.getElementById("vcd-css-core")!.textContent = readFileSync(rendererCss, "utf-8");
|
||||
});
|
||||
}
|
||||
|
||||
// #endregion
|
||||
|
||||
88
src/renderer/components/Settings.tsx
Normal file
88
src/renderer/components/Settings.tsx
Normal file
@@ -0,0 +1,88 @@
|
||||
import { getValueAndOnChange, useSettings } from "renderer/settings";
|
||||
import { Common, Util } from "../vencord";
|
||||
|
||||
import "./settings.css";
|
||||
|
||||
const { Margins } = Util;
|
||||
|
||||
export default function SettingsUi() {
|
||||
const Settings = useSettings();
|
||||
const { Forms: { FormSection, FormText, FormDivider, FormSwitch, FormTitle }, Text, Select, Button } = Common;
|
||||
|
||||
return (
|
||||
<FormSection>
|
||||
<Text variant="heading-lg/semibold" style={{ color: "var(--header-primary)" }} tag="h2">
|
||||
Vencord Desktop Settings
|
||||
</Text>
|
||||
|
||||
<FormTitle className={Margins.top16}>
|
||||
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}
|
||||
/>
|
||||
|
||||
<FormDivider className={Margins.top16 + " " + Margins.bottom16} />
|
||||
|
||||
<FormSwitch
|
||||
{...getValueAndOnChange("openLinksWithElectron")}
|
||||
note={"This will open links in a new window instead of your WebBrowser"}
|
||||
>
|
||||
Open Links in app
|
||||
</FormSwitch>
|
||||
|
||||
<FormTitle>Vencord Desktop Location</FormTitle>
|
||||
<FormText>
|
||||
Files are loaded from
|
||||
{" "}
|
||||
{Settings.vencordDir
|
||||
? (
|
||||
<a
|
||||
href="about:blank"
|
||||
onClick={e => {
|
||||
e.preventDefault();
|
||||
VencordDesktopNative.fileManager.showItemInFolder(Settings.vencordDir!);
|
||||
}}
|
||||
>
|
||||
{Settings.vencordDir}
|
||||
</a>
|
||||
)
|
||||
: "the default location"
|
||||
}
|
||||
</FormText>
|
||||
<div className="vcd-location-btns">
|
||||
<Button
|
||||
size={Button.Sizes.SMALL}
|
||||
onClick={async () => {
|
||||
const choice = await VencordDesktopNative.fileManager.selectVencordDir();
|
||||
switch (choice) {
|
||||
case "cancelled":
|
||||
case "invalid":
|
||||
// TODO
|
||||
return;
|
||||
}
|
||||
Settings.vencordDir = choice;
|
||||
}}
|
||||
>
|
||||
Change
|
||||
</Button>
|
||||
<Button
|
||||
size={Button.Sizes.SMALL}
|
||||
color={Button.Colors.RED}
|
||||
onClick={() => Settings.vencordDir = void 0}
|
||||
>
|
||||
Reset
|
||||
</Button>
|
||||
</div>
|
||||
</FormSection>
|
||||
);
|
||||
}
|
||||
1
src/renderer/components/index.ts
Normal file
1
src/renderer/components/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { default as Settings } from "./Settings";
|
||||
6
src/renderer/components/settings.css
Normal file
6
src/renderer/components/settings.css
Normal file
@@ -0,0 +1,6 @@
|
||||
.vcd-location-btns {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 0.5em;
|
||||
margin-top: 0.5em;
|
||||
}
|
||||
@@ -7,7 +7,7 @@ Object.defineProperty(Notification.prototype, "onclick", {
|
||||
set(onClick) {
|
||||
originalSetOnClick.call(this, function (this: unknown) {
|
||||
onClick.apply(this, arguments);
|
||||
VencordDesktop.win.focus();
|
||||
VencordDesktopNative.win.focus();
|
||||
});
|
||||
},
|
||||
configurable: true
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
import "./fixes";
|
||||
|
||||
console.log("read if cute :3");
|
||||
|
||||
export * as Components from "./components";
|
||||
export { PlainSettings, Settings } from "./settings";
|
||||
|
||||
|
||||
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 = VencordDesktopNative.settings.get() as TSettings;
|
||||
export const Settings = makeChangeListenerProxy(PlainSettings, s => {
|
||||
VencordDesktopNative.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
|
||||
};
|
||||
}
|
||||
13
src/renderer/vencord.ts
Normal file
13
src/renderer/vencord.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
// FIXME: this is terrible
|
||||
|
||||
const { Webpack, Plugins, Util } = Vencord;
|
||||
const { Common } = Webpack;
|
||||
const { plugins } = Plugins;
|
||||
|
||||
export {
|
||||
Webpack,
|
||||
Common,
|
||||
Util,
|
||||
Plugins,
|
||||
plugins
|
||||
};
|
||||
@@ -1,10 +1,18 @@
|
||||
export const GET_VENCORD_PRELOAD_FILE = "VCD_GET_VC_PRELOAD_FILE";
|
||||
export const GET_RENDERER_SCRIPT = "VCD_GET_RENDERER_SCRIPT";
|
||||
export const GET_RENDERER_STYLES = "VCD_GET_RENDERER_STYLES";
|
||||
export const enum IpcEvents {
|
||||
GET_VENCORD_PRELOAD_FILE = "VCD_GET_VC_PRELOAD_FILE",
|
||||
GET_VENCORD_RENDERER_SCRIPT = "VCD_GET_VC_RENDERER_SCRIPT",
|
||||
GET_RENDERER_SCRIPT = "VCD_GET_RENDERER_SCRIPT",
|
||||
GET_RENDERER_CSS_FILE = "VCD_GET_RENDERER_CSS_FILE",
|
||||
|
||||
export const RELAUNCH = "VCD_RELAUNCH";
|
||||
export const FOCUS = "VC_FOCUS";
|
||||
RELAUNCH = "VCD_RELAUNCH",
|
||||
FOCUS = "VCD_FOCUS",
|
||||
|
||||
GET_VERSION = "VCD_GET_VERSION",
|
||||
|
||||
SHOW_ITEM_IN_FOLDER = "VCD_SHOW_ITEM_IN_FOLDER",
|
||||
GET_SETTINGS = "VCD_GET_SETTINGS",
|
||||
SET_SETTINGS = "VCD_SET_SETTINGS",
|
||||
|
||||
SELECT_VENCORD_DIR = "VCD_SELECT_VENCORD_DIR"
|
||||
}
|
||||
|
||||
export const SHOW_ITEM_IN_FOLDER = "VCD_SHOW_ITEM_IN_FOLDER";
|
||||
export const GET_SETTINGS = "VCD_GET_SETTINGS";
|
||||
export const SET_SETTINGS = "VCD_SET_SETTINGS";
|
||||
|
||||
13
src/shared/settings.d.ts
vendored
Normal file
13
src/shared/settings.d.ts
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
export interface Settings {
|
||||
maximized?: boolean;
|
||||
minimized?: boolean;
|
||||
windowBounds?: {
|
||||
x: number;
|
||||
y: number;
|
||||
width: number;
|
||||
height: number;
|
||||
};
|
||||
discordBranch?: "stable" | "canary" | "ptb";
|
||||
openLinksWithElectron?: boolean;
|
||||
vencordDir?: string;
|
||||
}
|
||||
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;
|
||||
}
|
||||
Reference in New Issue
Block a user