Compare commits
23 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
49fb4c68b6 | ||
|
|
477ecbb4ba | ||
|
|
4abce9d084 | ||
|
|
7f54858b27 | ||
|
|
be176fab71 | ||
|
|
e60f04bb79 | ||
|
|
17f1c4ff6f | ||
|
|
a7ded71404 | ||
|
|
31799ccfb0 | ||
|
|
dde696627e | ||
|
|
50b2e864c2 | ||
|
|
dfa007669b | ||
|
|
4b27c67e83 | ||
|
|
f58ed485a9 | ||
|
|
c31eb8154b | ||
|
|
253277984b | ||
|
|
23c0647e6c | ||
|
|
fd45068a46 | ||
|
|
9f9f665ede | ||
|
|
fd0055032f | ||
|
|
a993d34c9d | ||
|
|
f232defd1c | ||
|
|
887f11ab37 |
6
.vscode/settings.json
vendored
@@ -9,6 +9,12 @@
|
||||
"[javascript]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
},
|
||||
"[typescriptreact]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
},
|
||||
"[javascriptreact]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
},
|
||||
"[json]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
},
|
||||
|
||||
@@ -2,10 +2,7 @@
|
||||
|
||||
Vencord Desktop is a cross platform desktop app aiming to give you a snappier Discord experience with Vencord pre-installed
|
||||
|
||||
Vencord Desktop is currently in beta
|
||||
|
||||
**Not yet supported**:
|
||||
- Screensharing
|
||||
- Global Keybinds
|
||||
|
||||
Bug reports, feature requests & contributions are highly appreciated!!
|
||||
|
||||
10
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "VencordDesktop",
|
||||
"version": "0.2.1",
|
||||
"version": "0.2.5",
|
||||
"private": true,
|
||||
"description": "",
|
||||
"keywords": [],
|
||||
@@ -22,6 +22,9 @@
|
||||
"testTypes": "tsc --noEmit",
|
||||
"watch": "pnpm build --watch"
|
||||
},
|
||||
"dependencies": {
|
||||
"arrpc": "github:OpenAsar/arrpc#3eb5d36a5e9295d3aeafc49975df5d399eb627fd"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@fal-works/esbuild-plugin-global-externals": "^2.1.2",
|
||||
"@types/node": "^18.15.11",
|
||||
@@ -30,7 +33,7 @@
|
||||
"@typescript-eslint/parser": "^5.57.1",
|
||||
"@vencord/types": "^0.1.2",
|
||||
"dotenv": "^16.0.3",
|
||||
"electron": "^23.2.0",
|
||||
"electron": "^25.2.0",
|
||||
"electron-builder": "^23.6.0",
|
||||
"esbuild": "^0.17.14",
|
||||
"eslint": "^8.38.0",
|
||||
@@ -105,8 +108,5 @@
|
||||
"provider": "github",
|
||||
"releaseType": "release"
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"arrpc": "^3.1.0"
|
||||
}
|
||||
}
|
||||
|
||||
38
pnpm-lock.yaml
generated
@@ -2,8 +2,8 @@ lockfileVersion: '6.0'
|
||||
|
||||
dependencies:
|
||||
arrpc:
|
||||
specifier: ^3.1.0
|
||||
version: 3.1.0
|
||||
specifier: github:OpenAsar/arrpc#3eb5d36a5e9295d3aeafc49975df5d399eb627fd
|
||||
version: github.com/OpenAsar/arrpc/3eb5d36a5e9295d3aeafc49975df5d399eb627fd
|
||||
|
||||
devDependencies:
|
||||
'@fal-works/esbuild-plugin-global-externals':
|
||||
@@ -28,8 +28,8 @@ devDependencies:
|
||||
specifier: ^16.0.3
|
||||
version: 16.0.3
|
||||
electron:
|
||||
specifier: ^23.2.0
|
||||
version: 23.2.0
|
||||
specifier: ^25.2.0
|
||||
version: 25.2.0
|
||||
electron-builder:
|
||||
specifier: ^23.6.0
|
||||
version: 23.6.0
|
||||
@@ -520,10 +520,6 @@ packages:
|
||||
resolution: {integrity: sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA==}
|
||||
dev: true
|
||||
|
||||
/@types/node@16.18.22:
|
||||
resolution: {integrity: sha512-LJSIirgASa1LicFGTUFwDY7BfKDtLIbijqDLkH47LxEo/jtdrtiZ4/kLPD99bEQhTcPcuh6KhDllHqRxygJD2w==}
|
||||
dev: true
|
||||
|
||||
/@types/node@18.15.11:
|
||||
resolution: {integrity: sha512-E5Kwq2n4SbMzQOn6wnmBjuK9ouqlURrcZDVfbo9ftDDTFt3nk7ZKK4GMOzoYgnpQJKcxwQw+lGaBvvlMo0qN/Q==}
|
||||
dev: true
|
||||
@@ -894,15 +890,6 @@ packages:
|
||||
es-shim-unscopables: 1.0.0
|
||||
dev: true
|
||||
|
||||
/arrpc@3.1.0:
|
||||
resolution: {integrity: sha512-QKagtB5fUDqDXT31tTUzcG3+rFxsMlrjKb3iE68/b2NbT1c6+0WYkkrJh4GaVPqH0Tlqy13sEgPW3XJ/VNmBDQ==}
|
||||
dependencies:
|
||||
ws: 8.13.0
|
||||
transitivePeerDependencies:
|
||||
- bufferutil
|
||||
- utf-8-validate
|
||||
dev: false
|
||||
|
||||
/asar@3.2.0:
|
||||
resolution: {integrity: sha512-COdw2ZQvKdFGFxXwX3oYh2/sOsJWJegrdJCGxnN4MZ7IULgRBp9P6665aqj9z1v9VwP4oP1hRBojRDQ//IGgAg==}
|
||||
engines: {node: '>=10.12.0'}
|
||||
@@ -1522,14 +1509,14 @@ packages:
|
||||
- supports-color
|
||||
dev: true
|
||||
|
||||
/electron@23.2.0:
|
||||
resolution: {integrity: sha512-De9e21cri0QYct/w6tTNOnKyCt9RVKUw5F8PEN4FPzGR9tr6IT53uyt42uH754uJWrZeLMCAdoXy6/0GmMmYZA==}
|
||||
/electron@25.2.0:
|
||||
resolution: {integrity: sha512-I/rhcW2sV2fyiveVSBr2N7v5ZiCtdGY0UiNCDZgk2fpSC+irQjbeh7JT2b4vWmJ2ogOXBjqesrN9XszTIG6DHg==}
|
||||
engines: {node: '>= 12.20.55'}
|
||||
hasBin: true
|
||||
requiresBuild: true
|
||||
dependencies:
|
||||
'@electron/get': 2.0.2
|
||||
'@types/node': 16.18.22
|
||||
'@types/node': 18.15.11
|
||||
extract-zip: 2.0.1
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
@@ -3856,3 +3843,14 @@ packages:
|
||||
resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}
|
||||
engines: {node: '>=10'}
|
||||
dev: true
|
||||
|
||||
github.com/OpenAsar/arrpc/3eb5d36a5e9295d3aeafc49975df5d399eb627fd:
|
||||
resolution: {tarball: https://codeload.github.com/OpenAsar/arrpc/tar.gz/3eb5d36a5e9295d3aeafc49975df5d399eb627fd}
|
||||
name: arrpc
|
||||
version: 3.1.0
|
||||
dependencies:
|
||||
ws: 8.13.0
|
||||
transitivePeerDependencies:
|
||||
- bufferutil
|
||||
- utf-8-validate
|
||||
dev: false
|
||||
|
||||
2
src/globals.d.ts
vendored
@@ -7,7 +7,7 @@
|
||||
declare global {
|
||||
export var VencordDesktopNative: typeof import("preload/VencordDesktopNative").VencordDesktopNative;
|
||||
export var VencordDesktop: typeof import("renderer/index");
|
||||
export var vcdLS: typeof localStorage;
|
||||
export var VCDP: any;
|
||||
|
||||
export var IS_DEV: boolean;
|
||||
}
|
||||
|
||||
@@ -4,10 +4,9 @@
|
||||
* Copyright (c) 2023 Vendicated and Vencord contributors
|
||||
*/
|
||||
|
||||
import { app, BrowserWindow } from "electron";
|
||||
import { readFileSync } from "fs";
|
||||
import { BrowserWindow } from "electron";
|
||||
import { join } from "path";
|
||||
import { ICON_PATH, STATIC_DIR } from "shared/paths";
|
||||
import { ICON_PATH, VIEW_DIR } from "shared/paths";
|
||||
|
||||
import { makeLinksOpenExternally } from "./utils/makeLinksOpenExternally";
|
||||
|
||||
@@ -15,14 +14,15 @@ export function createAboutWindow() {
|
||||
const about = new BrowserWindow({
|
||||
center: true,
|
||||
autoHideMenuBar: true,
|
||||
icon: ICON_PATH
|
||||
icon: ICON_PATH,
|
||||
webPreferences: {
|
||||
preload: join(__dirname, "updaterPreload.js")
|
||||
}
|
||||
});
|
||||
|
||||
makeLinksOpenExternally(about);
|
||||
|
||||
const html = readFileSync(join(STATIC_DIR, "about.html"), "utf-8").replaceAll("%VERSION%", app.getVersion());
|
||||
|
||||
about.loadURL("data:text/html;charset=utf-8," + html);
|
||||
about.loadFile(join(VIEW_DIR, "about.html"));
|
||||
|
||||
return about;
|
||||
}
|
||||
|
||||
50
src/main/appBadge.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* SPDX-License-Identifier: GPL-3.0
|
||||
* Vencord Desktop, a desktop app aiming to give you a snappier Discord Experience
|
||||
* Copyright (c) 2023 Vendicated and Vencord contributors
|
||||
*/
|
||||
|
||||
import { app, NativeImage, nativeImage } from "electron";
|
||||
import { join } from "path";
|
||||
import { BADGE_DIR } from "shared/paths";
|
||||
|
||||
const imgCache = new Map<number, NativeImage>();
|
||||
function loadBadge(index: number) {
|
||||
const cached = imgCache.get(index);
|
||||
if (cached) return cached;
|
||||
|
||||
const img = nativeImage.createFromPath(join(BADGE_DIR, `${index}.ico`));
|
||||
imgCache.set(index, img);
|
||||
|
||||
return img;
|
||||
}
|
||||
|
||||
let lastIndex: null | number = -1;
|
||||
|
||||
export function setBadgeCount(count: number) {
|
||||
switch (process.platform) {
|
||||
case "darwin":
|
||||
case "linux":
|
||||
if (count === -1) count = 0;
|
||||
app.setBadgeCount(count);
|
||||
break;
|
||||
case "win32":
|
||||
const [index, description] = getBadgeIndexAndDescription(count);
|
||||
if (lastIndex === index) break;
|
||||
|
||||
lastIndex = index;
|
||||
|
||||
// circular import shenanigans
|
||||
const { mainWin } = require("./mainWindow") as typeof import("./mainWindow");
|
||||
mainWin.setOverlayIcon(index === null ? null : loadBadge(index), description);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
function getBadgeIndexAndDescription(count: number): [number | null, string] {
|
||||
if (count === -1) return [11, "Unread Messages"];
|
||||
if (count === 0) return [null, "No Notifications"];
|
||||
|
||||
const index = Math.max(1, Math.min(count, 10));
|
||||
return [index, `${index} Notification`];
|
||||
}
|
||||
49
src/main/autoStart.ts
Normal file
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* SPDX-License-Identifier: GPL-3.0
|
||||
* Vencord Desktop, a desktop app aiming to give you a snappier Discord Experience
|
||||
* Copyright (c) 2023 Vendicated and Vencord contributors
|
||||
*/
|
||||
|
||||
import { app } from "electron";
|
||||
import { existsSync, mkdirSync, rmSync, writeFileSync } from "fs";
|
||||
import { join } from "path";
|
||||
|
||||
interface AutoStart {
|
||||
isEnabled(): boolean;
|
||||
enable(): void;
|
||||
disable(): void;
|
||||
}
|
||||
|
||||
function makeAutoStartLinux(): AutoStart {
|
||||
const configDir = process.env.XDG_CONFIG_HOME || join(process.env.HOME!, ".config");
|
||||
const dir = join(configDir, "autostart");
|
||||
const file = join(dir, "vencord.desktop");
|
||||
|
||||
return {
|
||||
isEnabled: () => existsSync(file),
|
||||
enable() {
|
||||
const desktopFile = `
|
||||
[Desktop Entry]
|
||||
Type=Application
|
||||
Version=1.0
|
||||
Name=Vencord
|
||||
Comment=Vencord autostart script
|
||||
Exec=${process.execPath}
|
||||
Terminal=false
|
||||
StartupNotify=false
|
||||
`.trim();
|
||||
|
||||
mkdirSync(dir, { recursive: true });
|
||||
writeFileSync(file, desktopFile);
|
||||
},
|
||||
disable: () => rmSync(file, { force: true })
|
||||
};
|
||||
}
|
||||
|
||||
const autoStartWindowsMac: AutoStart = {
|
||||
isEnabled: () => app.getLoginItemSettings().openAtLogin,
|
||||
enable: () => app.setLoginItemSettings({ openAtLogin: true }),
|
||||
disable: () => app.setLoginItemSettings({ openAtLogin: false })
|
||||
};
|
||||
|
||||
export const autoStart = process.platform === "linux" ? makeAutoStartLinux() : autoStartWindowsMac;
|
||||
@@ -17,7 +17,7 @@ export const VENCORD_SETTINGS_FILE = join(VENCORD_SETTINGS_DIR, "settings.json")
|
||||
export const VENCORD_FILES_DIR =
|
||||
(require("./settings") as typeof import("./settings")).Settings.store.vencordDir || join(DATA_DIR, "vencordDist");
|
||||
|
||||
export const USER_AGENT = `VencordDesktop/${app.getVersion()} (https://github.com/Vencord/Electron)`;
|
||||
export const USER_AGENT = `VencordDesktop/${app.getVersion()} (https://github.com/Vencord/Desktop)`;
|
||||
|
||||
// dimensions shamelessly stolen from Discord Desktop :3
|
||||
export const MIN_WIDTH = 940;
|
||||
|
||||
@@ -9,31 +9,46 @@ import { BrowserWindow } from "electron/main";
|
||||
import { copyFileSync, mkdirSync, readdirSync } from "fs";
|
||||
import { join } from "path";
|
||||
import { SplashProps } from "shared/browserWinProperties";
|
||||
import { STATIC_DIR } from "shared/paths";
|
||||
import { VIEW_DIR } from "shared/paths";
|
||||
|
||||
import { autoStart } from "./autoStart";
|
||||
import { DATA_DIR } from "./constants";
|
||||
import { createWindows } from "./mainWindow";
|
||||
import { Settings } from "./settings";
|
||||
import { makeLinksOpenExternally } from "./utils/makeLinksOpenExternally";
|
||||
|
||||
interface Data {
|
||||
minimizeToTray: boolean;
|
||||
discordBranch: "stable" | "canary" | "ptb";
|
||||
autoStart: boolean;
|
||||
importSettings: boolean;
|
||||
richPresence: boolean;
|
||||
}
|
||||
|
||||
export function createFirstLaunchTour() {
|
||||
const win = new BrowserWindow({
|
||||
...SplashProps,
|
||||
frame: true,
|
||||
autoHideMenuBar: true,
|
||||
height: 320,
|
||||
height: 470,
|
||||
width: 550
|
||||
});
|
||||
|
||||
win.loadFile(join(STATIC_DIR, "first-launch.html"));
|
||||
makeLinksOpenExternally(win);
|
||||
|
||||
win.loadFile(join(VIEW_DIR, "first-launch.html"));
|
||||
win.webContents.addListener("console-message", (_e, _l, msg) => {
|
||||
if (msg === "cancel") return app.exit();
|
||||
|
||||
if (!msg.startsWith("form:")) return;
|
||||
const data = JSON.parse(msg.slice(5));
|
||||
const data = JSON.parse(msg.slice(5)) as Data;
|
||||
|
||||
Settings.store.minimizeToTray = data.minimizeToTray;
|
||||
Settings.store.discordBranch = data.discordBranch;
|
||||
Settings.store.firstLaunch = false;
|
||||
Settings.store.arRPC = data.richPresence;
|
||||
|
||||
if (data.autoStart) autoStart.enable();
|
||||
|
||||
if (data.importSettings) {
|
||||
const from = join(app.getPath("userData"), "..", "Vencord", "settings");
|
||||
|
||||
@@ -13,6 +13,7 @@ import { ICON_PATH } from "../shared/paths";
|
||||
import { DATA_DIR } from "./constants";
|
||||
import { createFirstLaunchTour } from "./firstLaunch";
|
||||
import { createWindows, mainWin } from "./mainWindow";
|
||||
import { registerScreenShareHandler } from "./screenShare";
|
||||
import { Settings } from "./settings";
|
||||
|
||||
if (IS_DEV) {
|
||||
@@ -23,20 +24,18 @@ if (IS_DEV) {
|
||||
process.env.VENCORD_USER_DATA_DIR = DATA_DIR;
|
||||
|
||||
function init() {
|
||||
// <-- BEGIN COPY PASTED FROM DISCORD -->
|
||||
|
||||
// work around chrome 66 disabling autoplay by default
|
||||
app.commandLine.appendSwitch("autoplay-policy", "no-user-gesture-required");
|
||||
|
||||
// WinRetrieveSuggestionsOnlyOnDemand: Work around electron 13 bug w/ async spellchecking on Windows.
|
||||
// HardwareMediaKeyHandling,MediaSessionService: Prevent Discord from registering as a media service.
|
||||
//
|
||||
// WidgetLayering (Vencord Added): Fix DevTools context menus https://github.com/electron/electron/issues/38790
|
||||
app.commandLine.appendSwitch(
|
||||
"disable-features",
|
||||
"WinRetrieveSuggestionsOnlyOnDemand,HardwareMediaKeyHandling,MediaSessionService"
|
||||
"WinRetrieveSuggestionsOnlyOnDemand,HardwareMediaKeyHandling,MediaSessionService,WidgetLayering"
|
||||
);
|
||||
|
||||
// <-- END COPY PASTED FROM DISCORD -->
|
||||
|
||||
app.on("second-instance", (_event, _cmdLine, _cwd, data: any) => {
|
||||
if (data.IS_DEV) app.quit();
|
||||
else if (mainWin) {
|
||||
@@ -51,6 +50,7 @@ function init() {
|
||||
if (process.platform === "win32") app.setAppUserModelId("dev.vencord.desktop");
|
||||
else if (process.platform === "darwin") app.dock.setIcon(ICON_PATH);
|
||||
|
||||
registerScreenShareHandler();
|
||||
bootstrap();
|
||||
|
||||
app.on("activate", () => {
|
||||
|
||||
@@ -11,6 +11,8 @@ import { join } from "path";
|
||||
import { debounce } from "shared/utils/debounce";
|
||||
|
||||
import { IpcEvents } from "../shared/IpcEvents";
|
||||
import { setBadgeCount } from "./appBadge";
|
||||
import { autoStart } from "./autoStart";
|
||||
import { VENCORD_FILES_DIR, VENCORD_QUICKCSS_FILE } from "./constants";
|
||||
import { mainWin } from "./mainWindow";
|
||||
import { Settings } from "./settings";
|
||||
@@ -39,6 +41,12 @@ ipcMain.on(IpcEvents.GET_VERSION, e => {
|
||||
e.returnValue = app.getVersion();
|
||||
});
|
||||
|
||||
ipcMain.on(IpcEvents.AUTOSTART_ENABLED, e => {
|
||||
e.returnValue = autoStart.isEnabled();
|
||||
});
|
||||
ipcMain.handle(IpcEvents.ENABLE_AUTOSTART, autoStart.enable);
|
||||
ipcMain.handle(IpcEvents.DISABLE_AUTOSTART, autoStart.disable);
|
||||
|
||||
ipcMain.handle(IpcEvents.SET_SETTINGS, (_, settings: typeof Settings.store, path?: string) => {
|
||||
Settings.setData(settings, path);
|
||||
});
|
||||
@@ -68,6 +76,14 @@ ipcMain.handle(IpcEvents.SPELLCHECK_SET_LANGUAGES, (_, languages: string[]) => {
|
||||
if (applicable.length) ses.setSpellCheckerLanguages(applicable);
|
||||
});
|
||||
|
||||
ipcMain.handle(IpcEvents.SPELLCHECK_REPLACE_MISSPELLING, (e, word: string) => {
|
||||
e.sender.replaceMisspelling(word);
|
||||
});
|
||||
|
||||
ipcMain.handle(IpcEvents.SPELLCHECK_ADD_TO_DICTIONARY, (e, word: string) => {
|
||||
e.sender.session.addWordToSpellCheckerDictionary(word);
|
||||
});
|
||||
|
||||
ipcMain.handle(IpcEvents.SELECT_VENCORD_DIR, async () => {
|
||||
const res = await dialog.showOpenDialog(mainWin!, {
|
||||
properties: ["openDirectory"]
|
||||
@@ -82,6 +98,8 @@ ipcMain.handle(IpcEvents.SELECT_VENCORD_DIR, async () => {
|
||||
return dir;
|
||||
});
|
||||
|
||||
ipcMain.handle(IpcEvents.SET_BADGE_COUNT, (_, count: number) => setBadgeCount(count));
|
||||
|
||||
function readCss() {
|
||||
return readFile(VENCORD_QUICKCSS_FILE, "utf-8").catch(() => "");
|
||||
}
|
||||
|
||||
@@ -6,7 +6,9 @@
|
||||
|
||||
import { app, BrowserWindow, BrowserWindowConstructorOptions, Menu, Tray } from "electron";
|
||||
import { join } from "path";
|
||||
import { IpcEvents } from "shared/IpcEvents";
|
||||
import { once } from "shared/utils/once";
|
||||
import type { SettingsStore } from "shared/utils/SettingsStore";
|
||||
|
||||
import { ICON_PATH } from "../shared/paths";
|
||||
import { createAboutWindow } from "./about";
|
||||
@@ -26,6 +28,27 @@ app.on("before-quit", () => {
|
||||
|
||||
export let mainWin: BrowserWindow;
|
||||
|
||||
function makeSettingsListenerHelpers<O extends object>(o: SettingsStore<O>) {
|
||||
const listeners = new Map<(data: any) => void, PropertyKey>();
|
||||
|
||||
const addListener: typeof o.addChangeListener = (path, cb) => {
|
||||
listeners.set(cb, path);
|
||||
o.addChangeListener(path, cb);
|
||||
};
|
||||
const removeAllListeners = () => {
|
||||
for (const [listener, path] of listeners) {
|
||||
o.removeChangeListener(path as any, listener);
|
||||
}
|
||||
|
||||
listeners.clear();
|
||||
};
|
||||
|
||||
return [addListener, removeAllListeners] as const;
|
||||
}
|
||||
|
||||
const [addSettingsListener, removeSettingsListeners] = makeSettingsListenerHelpers(Settings);
|
||||
const [addVencordSettingsListener, removeVencordSettingsListeners] = makeSettingsListenerHelpers(VencordSettings);
|
||||
|
||||
function initTray(win: BrowserWindow) {
|
||||
const trayMenu = Menu.buildFromTemplate([
|
||||
{
|
||||
@@ -186,11 +209,11 @@ function initWindowBoundsListeners(win: BrowserWindow) {
|
||||
}
|
||||
|
||||
function initSettingsListeners(win: BrowserWindow) {
|
||||
Settings.addChangeListener("tray", enable => {
|
||||
addSettingsListener("tray", enable => {
|
||||
if (enable) initTray(win);
|
||||
else tray?.destroy();
|
||||
});
|
||||
Settings.addChangeListener("disableMinSize", disable => {
|
||||
addSettingsListener("disableMinSize", disable => {
|
||||
if (disable) {
|
||||
// 0 no work
|
||||
win.setMinimumSize(1, 1);
|
||||
@@ -205,7 +228,7 @@ function initSettingsListeners(win: BrowserWindow) {
|
||||
}
|
||||
});
|
||||
|
||||
VencordSettings.addChangeListener("macosTranslucency", enabled => {
|
||||
addVencordSettingsListener("macosTranslucency", enabled => {
|
||||
if (enabled) {
|
||||
win.setVibrancy("sidebar");
|
||||
win.setBackgroundColor("#ffffff00");
|
||||
@@ -216,7 +239,17 @@ function initSettingsListeners(win: BrowserWindow) {
|
||||
});
|
||||
}
|
||||
|
||||
function initSpellCheck(win: BrowserWindow) {
|
||||
win.webContents.on("context-menu", (_, data) => {
|
||||
win.webContents.send(IpcEvents.SPELLCHECK_RESULT, data.misspelledWord, data.dictionarySuggestions);
|
||||
});
|
||||
}
|
||||
|
||||
function createMainWindow() {
|
||||
// Clear up previous settings listeners
|
||||
removeSettingsListeners();
|
||||
removeVencordSettingsListeners();
|
||||
|
||||
const win = (mainWin = new BrowserWindow({
|
||||
show: false,
|
||||
autoHideMenuBar: true,
|
||||
@@ -225,7 +258,8 @@ function createMainWindow() {
|
||||
sandbox: false,
|
||||
contextIsolation: true,
|
||||
devTools: true,
|
||||
preload: join(__dirname, "preload.js")
|
||||
preload: join(__dirname, "preload.js"),
|
||||
spellcheck: true
|
||||
},
|
||||
icon: ICON_PATH,
|
||||
frame: VencordSettings.store.frameless !== true,
|
||||
@@ -255,9 +289,10 @@ function createMainWindow() {
|
||||
initMenuBar(win);
|
||||
makeLinksOpenExternally(win);
|
||||
initSettingsListeners(win);
|
||||
initSpellCheck(win);
|
||||
|
||||
win.webContents.setUserAgent(
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/111.0.0.0 Safari/537.36"
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36"
|
||||
);
|
||||
|
||||
const subdomain =
|
||||
|
||||
55
src/main/screenShare.ts
Normal file
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* SPDX-License-Identifier: GPL-3.0
|
||||
* Vencord Desktop, a desktop app aiming to give you a snappier Discord Experience
|
||||
* Copyright (c) 2023 Vendicated and Vencord contributors
|
||||
*/
|
||||
|
||||
import { desktopCapturer, ipcMain, session, Streams } from "electron";
|
||||
import type { StreamPick } from "renderer/components/ScreenSharePicker";
|
||||
import { IpcEvents } from "shared/IpcEvents";
|
||||
|
||||
export function registerScreenShareHandler() {
|
||||
ipcMain.handle(IpcEvents.CAPTURER_GET_LARGE_THUMBNAIL, async (_, id: string) => {
|
||||
const sources = await desktopCapturer.getSources({
|
||||
types: ["window", "screen"],
|
||||
thumbnailSize: {
|
||||
width: 1920,
|
||||
height: 1080
|
||||
}
|
||||
});
|
||||
return sources.find(s => s.id === id)?.thumbnail.toDataURL();
|
||||
});
|
||||
|
||||
session.defaultSession.setDisplayMediaRequestHandler(async (request, callback) => {
|
||||
const sources = await desktopCapturer.getSources({
|
||||
types: ["window", "screen"],
|
||||
thumbnailSize: {
|
||||
width: 176,
|
||||
height: 99
|
||||
}
|
||||
});
|
||||
|
||||
const data = sources.map(({ id, name, thumbnail }) => ({
|
||||
id,
|
||||
name,
|
||||
url: thumbnail.toDataURL()
|
||||
}));
|
||||
|
||||
const choice = await request.frame
|
||||
.executeJavaScript(`VencordDesktop.Components.ScreenShare.openScreenSharePicker(${JSON.stringify(data)})`)
|
||||
.then(e => e as StreamPick)
|
||||
.catch(() => null);
|
||||
|
||||
if (!choice) return callback({});
|
||||
|
||||
const source = sources.find(s => s.id === choice.id);
|
||||
if (!source) return callback({});
|
||||
|
||||
const streams: Streams = {
|
||||
video: source
|
||||
};
|
||||
if (choice.audio && process.platform === "win32") streams.audio = "loopback";
|
||||
|
||||
callback(streams);
|
||||
});
|
||||
}
|
||||
@@ -4,8 +4,8 @@
|
||||
* Copyright (c) 2023 Vendicated and Vencord contributors
|
||||
*/
|
||||
|
||||
import { readFileSync, writeFileSync } from "fs";
|
||||
import { join } from "path";
|
||||
import { mkdirSync, readFileSync, writeFileSync } from "fs";
|
||||
import { dirname, join } from "path";
|
||||
import type { Settings as TSettings } from "shared/settings";
|
||||
import { SettingsStore } from "shared/utils/SettingsStore";
|
||||
|
||||
@@ -25,7 +25,10 @@ function loadSettings<T extends object = any>(file: string, name: string) {
|
||||
} catch {}
|
||||
|
||||
const store = new SettingsStore(settings);
|
||||
store.addGlobalChangeListener(o => writeFileSync(file, JSON.stringify(o, null, 4)));
|
||||
store.addGlobalChangeListener(o => {
|
||||
mkdirSync(dirname(file), { recursive: true });
|
||||
writeFileSync(file, JSON.stringify(o, null, 4));
|
||||
});
|
||||
|
||||
return store;
|
||||
}
|
||||
|
||||
@@ -7,12 +7,12 @@
|
||||
import { BrowserWindow } from "electron";
|
||||
import { join } from "path";
|
||||
import { SplashProps } from "shared/browserWinProperties";
|
||||
import { STATIC_DIR } from "shared/paths";
|
||||
import { VIEW_DIR } from "shared/paths";
|
||||
|
||||
export function createSplashWindow() {
|
||||
const splash = new BrowserWindow(SplashProps);
|
||||
|
||||
splash.loadFile(join(STATIC_DIR, "splash.html"));
|
||||
splash.loadFile(join(VIEW_DIR, "splash.html"));
|
||||
|
||||
return splash;
|
||||
}
|
||||
|
||||
@@ -4,16 +4,31 @@
|
||||
* Copyright (c) 2023 Vendicated and Vencord contributors
|
||||
*/
|
||||
|
||||
import { ipcRenderer } from "electron";
|
||||
import type { Settings } from "shared/settings";
|
||||
import type { LiteralUnion } from "type-fest";
|
||||
|
||||
import { IpcEvents } from "../shared/IpcEvents";
|
||||
import { invoke, sendSync } from "./typedIpcs";
|
||||
import { invoke, sendSync } from "./typedIpc";
|
||||
|
||||
type SpellCheckerResultCallback = (word: string, suggestions: string[]) => void;
|
||||
|
||||
const spellCheckCallbacks = new Set<SpellCheckerResultCallback>();
|
||||
|
||||
ipcRenderer.on(IpcEvents.SPELLCHECK_RESULT, (_, w: string, s: string[]) => {
|
||||
spellCheckCallbacks.forEach(cb => cb(w, s));
|
||||
});
|
||||
|
||||
export const VencordDesktopNative = {
|
||||
app: {
|
||||
relaunch: () => invoke<void>(IpcEvents.RELAUNCH),
|
||||
getVersion: () => sendSync<void>(IpcEvents.GET_VERSION)
|
||||
getVersion: () => sendSync<void>(IpcEvents.GET_VERSION),
|
||||
setBadgeCount: (count: number) => invoke<void>(IpcEvents.SET_BADGE_COUNT, count)
|
||||
},
|
||||
autostart: {
|
||||
isEnabled: () => sendSync<boolean>(IpcEvents.AUTOSTART_ENABLED),
|
||||
enable: () => invoke<void>(IpcEvents.ENABLE_AUTOSTART),
|
||||
disable: () => invoke<void>(IpcEvents.DISABLE_AUTOSTART)
|
||||
},
|
||||
fileManager: {
|
||||
showItemInFolder: (path: string) => invoke<void>(IpcEvents.SHOW_ITEM_IN_FOLDER, path),
|
||||
@@ -24,10 +39,20 @@ export const VencordDesktopNative = {
|
||||
set: (settings: Settings, path?: string) => invoke<void>(IpcEvents.SET_SETTINGS, settings, path)
|
||||
},
|
||||
spellcheck: {
|
||||
setLanguages: (languages: readonly string[]) => invoke<void>(IpcEvents.SPELLCHECK_SET_LANGUAGES, languages)
|
||||
// todo: perhaps add ways to learn words
|
||||
setLanguages: (languages: readonly string[]) => invoke<void>(IpcEvents.SPELLCHECK_SET_LANGUAGES, languages),
|
||||
onSpellcheckResult(cb: SpellCheckerResultCallback) {
|
||||
spellCheckCallbacks.add(cb);
|
||||
},
|
||||
offSpellcheckResult(cb: SpellCheckerResultCallback) {
|
||||
spellCheckCallbacks.delete(cb);
|
||||
},
|
||||
replaceMisspelling: (word: string) => invoke<void>(IpcEvents.SPELLCHECK_REPLACE_MISSPELLING, word),
|
||||
addToDictionary: (word: string) => invoke<void>(IpcEvents.SPELLCHECK_ADD_TO_DICTIONARY, word)
|
||||
},
|
||||
win: {
|
||||
focus: () => invoke<void>(IpcEvents.FOCUS)
|
||||
},
|
||||
capturer: {
|
||||
getLargeThumbnail: (id: string) => invoke<string>(IpcEvents.CAPTURER_GET_LARGE_THUMBNAIL, id)
|
||||
}
|
||||
};
|
||||
|
||||
43
src/renderer/appBadge.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* SPDX-License-Identifier: GPL-3.0
|
||||
* Vencord Desktop, a desktop app aiming to give you a snappier Discord Experience
|
||||
* Copyright (c) 2023 Vendicated and Vencord contributors
|
||||
*/
|
||||
|
||||
import { filters, waitFor } from "@vencord/types/webpack";
|
||||
import { RelationshipStore } from "@vencord/types/webpack/common";
|
||||
|
||||
import { Settings } from "./settings";
|
||||
|
||||
let GuildReadStateStore: any;
|
||||
let NotificationSettingsStore: any;
|
||||
|
||||
export function setBadge() {
|
||||
if (Settings.store.appBadge === false) return;
|
||||
|
||||
const mentionCount = GuildReadStateStore.getTotalMentionCount();
|
||||
const pendingRequests = RelationshipStore.getPendingCount();
|
||||
const hasUnread = GuildReadStateStore.hasAnyUnread();
|
||||
const disableUnreadBadge = NotificationSettingsStore.getDisableUnreadBadge();
|
||||
|
||||
let totalCount = mentionCount + pendingRequests;
|
||||
if (!totalCount && hasUnread && !disableUnreadBadge) totalCount = -1;
|
||||
|
||||
VencordDesktopNative.app.setBadgeCount(totalCount);
|
||||
}
|
||||
|
||||
let toFind = 3;
|
||||
|
||||
function waitForAndSubscribeToStore(name: string, cb?: (m: any) => void) {
|
||||
waitFor(filters.byStoreName(name), store => {
|
||||
cb?.(store);
|
||||
store.addChangeListener(setBadge);
|
||||
|
||||
toFind--;
|
||||
if (toFind === 0) setBadge();
|
||||
});
|
||||
}
|
||||
|
||||
waitForAndSubscribeToStore("GuildReadStateStore", store => (GuildReadStateStore = store));
|
||||
waitForAndSubscribeToStore("NotificationSettingsStore", store => (NotificationSettingsStore = store));
|
||||
waitForAndSubscribeToStore("RelationshipStore");
|
||||
225
src/renderer/components/ScreenSharePicker.tsx
Normal file
@@ -0,0 +1,225 @@
|
||||
/*
|
||||
* SPDX-License-Identifier: GPL-3.0
|
||||
* Vencord Desktop, a desktop app aiming to give you a snappier Discord Experience
|
||||
* Copyright (c) 2023 Vendicated and Vencord contributors
|
||||
*/
|
||||
|
||||
import "./screenSharePicker.css";
|
||||
|
||||
import { classes, closeModal, Margins, Modals, openModal, useAwaiter } from "@vencord/types/utils";
|
||||
import { findByPropsLazy } from "@vencord/types/webpack";
|
||||
import { Button, Card, Forms, Switch, Text, useState } from "@vencord/types/webpack/common";
|
||||
import type { Dispatch, SetStateAction } from "react";
|
||||
import { isWindows } from "renderer/utils";
|
||||
|
||||
const StreamResolutions = ["720", "1080", "1440", "Source"] as const;
|
||||
const StreamFps = ["15", "30", "60"] as const;
|
||||
|
||||
const WarningIconClasses = findByPropsLazy("warning", "error", "container");
|
||||
|
||||
export type StreamResolution = (typeof StreamResolutions)[number];
|
||||
export type StreamFps = (typeof StreamFps)[number];
|
||||
|
||||
interface StreamSettings {
|
||||
resolution: StreamResolution;
|
||||
fps: StreamFps;
|
||||
audio: boolean;
|
||||
}
|
||||
|
||||
export interface StreamPick extends StreamSettings {
|
||||
id: string;
|
||||
}
|
||||
|
||||
interface Source {
|
||||
id: string;
|
||||
name: string;
|
||||
url: string;
|
||||
}
|
||||
|
||||
export function openScreenSharePicker(screens: Source[]) {
|
||||
return new Promise<StreamPick>((resolve, reject) => {
|
||||
const key = openModal(
|
||||
props => (
|
||||
<ModalComponent
|
||||
screens={screens}
|
||||
modalProps={props}
|
||||
submit={resolve}
|
||||
close={() => {
|
||||
props.onClose();
|
||||
reject("Aborted");
|
||||
}}
|
||||
/>
|
||||
),
|
||||
{
|
||||
onCloseRequest() {
|
||||
closeModal(key);
|
||||
reject("Aborted");
|
||||
}
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
function ScreenPicker({ screens, chooseScreen }: { screens: Source[]; chooseScreen: (id: string) => void }) {
|
||||
return (
|
||||
<div className="vcd-screen-picker-grid">
|
||||
{screens.map(({ id, name, url }) => (
|
||||
<label key={id}>
|
||||
<input type="radio" name="screen" value={id} onChange={() => chooseScreen(id)} />
|
||||
|
||||
<img src={url} alt="" />
|
||||
<Text variant="text-sm/normal">{name}</Text>
|
||||
</label>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function StreamSettings({
|
||||
source,
|
||||
settings,
|
||||
setSettings
|
||||
}: {
|
||||
source: Source;
|
||||
settings: StreamSettings;
|
||||
setSettings: Dispatch<SetStateAction<StreamSettings>>;
|
||||
}) {
|
||||
const [thumb] = useAwaiter(() => VencordDesktopNative.capturer.getLargeThumbnail(source.id), {
|
||||
fallbackValue: source.url,
|
||||
deps: [source.id]
|
||||
});
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Forms.FormTitle>What you're streaming</Forms.FormTitle>
|
||||
<Card className="vcd-screen-picker-card vcd-screen-picker-preview">
|
||||
<img src={thumb} alt="" />
|
||||
<Text variant="text-sm/normal">{source.name}</Text>
|
||||
</Card>
|
||||
|
||||
<Forms.FormTitle>Stream Settings</Forms.FormTitle>
|
||||
|
||||
<Card className="vcd-screen-picker-card">
|
||||
<Card className={classes(WarningIconClasses.container, WarningIconClasses.warning, Margins.bottom8)}>
|
||||
<Forms.FormText>
|
||||
Resolution and Frame Rate aren't implemented for now. Locked to 720p 30fps
|
||||
</Forms.FormText>
|
||||
</Card>
|
||||
|
||||
<div className="vcd-screen-picker-quality">
|
||||
<section>
|
||||
<Forms.FormTitle>Resolution</Forms.FormTitle>
|
||||
<div className="vcd-screen-picker-radios">
|
||||
{StreamResolutions.map(res => (
|
||||
<label className="vcd-screen-picker-radio" data-checked={settings.resolution === res}>
|
||||
<Text variant="text-sm/bold">{res}</Text>
|
||||
<input
|
||||
type="radio"
|
||||
name="resolution"
|
||||
value={res}
|
||||
checked={settings.resolution === res}
|
||||
onChange={() => setSettings(s => ({ ...s, resolution: res }))}
|
||||
/>
|
||||
</label>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<Forms.FormTitle>Frame Rate</Forms.FormTitle>
|
||||
<div className="vcd-screen-picker-radios">
|
||||
{StreamFps.map(fps => (
|
||||
<label className="vcd-screen-picker-radio" data-checked={settings.fps === fps}>
|
||||
<Text variant="text-sm/bold">{fps}</Text>
|
||||
<input
|
||||
type="radio"
|
||||
name="fps"
|
||||
value={fps}
|
||||
checked={settings.fps === fps}
|
||||
onChange={() => setSettings(s => ({ ...s, fps }))}
|
||||
/>
|
||||
</label>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
{isWindows && (
|
||||
<Switch
|
||||
value={settings.audio}
|
||||
onChange={checked => setSettings(s => ({ ...s, audio: checked }))}
|
||||
hideBorder
|
||||
className="vcd-screen-picker-audio"
|
||||
>
|
||||
Stream With Audio
|
||||
</Switch>
|
||||
)}
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function ModalComponent({
|
||||
screens,
|
||||
modalProps,
|
||||
submit,
|
||||
close
|
||||
}: {
|
||||
screens: Source[];
|
||||
modalProps: any;
|
||||
submit: (data: StreamPick) => void;
|
||||
close: () => void;
|
||||
}) {
|
||||
const [selected, setSelected] = useState<string>();
|
||||
const [settings, setSettings] = useState<StreamSettings>({
|
||||
resolution: "1080",
|
||||
fps: "60",
|
||||
audio: true
|
||||
});
|
||||
|
||||
return (
|
||||
<Modals.ModalRoot {...modalProps}>
|
||||
<Modals.ModalHeader className="vcd-screen-picker-header">
|
||||
<Forms.FormTitle tag="h2">ScreenShare</Forms.FormTitle>
|
||||
<Modals.ModalCloseButton onClick={close} />
|
||||
</Modals.ModalHeader>
|
||||
|
||||
<Modals.ModalContent className="vcd-screen-picker-modal">
|
||||
{!selected ? (
|
||||
<ScreenPicker screens={screens} chooseScreen={setSelected} />
|
||||
) : (
|
||||
<StreamSettings
|
||||
source={screens.find(s => s.id === selected)!}
|
||||
settings={settings}
|
||||
setSettings={setSettings}
|
||||
/>
|
||||
)}
|
||||
</Modals.ModalContent>
|
||||
|
||||
<Modals.ModalFooter className="vcd-screen-picker-footer">
|
||||
<Button
|
||||
disabled={!selected}
|
||||
onClick={() => {
|
||||
submit({
|
||||
id: selected!,
|
||||
...settings
|
||||
});
|
||||
close();
|
||||
}}
|
||||
>
|
||||
Go Live
|
||||
</Button>
|
||||
|
||||
{selected ? (
|
||||
<Button color={Button.Colors.TRANSPARENT} onClick={() => setSelected(void 0)}>
|
||||
Back
|
||||
</Button>
|
||||
) : (
|
||||
<Button color={Button.Colors.TRANSPARENT} onClick={close}>
|
||||
Cancel
|
||||
</Button>
|
||||
)}
|
||||
</Modals.ModalFooter>
|
||||
</Modals.ModalRoot>
|
||||
);
|
||||
}
|
||||
@@ -7,12 +7,16 @@
|
||||
import "./settings.css";
|
||||
|
||||
import { Margins } from "@vencord/types/utils";
|
||||
import { Button, Forms, Select, Switch, Text } from "@vencord/types/webpack/common";
|
||||
import { Button, Forms, Select, Switch, Text, useState } from "@vencord/types/webpack/common";
|
||||
import { setBadge } from "renderer/appBadge";
|
||||
import { useSettings } from "renderer/settings";
|
||||
|
||||
export default function SettingsUi() {
|
||||
const Settings = useSettings();
|
||||
|
||||
const { autostart } = VencordDesktopNative;
|
||||
const [autoStartEnabled, setAutoStartEnabled] = useState(autostart.isEnabled());
|
||||
|
||||
const switches: [keyof typeof Settings, string, string, boolean?, (() => boolean)?][] = [
|
||||
["tray", "Tray Icon", "Add a tray icon for Vencord Desktop", true],
|
||||
[
|
||||
@@ -58,6 +62,29 @@ export default function SettingsUi() {
|
||||
|
||||
<Forms.FormDivider className={Margins.top16 + " " + Margins.bottom16} />
|
||||
|
||||
<Switch
|
||||
value={autoStartEnabled}
|
||||
onChange={async v => {
|
||||
await autostart[v ? "enable" : "disable"]();
|
||||
setAutoStartEnabled(v);
|
||||
}}
|
||||
note="Automatically start Vencord Desktop on computer start-up"
|
||||
>
|
||||
Start With System
|
||||
</Switch>
|
||||
|
||||
<Switch
|
||||
value={Settings.appBadge ?? true}
|
||||
onChange={v => {
|
||||
Settings.appBadge = v;
|
||||
if (v) setBadge();
|
||||
else VencordDesktopNative.app.setBadgeCount(0);
|
||||
}}
|
||||
note="Show mention badge on the app icon"
|
||||
>
|
||||
Notification Badge
|
||||
</Switch>
|
||||
|
||||
{switches.map(([key, text, note, def, predicate]) => (
|
||||
<Switch
|
||||
value={(Settings[key as any] ?? def ?? false) && predicate?.() !== false}
|
||||
|
||||
@@ -4,4 +4,5 @@
|
||||
* Copyright (c) 2023 Vendicated and Vencord contributors
|
||||
*/
|
||||
|
||||
export * as ScreenShare from "./ScreenSharePicker";
|
||||
export { default as Settings } from "./Settings";
|
||||
|
||||
126
src/renderer/components/screenSharePicker.css
Normal file
@@ -0,0 +1,126 @@
|
||||
.vcd-screen-picker-modal {
|
||||
padding: 1em;
|
||||
}
|
||||
|
||||
.vcd-screen-picker-header h1 {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.vcd-screen-picker-footer {
|
||||
display: flex;
|
||||
gap: 1em;
|
||||
}
|
||||
|
||||
.vcd-screen-picker-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 2em 1em;
|
||||
}
|
||||
|
||||
.vcd-screen-picker-grid input {
|
||||
appearance: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.vcd-screen-picker-selected img {
|
||||
border: 2px solid var(--brand-experiment);
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.vcd-screen-picker-grid label {
|
||||
overflow: hidden;
|
||||
padding: 4px 0px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.vcd-screen-picker-grid label:hover {
|
||||
outline: 2px solid var(--brand-experiment);
|
||||
}
|
||||
|
||||
|
||||
.vcd-screen-picker-grid div {
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
text-align: center;
|
||||
font-weight: 600;
|
||||
margin-inline: 0.5em;
|
||||
}
|
||||
|
||||
.vcd-screen-picker-card {
|
||||
padding: 0.5em;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.vcd-screen-picker-preview img {
|
||||
width: 100%;
|
||||
margin-bottom: 0.5em;
|
||||
}
|
||||
|
||||
.vcd-screen-picker-preview {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
.vcd-screen-picker-radio input {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.vcd-screen-picker-radio {
|
||||
background-color: var(--background-secondary);
|
||||
border: 1px solid var(--primary-800);
|
||||
padding: 0.3em;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.vcd-screen-picker-radio h2 {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.vcd-screen-picker-radio[data-checked="true"] {
|
||||
background-color: var(--brand-experiment);
|
||||
border-color: var(--brand-experiment);
|
||||
}
|
||||
|
||||
.vcd-screen-picker-radio[data-checked="true"] h2 {
|
||||
color: var(--interactive-active);
|
||||
}
|
||||
|
||||
.vcd-screen-picker-quality {
|
||||
display: flex;
|
||||
gap: 1em;
|
||||
|
||||
margin-bottom: 0.5em;
|
||||
|
||||
opacity: 0.3;
|
||||
}
|
||||
|
||||
.vcd-screen-picker-quality section {
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
|
||||
.vcd-screen-picker-radios {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.vcd-screen-picker-radios label {
|
||||
flex: 1 1 auto;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.vcd-screen-picker-radios label:first-child {
|
||||
border-radius: 3px 0 0 3px;
|
||||
}
|
||||
|
||||
.vcd-screen-picker-radios label:last-child {
|
||||
border-radius: 0 3px 3px 0;
|
||||
}
|
||||
|
||||
.vcd-screen-picker-audio {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
@@ -22,11 +22,11 @@ Object.defineProperty(Notification.prototype, "onclick", {
|
||||
configurable: true
|
||||
});
|
||||
|
||||
// Enable Desktop Notifications by default
|
||||
if (isFirstRun) {
|
||||
// Hide "Download Discord Desktop now!!!!" banner
|
||||
localStorage.setItem("hideNag", "true");
|
||||
|
||||
// Enable Desktop Notifications by default
|
||||
waitFor("setDesktopType", m => {
|
||||
m.setDesktopType("all");
|
||||
});
|
||||
|
||||
@@ -5,6 +5,8 @@
|
||||
*/
|
||||
|
||||
import "./fixes";
|
||||
import "./appBadge";
|
||||
import "./patches";
|
||||
|
||||
console.log("read if cute :3");
|
||||
|
||||
|
||||
8
src/renderer/patches/index.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
/*
|
||||
* SPDX-License-Identifier: GPL-3.0
|
||||
* Vencord Desktop, a desktop app aiming to give you a snappier Discord Experience
|
||||
* Copyright (c) 2023 Vendicated and Vencord contributors
|
||||
*/
|
||||
|
||||
// TODO: Possibly auto generate glob if we have more patches in the future
|
||||
import "./spellCheck";
|
||||
30
src/renderer/patches/shared.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* SPDX-License-Identifier: GPL-3.0
|
||||
* Vencord Desktop, a desktop app aiming to give you a snappier Discord Experience
|
||||
* Copyright (c) 2023 Vendicated and Vencord contributors
|
||||
*/
|
||||
|
||||
import { Patch } from "@vencord/types/utils/types";
|
||||
|
||||
window.VCDP = {};
|
||||
|
||||
interface PatchData {
|
||||
patches: Omit<Patch, "plugin">[];
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
export function addPatch<P extends PatchData>(p: P) {
|
||||
const { patches, ...globals } = p;
|
||||
|
||||
for (const patch of patches as Patch[]) {
|
||||
if (!Array.isArray(patch.replacement)) patch.replacement = [patch.replacement];
|
||||
for (const r of patch.replacement) {
|
||||
if (typeof r.replace === "string") r.replace = r.replace.replaceAll("$self", "VCDP");
|
||||
}
|
||||
|
||||
patch.plugin = "VencordDesktop";
|
||||
Vencord.Plugins.patches.push(patch);
|
||||
}
|
||||
|
||||
Object.assign(VCDP, globals);
|
||||
}
|
||||
83
src/renderer/patches/spellCheck.tsx
Normal file
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
* SPDX-License-Identifier: GPL-3.0
|
||||
* Vencord Desktop, a desktop app aiming to give you a snappier Discord Experience
|
||||
* Copyright (c) 2023 Vendicated and Vencord contributors
|
||||
*/
|
||||
|
||||
import { addContextMenuPatch } from "@vencord/types/api/ContextMenu";
|
||||
import { findStoreLazy } from "@vencord/types/webpack";
|
||||
import { ContextMenu, FluxDispatcher, Menu } from "@vencord/types/webpack/common";
|
||||
|
||||
import { addPatch } from "./shared";
|
||||
|
||||
let word: string;
|
||||
let corrections: string[];
|
||||
|
||||
const SpellCheckStore = findStoreLazy("SpellcheckStore");
|
||||
|
||||
// Make spellcheck suggestions work
|
||||
addPatch({
|
||||
patches: [
|
||||
{
|
||||
find: ".enableSpellCheck)",
|
||||
replacement: {
|
||||
// if (isDesktop) { DiscordNative.onSpellcheck(openMenu(props)) } else { e.preventDefault(); openMenu(props) }
|
||||
match: /else\{(.{1,3})\.preventDefault\(\);(.{1,3}\(.{1,3}\))\}(?<=:(.{1,3})\.enableSpellCheck\).+?)/,
|
||||
// ... else { $self.onSlateContext(() => openMenu(props)) }
|
||||
replace: "else {$self.onSlateContext($1, $3?.enableSpellCheck, () => $2)}"
|
||||
}
|
||||
}
|
||||
],
|
||||
|
||||
onSlateContext(e: MouseEvent, hasSpellcheck: boolean | undefined, openMenu: () => void) {
|
||||
if (!hasSpellcheck) {
|
||||
e.preventDefault();
|
||||
openMenu();
|
||||
return;
|
||||
}
|
||||
|
||||
const cb = (w: string, c: string[]) => {
|
||||
VencordDesktopNative.spellcheck.offSpellcheckResult(cb);
|
||||
word = w;
|
||||
corrections = c;
|
||||
openMenu();
|
||||
};
|
||||
VencordDesktopNative.spellcheck.onSpellcheckResult(cb);
|
||||
}
|
||||
});
|
||||
|
||||
addContextMenuPatch("textarea-context", children => () => {
|
||||
const hasCorrections = Boolean(word && corrections?.length);
|
||||
|
||||
children.push(
|
||||
<Menu.MenuGroup>
|
||||
{hasCorrections && (
|
||||
<>
|
||||
{corrections.map(c => (
|
||||
<Menu.MenuItem
|
||||
id={"vcd-spellcheck-suggestion-" + c}
|
||||
label={c}
|
||||
action={() => VencordDesktopNative.spellcheck.replaceMisspelling(c)}
|
||||
/>
|
||||
))}
|
||||
<Menu.MenuSeparator />
|
||||
<Menu.MenuItem
|
||||
id="vcd-spellcheck-learn"
|
||||
label={`Add ${word} to dictionary`}
|
||||
action={() => VencordDesktopNative.spellcheck.addToDictionary(word)}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
<Menu.MenuCheckboxItem
|
||||
id="vcd-spellcheck-enabled"
|
||||
label="Enable Spellcheck"
|
||||
checked={SpellCheckStore.isEnabled()}
|
||||
action={() => {
|
||||
FluxDispatcher.dispatch({ type: "SPELLCHECK_TOGGLE" });
|
||||
// Haven't found a good way to update state, so just close for now 🤷♀️
|
||||
ContextMenu.close();
|
||||
}}
|
||||
/>
|
||||
</Menu.MenuGroup>
|
||||
);
|
||||
});
|
||||
@@ -4,7 +4,7 @@
|
||||
* Copyright (c) 2023 Vendicated and Vencord contributors
|
||||
*/
|
||||
|
||||
export const localStorage = (window.vcdLS = window.localStorage);
|
||||
export const { localStorage } = window;
|
||||
|
||||
export const isFirstRun = (() => {
|
||||
const key = "VCD_FIRST_RUN";
|
||||
@@ -12,3 +12,7 @@ export const isFirstRun = (() => {
|
||||
localStorage.setItem(key, "false");
|
||||
return true;
|
||||
})();
|
||||
|
||||
const { platform } = navigator;
|
||||
|
||||
export const isWindows = platform.startsWith("Win");
|
||||
|
||||
@@ -10,11 +10,12 @@ export const enum IpcEvents {
|
||||
GET_RENDERER_SCRIPT = "VCD_GET_RENDERER_SCRIPT",
|
||||
GET_RENDERER_CSS_FILE = "VCD_GET_RENDERER_CSS_FILE",
|
||||
|
||||
RELAUNCH = "VCD_RELAUNCH",
|
||||
FOCUS = "VCD_FOCUS",
|
||||
|
||||
GET_VERSION = "VCD_GET_VERSION",
|
||||
|
||||
RELAUNCH = "VCD_RELAUNCH",
|
||||
CLOSE = "VCD_CLOSE",
|
||||
FOCUS = "VCD_FOCUS",
|
||||
|
||||
SHOW_ITEM_IN_FOLDER = "VCD_SHOW_ITEM_IN_FOLDER",
|
||||
GET_SETTINGS = "VCD_GET_SETTINGS",
|
||||
SET_SETTINGS = "VCD_SET_SETTINGS",
|
||||
@@ -26,6 +27,15 @@ export const enum IpcEvents {
|
||||
UPDATE_IGNORE = "VCD_UPDATE_IGNORE",
|
||||
|
||||
SPELLCHECK_SET_LANGUAGES = "VCD_SPELLCHECK_SET_LANGUAGES",
|
||||
SPELLCHECK_RESULT = "VCD_SPELLCHECK_RESULT",
|
||||
SPELLCHECK_REPLACE_MISSPELLING = "VCD_SPELLCHECK_REPLACE_MISSPELLING",
|
||||
SPELLCHECK_ADD_TO_DICTIONARY = "VCD_SPELLCHECK_ADD_TO_DICTIONARY",
|
||||
|
||||
CLOSE = "VCD_CLOSE"
|
||||
SET_BADGE_COUNT = "VCD_SET_BADGE_COUNT",
|
||||
|
||||
CAPTURER_GET_LARGE_THUMBNAIL = "VCD_CAPTURER_GET_LARGE_THUMBNAIL",
|
||||
|
||||
AUTOSTART_ENABLED = "VCD_AUTOSTART_ENABLED",
|
||||
ENABLE_AUTOSTART = "VCD_ENABLE_AUTOSTART",
|
||||
DISABLE_AUTOSTART = "VCD_DISABLE_AUTOSTART"
|
||||
}
|
||||
|
||||
@@ -7,4 +7,6 @@
|
||||
import { join } from "path";
|
||||
|
||||
export const STATIC_DIR = /* @__PURE__ */ join(__dirname, "..", "..", "static");
|
||||
export const VIEW_DIR = /* @__PURE__ */ join(STATIC_DIR, "views");
|
||||
export const BADGE_DIR = /* @__PURE__ */ join(STATIC_DIR, "badges");
|
||||
export const ICON_PATH = /* @__PURE__ */ join(STATIC_DIR, "icon.png");
|
||||
|
||||
1
src/shared/settings.d.ts
vendored
@@ -19,6 +19,7 @@ export interface Settings {
|
||||
skippedUpdate?: string;
|
||||
staticTitle?: boolean;
|
||||
arRPC?: boolean;
|
||||
appBadge?: boolean;
|
||||
|
||||
firstLaunch?: boolean;
|
||||
}
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
/*
|
||||
* SPDX-License-Identifier: GPL-3.0
|
||||
* Vencord Desktop, a desktop app aiming to give you a snappier Discord Experience
|
||||
* Copyright (c) 2023 Vendicated and Vencord contributors
|
||||
*/
|
||||
|
||||
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;
|
||||
}
|
||||
@@ -6,11 +6,11 @@
|
||||
|
||||
import { app, BrowserWindow, ipcMain, shell } from "electron";
|
||||
import { Settings } from "main/settings";
|
||||
import { makeLinksOpenExternally } from "main/utils/makeLinksOpenExternally";
|
||||
import { githubGet, ReleaseData } from "main/utils/vencordLoader";
|
||||
import { join } from "path";
|
||||
import { SplashProps } from "shared/browserWinProperties";
|
||||
import { IpcEvents } from "shared/IpcEvents";
|
||||
import { STATIC_DIR } from "shared/paths";
|
||||
import { VIEW_DIR } from "shared/paths";
|
||||
|
||||
export interface UpdateData {
|
||||
currentVersion: string;
|
||||
@@ -79,13 +79,13 @@ export async function checkUpdates() {
|
||||
|
||||
const oldVersion = app.getVersion();
|
||||
const newVersion = data.tag_name.replace(/^v/, "");
|
||||
if (Settings.store.skippedUpdate !== newVersion && isOutdated(oldVersion, newVersion)) {
|
||||
updateData = {
|
||||
currentVersion: oldVersion,
|
||||
latestVersion: newVersion,
|
||||
release: data
|
||||
};
|
||||
updateData = {
|
||||
currentVersion: oldVersion,
|
||||
latestVersion: newVersion,
|
||||
release: data
|
||||
};
|
||||
|
||||
if (Settings.store.skippedUpdate !== newVersion && isOutdated(oldVersion, newVersion)) {
|
||||
openNewUpdateWindow();
|
||||
}
|
||||
} catch (e) {
|
||||
@@ -95,11 +95,18 @@ export async function checkUpdates() {
|
||||
|
||||
function openNewUpdateWindow() {
|
||||
const win = new BrowserWindow({
|
||||
...SplashProps,
|
||||
width: 500,
|
||||
autoHideMenuBar: true,
|
||||
alwaysOnTop: true,
|
||||
webPreferences: {
|
||||
preload: join(__dirname, "updaterPreload.js")
|
||||
preload: join(__dirname, "updaterPreload.js"),
|
||||
nodeIntegration: false,
|
||||
contextIsolation: true,
|
||||
sandbox: true
|
||||
}
|
||||
});
|
||||
|
||||
win.loadFile(join(STATIC_DIR, "updater.html"));
|
||||
makeLinksOpenExternally(win);
|
||||
|
||||
win.loadFile(join(VIEW_DIR, "updater.html"));
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
*/
|
||||
|
||||
import { contextBridge } from "electron";
|
||||
import { invoke } from "preload/typedIpcs";
|
||||
import { invoke } from "preload/typedIpc";
|
||||
import { IpcEvents } from "shared/IpcEvents";
|
||||
|
||||
import type { UpdateData } from "./main";
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
<head>
|
||||
<style>
|
||||
body {
|
||||
padding: 2em;
|
||||
font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell,
|
||||
"Open Sans", "Helvetica Neue", sans-serif;
|
||||
}
|
||||
|
||||
h1 {
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<h1>Vencord Desktop %VERSION%</h1>
|
||||
<p>
|
||||
Vencord Desktop is a free/libre cross platform desktop app aiming to give you a snappier Discord experience with
|
||||
Vencord pre-installed
|
||||
</p>
|
||||
|
||||
<h2>Links</h2>
|
||||
<ul>
|
||||
<li>
|
||||
<a href="https://vencord.dev">Vencord Website</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://github.com/Vencord/Desktop" target="_blank">Source Code</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://github.com/Vencord/Desktop/issues" target="_blank">Report bugs / Request features</a>
|
||||
</li>
|
||||
</ul>
|
||||
</body>
|
||||
BIN
static/badges/1.ico
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
static/badges/10.ico
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
static/badges/11.ico
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
static/badges/2.ico
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
static/badges/3.ico
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
static/badges/4.ico
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
static/badges/5.ico
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
static/badges/6.ico
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
static/badges/7.ico
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
static/badges/8.ico
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
static/badges/9.ico
Normal file
|
After Width: | Height: | Size: 15 KiB |
71
static/views/about.html
Normal file
@@ -0,0 +1,71 @@
|
||||
<head>
|
||||
<link rel="stylesheet" href="./style.css" type="text/css" />
|
||||
|
||||
<style>
|
||||
body {
|
||||
padding: 2em;
|
||||
}
|
||||
|
||||
h1 {
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<h1 id="title">Vencord Desktop</h1>
|
||||
<p>
|
||||
Vencord Desktop is a free/libre cross platform desktop app aiming to give you a snappier Discord experience with
|
||||
Vencord pre-installed
|
||||
</p>
|
||||
|
||||
<section>
|
||||
<h2>Links</h2>
|
||||
<ul>
|
||||
<li>
|
||||
<a href="https://vencord.dev" target="_blank">Vencord Website</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://github.com/Vencord/Desktop" target="_blank">Source Code</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://github.com/Vencord/Desktop/issues" target="_blank">Report bugs / Request features</a>
|
||||
</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>Acknowledgements</h2>
|
||||
<p>These awesome libraries empower Vencord Desktop</p>
|
||||
<ul>
|
||||
<li>
|
||||
<a href="https://github.com/electron/electron" target="_blank">Electron</a>
|
||||
- Build cross-platform desktop apps with JavaScript, HTML, and CSS
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://github.com/electron-userland/electron-builder" target="_blank">Electron Builder</a>
|
||||
- A complete solution to package and build a ready for distribution Electron app with “auto update”
|
||||
support out of the box
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://github.com/OpenAsar/arrpc" target="_blank">arrpc</a>
|
||||
- An open implementation of Discord's Rich Presence server
|
||||
</li>
|
||||
<li>
|
||||
And many
|
||||
<a href="https://github.com/Vencord/Desktop/blob/main/pnpm-lock.yaml" target="_blank"
|
||||
>more awesome open source libraries</a
|
||||
>
|
||||
</li>
|
||||
</ul>
|
||||
</section>
|
||||
</body>
|
||||
|
||||
<script type="module">
|
||||
const data = await Updater.getData();
|
||||
if (data.currentVersion) {
|
||||
const title = document.getElementById("title");
|
||||
|
||||
title.textContent += ` v${data.currentVersion}`;
|
||||
}
|
||||
</script>
|
||||
@@ -1,25 +1,10 @@
|
||||
<head>
|
||||
<link rel="stylesheet" href="./style.css" type="text/css" />
|
||||
|
||||
<style>
|
||||
:root {
|
||||
--bg: white;
|
||||
--fg: black;
|
||||
--fg-semi-trans: rgb(0 0 0 / 0.2);
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root {
|
||||
--bg: hsl(223 6.7% 20.6%);
|
||||
--fg: white;
|
||||
--fg-semi-trans: rgb(255 255 255 / 0.2);
|
||||
}
|
||||
}
|
||||
|
||||
body {
|
||||
height: 100vh;
|
||||
|
||||
font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell,
|
||||
"Open Sans", "Helvetica Neue", sans-serif;
|
||||
margin: 0;
|
||||
padding: 1.5em;
|
||||
padding-bottom: 1em;
|
||||
|
||||
@@ -30,13 +15,9 @@
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body,
|
||||
select {
|
||||
background: var(--bg);
|
||||
color: var(--fg);
|
||||
}
|
||||
|
||||
select {
|
||||
padding: 0.3em;
|
||||
margin: -0.3em;
|
||||
border-radius: 6px;
|
||||
@@ -52,15 +33,49 @@
|
||||
|
||||
form {
|
||||
display: grid;
|
||||
gap: 0.5em;
|
||||
gap: 1em;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
label {
|
||||
position: relative;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
label:has(input[type="checkbox"]),
|
||||
select {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
label:not(:last-child)::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
bottom: -10px;
|
||||
width: 100%;
|
||||
height: 1px;
|
||||
background-color: var(--fg-secondary);
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
label div {
|
||||
display: grid;
|
||||
gap: 0.2em;
|
||||
}
|
||||
|
||||
label h2 {
|
||||
margin: 0;
|
||||
font-weight: normal;
|
||||
font-size: 1.1rem;
|
||||
line-height: 1rem;
|
||||
}
|
||||
|
||||
label span {
|
||||
font-size: 0.9rem;
|
||||
font-weight: 400;
|
||||
color: var(--fg-secondary);
|
||||
}
|
||||
|
||||
#buttons {
|
||||
display: flex;
|
||||
justify-content: end;
|
||||
@@ -75,6 +90,11 @@
|
||||
border-radius: 6px;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
transition: 200ms filter;
|
||||
}
|
||||
|
||||
button:hover {
|
||||
filter: brightness(0.8);
|
||||
}
|
||||
|
||||
#submit {
|
||||
@@ -89,7 +109,7 @@
|
||||
|
||||
<form>
|
||||
<label>
|
||||
Discord Branch
|
||||
<h2>Discord Branch</h2>
|
||||
<select name="discordBranch">
|
||||
<option value="stable">stable</option>
|
||||
<option value="canary">canary</option>
|
||||
@@ -98,17 +118,42 @@
|
||||
</label>
|
||||
|
||||
<label>
|
||||
Import Settings from existing Vencord install (if found)
|
||||
<div>
|
||||
<h2>Start with System</h2>
|
||||
<span>Automatically open Vencord Desktop when your computer starts</span>
|
||||
</div>
|
||||
<input type="checkbox" name="autoStart" />
|
||||
</label>
|
||||
|
||||
<label>
|
||||
<div>
|
||||
<h2>Rich Presence</h2>
|
||||
<span
|
||||
>Enable Rich presence (game activity) via
|
||||
<a href="https://github.com/OpenAsar/arrpc" target="_blank">arRPC</a></span
|
||||
>
|
||||
</div>
|
||||
<input type="checkbox" name="richPresence" checked />
|
||||
</label>
|
||||
|
||||
<label>
|
||||
<div>
|
||||
<h2>Import Settings</h2>
|
||||
<span>Import Settings from existing Vencord install (if found)</span>
|
||||
</div>
|
||||
<input type="checkbox" name="importSettings" checked />
|
||||
</label>
|
||||
|
||||
<label>
|
||||
Minimise to Tray when closing
|
||||
<div>
|
||||
<h2>Minimise to Tray</h2>
|
||||
<span>Minimise to Tray when closing</span>
|
||||
</div>
|
||||
<input type="checkbox" name="minimizeToTray" checked />
|
||||
</label>
|
||||
</form>
|
||||
<div id="buttons">
|
||||
<button id="cancel">Cancel</button>
|
||||
<button id="cancel">Quit</button>
|
||||
<button id="submit">Submit</button>
|
||||
</div>
|
||||
</body>
|
||||
@@ -1,16 +1,11 @@
|
||||
<head>
|
||||
<link rel="stylesheet" href="./style.css" type="text/css" />
|
||||
|
||||
<style>
|
||||
* {
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell,
|
||||
"Open Sans", "Helvetica Neue", sans-serif;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.wrapper {
|
||||
box-sizing: border-box;
|
||||
height: 100%;
|
||||
@@ -18,13 +13,11 @@
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
background-color: hsl(223 6.7% 20.6%);
|
||||
border-radius: 8px;
|
||||
border: 1px solid hsl(220 6.5% 18%);
|
||||
border: 1px solid var(--fg-semi-trans);
|
||||
}
|
||||
|
||||
p {
|
||||
color: rgb(219, 222, 225);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
30
static/views/style.css
Normal file
@@ -0,0 +1,30 @@
|
||||
:root {
|
||||
--bg: white;
|
||||
--fg: black;
|
||||
--fg-secondary: #313338;
|
||||
--fg-semi-trans: rgb(0 0 0 / 0.2);
|
||||
--link: #006ce7;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root {
|
||||
--bg: hsl(223 6.7% 20.6%);
|
||||
--fg: white;
|
||||
--fg-secondary: #b5bac1;
|
||||
--fg-semi-trans: rgb(255 255 255 / 0.2);
|
||||
--link: #00a8fc;
|
||||
}
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell,
|
||||
"Open Sans", "Helvetica Neue", sans-serif;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
background: var(--bg);
|
||||
color: var(--fg);
|
||||
}
|
||||
|
||||
a {
|
||||
color: var(--link);
|
||||
}
|
||||
@@ -1,22 +1,13 @@
|
||||
<head>
|
||||
<style>
|
||||
body {
|
||||
font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell,
|
||||
"Open Sans", "Helvetica Neue", sans-serif;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
color: rgb(219, 222, 225);
|
||||
}
|
||||
<link rel="stylesheet" href="./style.css" type="text/css" />
|
||||
|
||||
<style>
|
||||
.wrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
box-sizing: border-box;
|
||||
height: 100%;
|
||||
background-color: #313338;
|
||||
border-radius: 8px;
|
||||
border: 1px solid #248046;
|
||||
min-height: 100%;
|
||||
padding: 1em;
|
||||
}
|
||||
|
||||
@@ -34,7 +25,7 @@
|
||||
button {
|
||||
cursor: pointer;
|
||||
padding: 0.5em;
|
||||
color: white;
|
||||
color: var(--fg);
|
||||
border: none;
|
||||
border-radius: 3px;
|
||||
font-weight: bold;
|
||||
@@ -66,6 +57,9 @@
|
||||
<br />
|
||||
Latest: <span id="latest"></span>
|
||||
</p>
|
||||
|
||||
<h2>Changelog</h2>
|
||||
<p id="changelog">Loading...</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
@@ -113,3 +107,17 @@
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<script type="module">
|
||||
import { micromark } from "https://esm.sh/micromark@3?bundle";
|
||||
import { gfm, gfmHtml } from "https://esm.sh/micromark-extension-gfm@2?bundle";
|
||||
|
||||
const changelog = (await Updater.getData()).release.body;
|
||||
if (changelog)
|
||||
document.getElementById("changelog").innerHTML = micromark(changelog, {
|
||||
extensions: [gfm()],
|
||||
htmlExtensions: [gfmHtml()]
|
||||
})
|
||||
.replace(/h1>/g, "h3>")
|
||||
.replace(/<a /g, '<a target="_blank" ');
|
||||
</script>
|
||||