Add splash/tray image customisation & change default splash (#1179)
This commit is contained in:
@@ -8,6 +8,8 @@ import { app, NativeImage, nativeImage } from "electron";
|
|||||||
import { join } from "path";
|
import { join } from "path";
|
||||||
import { BADGE_DIR } from "shared/paths";
|
import { BADGE_DIR } from "shared/paths";
|
||||||
|
|
||||||
|
import { AppEvents } from "./events";
|
||||||
|
|
||||||
const imgCache = new Map<number, NativeImage>();
|
const imgCache = new Map<number, NativeImage>();
|
||||||
function loadBadge(index: number) {
|
function loadBadge(index: number) {
|
||||||
const cached = imgCache.get(index);
|
const cached = imgCache.get(index);
|
||||||
@@ -21,7 +23,13 @@ function loadBadge(index: number) {
|
|||||||
|
|
||||||
let lastIndex: null | number = -1;
|
let lastIndex: null | number = -1;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* -1 = show unread indicator
|
||||||
|
* 0 = clear
|
||||||
|
*/
|
||||||
export function setBadgeCount(count: number) {
|
export function setBadgeCount(count: number) {
|
||||||
|
AppEvents.emit("setTrayVariant", count !== 0 ? "trayUnread" : "tray");
|
||||||
|
|
||||||
switch (process.platform) {
|
switch (process.platform) {
|
||||||
case "linux":
|
case "linux":
|
||||||
if (count === -1) count = 0;
|
if (count === -1) count = 0;
|
||||||
|
|||||||
15
src/main/events.ts
Normal file
15
src/main/events.ts
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
/*
|
||||||
|
* Vesktop, a desktop app aiming to give you a snappier Discord Experience
|
||||||
|
* Copyright (c) 2025 Vendicated and Vesktop contributors
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { EventEmitter } from "events";
|
||||||
|
|
||||||
|
import { UserAssetType } from "./userAssets";
|
||||||
|
|
||||||
|
export const AppEvents = new EventEmitter<{
|
||||||
|
appLoaded: [];
|
||||||
|
userAssetChanged: [UserAssetType];
|
||||||
|
setTrayVariant: ["tray" | "trayUnread"];
|
||||||
|
}>();
|
||||||
@@ -5,6 +5,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import "./ipc";
|
import "./ipc";
|
||||||
|
import "./userAssets";
|
||||||
|
|
||||||
import { app, BrowserWindow, nativeTheme } from "electron";
|
import { app, BrowserWindow, nativeTheme } from "electron";
|
||||||
import { autoUpdater } from "electron-updater";
|
import { autoUpdater } from "electron-updater";
|
||||||
|
|||||||
@@ -8,45 +8,33 @@ import {
|
|||||||
app,
|
app,
|
||||||
BrowserWindow,
|
BrowserWindow,
|
||||||
BrowserWindowConstructorOptions,
|
BrowserWindowConstructorOptions,
|
||||||
dialog,
|
|
||||||
Menu,
|
Menu,
|
||||||
MenuItemConstructorOptions,
|
MenuItemConstructorOptions,
|
||||||
nativeTheme,
|
nativeTheme,
|
||||||
screen,
|
screen,
|
||||||
session,
|
session
|
||||||
Tray
|
|
||||||
} from "electron";
|
} from "electron";
|
||||||
import { EventEmitter } from "events";
|
|
||||||
import { rm } from "fs/promises";
|
|
||||||
import { join } from "path";
|
import { join } from "path";
|
||||||
import { IpcCommands, IpcEvents } from "shared/IpcEvents";
|
import { IpcCommands, IpcEvents } from "shared/IpcEvents";
|
||||||
import { isTruthy } from "shared/utils/guards";
|
import { isTruthy } from "shared/utils/guards";
|
||||||
import { once } from "shared/utils/once";
|
import { once } from "shared/utils/once";
|
||||||
import type { SettingsStore } from "shared/utils/SettingsStore";
|
import type { SettingsStore } from "shared/utils/SettingsStore";
|
||||||
|
|
||||||
import { TRAY_ICON_PATH } from "../shared/paths";
|
|
||||||
import { createAboutWindow } from "./about";
|
import { createAboutWindow } from "./about";
|
||||||
import { initArRPC } from "./arrpc";
|
import { initArRPC } from "./arrpc";
|
||||||
import {
|
import { BrowserUserAgent, DEFAULT_HEIGHT, DEFAULT_WIDTH, MIN_HEIGHT, MIN_WIDTH, VENCORD_FILES_DIR } from "./constants";
|
||||||
BrowserUserAgent,
|
import { AppEvents } from "./events";
|
||||||
DATA_DIR,
|
|
||||||
DEFAULT_HEIGHT,
|
|
||||||
DEFAULT_WIDTH,
|
|
||||||
MessageBoxChoice,
|
|
||||||
MIN_HEIGHT,
|
|
||||||
MIN_WIDTH,
|
|
||||||
VENCORD_FILES_DIR
|
|
||||||
} from "./constants";
|
|
||||||
import { darwinURL } from "./index";
|
import { darwinURL } from "./index";
|
||||||
import { sendRendererCommand } from "./ipcCommands";
|
import { sendRendererCommand } from "./ipcCommands";
|
||||||
import { Settings, State, VencordSettings } from "./settings";
|
import { Settings, State, VencordSettings } from "./settings";
|
||||||
import { createSplashWindow, updateSplashMessage } from "./splash";
|
import { createSplashWindow, updateSplashMessage } from "./splash";
|
||||||
|
import { destroyTray, initTray } from "./tray";
|
||||||
|
import { clearData } from "./utils/clearData";
|
||||||
import { makeLinksOpenExternally } from "./utils/makeLinksOpenExternally";
|
import { makeLinksOpenExternally } from "./utils/makeLinksOpenExternally";
|
||||||
import { applyDeckKeyboardFix, askToApplySteamLayout, isDeckGameMode } from "./utils/steamOS";
|
import { applyDeckKeyboardFix, askToApplySteamLayout, isDeckGameMode } from "./utils/steamOS";
|
||||||
import { downloadVencordFiles, ensureVencordFiles } from "./utils/vencordLoader";
|
import { downloadVencordFiles, ensureVencordFiles } from "./utils/vencordLoader";
|
||||||
|
|
||||||
let isQuitting = false;
|
let isQuitting = false;
|
||||||
let tray: Tray;
|
|
||||||
|
|
||||||
applyDeckKeyboardFix();
|
applyDeckKeyboardFix();
|
||||||
|
|
||||||
@@ -77,84 +65,6 @@ function makeSettingsListenerHelpers<O extends object>(o: SettingsStore<O>) {
|
|||||||
const [addSettingsListener, removeSettingsListeners] = makeSettingsListenerHelpers(Settings);
|
const [addSettingsListener, removeSettingsListeners] = makeSettingsListenerHelpers(Settings);
|
||||||
const [addVencordSettingsListener, removeVencordSettingsListeners] = makeSettingsListenerHelpers(VencordSettings);
|
const [addVencordSettingsListener, removeVencordSettingsListeners] = makeSettingsListenerHelpers(VencordSettings);
|
||||||
|
|
||||||
function initTray(win: BrowserWindow) {
|
|
||||||
const onTrayClick = () => {
|
|
||||||
if (Settings.store.clickTrayToShowHide && win.isVisible()) win.hide();
|
|
||||||
else win.show();
|
|
||||||
};
|
|
||||||
const trayMenu = Menu.buildFromTemplate([
|
|
||||||
{
|
|
||||||
label: "Open",
|
|
||||||
click() {
|
|
||||||
win.show();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "About",
|
|
||||||
click: createAboutWindow
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "Repair Vencord",
|
|
||||||
async click() {
|
|
||||||
await downloadVencordFiles();
|
|
||||||
app.relaunch();
|
|
||||||
app.quit();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "Reset Vesktop",
|
|
||||||
async click() {
|
|
||||||
await clearData(win);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: "separator"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "Restart",
|
|
||||||
click() {
|
|
||||||
app.relaunch();
|
|
||||||
app.quit();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "Quit",
|
|
||||||
click() {
|
|
||||||
isQuitting = true;
|
|
||||||
app.quit();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]);
|
|
||||||
|
|
||||||
tray = new Tray(join(TRAY_ICON_PATH, `${process.platform === "darwin" ? "trayTemplate" : "tray"}.png`));
|
|
||||||
tray.setToolTip("Vesktop");
|
|
||||||
tray.setContextMenu(trayMenu);
|
|
||||||
tray.on("click", onTrayClick);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function clearData(win: BrowserWindow) {
|
|
||||||
const { response } = await dialog.showMessageBox(win, {
|
|
||||||
message: "Are you sure you want to reset Vesktop?",
|
|
||||||
detail: "This will log you out, clear caches and reset all your settings!\n\nVesktop will automatically restart after this operation.",
|
|
||||||
buttons: ["Yes", "No"],
|
|
||||||
cancelId: MessageBoxChoice.Cancel,
|
|
||||||
defaultId: MessageBoxChoice.Default,
|
|
||||||
type: "warning"
|
|
||||||
});
|
|
||||||
|
|
||||||
if (response === MessageBoxChoice.Cancel) return;
|
|
||||||
|
|
||||||
win.close();
|
|
||||||
|
|
||||||
await win.webContents.session.clearStorageData();
|
|
||||||
await win.webContents.session.clearCache();
|
|
||||||
await win.webContents.session.clearCodeCaches({});
|
|
||||||
await rm(DATA_DIR, { force: true, recursive: true });
|
|
||||||
|
|
||||||
app.relaunch();
|
|
||||||
app.quit();
|
|
||||||
}
|
|
||||||
|
|
||||||
type MenuItemList = Array<MenuItemConstructorOptions | false>;
|
type MenuItemList = Array<MenuItemConstructorOptions | false>;
|
||||||
|
|
||||||
function initMenuBar(win: BrowserWindow) {
|
function initMenuBar(win: BrowserWindow) {
|
||||||
@@ -333,8 +243,8 @@ function initWindowBoundsListeners(win: BrowserWindow) {
|
|||||||
|
|
||||||
function initSettingsListeners(win: BrowserWindow) {
|
function initSettingsListeners(win: BrowserWindow) {
|
||||||
addSettingsListener("tray", enable => {
|
addSettingsListener("tray", enable => {
|
||||||
if (enable) initTray(win);
|
if (enable) initTray(win, q => (isQuitting = q));
|
||||||
else tray?.destroy();
|
else destroyTray();
|
||||||
});
|
});
|
||||||
|
|
||||||
addSettingsListener("disableMinSize", disable => {
|
addSettingsListener("disableMinSize", disable => {
|
||||||
@@ -476,7 +386,9 @@ function createMainWindow() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
initWindowBoundsListeners(win);
|
initWindowBoundsListeners(win);
|
||||||
if (!isDeckGameMode && (Settings.store.tray ?? true) && process.platform !== "darwin") initTray(win);
|
if (!isDeckGameMode && (Settings.store.tray ?? true) && process.platform !== "darwin")
|
||||||
|
initTray(win, q => (isQuitting = q));
|
||||||
|
|
||||||
initMenuBar(win);
|
initMenuBar(win);
|
||||||
makeLinksOpenExternally(win);
|
makeLinksOpenExternally(win);
|
||||||
initSettingsListeners(win);
|
initSettingsListeners(win);
|
||||||
@@ -496,8 +408,6 @@ function createMainWindow() {
|
|||||||
|
|
||||||
const runVencordMain = once(() => require(join(VENCORD_FILES_DIR, "vencordDesktopMain.js")));
|
const runVencordMain = once(() => require(join(VENCORD_FILES_DIR, "vencordDesktopMain.js")));
|
||||||
|
|
||||||
const loadEvents = new EventEmitter();
|
|
||||||
|
|
||||||
export function loadUrl(uri: string | undefined) {
|
export function loadUrl(uri: string | undefined) {
|
||||||
const branch = Settings.store.discordBranch;
|
const branch = Settings.store.discordBranch;
|
||||||
const subdomain = branch === "canary" || branch === "ptb" ? `${branch}.` : "";
|
const subdomain = branch === "canary" || branch === "ptb" ? `${branch}.` : "";
|
||||||
@@ -505,7 +415,7 @@ export function loadUrl(uri: string | undefined) {
|
|||||||
// we do not rely on 'did-finish-load' because it fires even if loadURL fails which triggers early detruction of the splash
|
// we do not rely on 'did-finish-load' because it fires even if loadURL fails which triggers early detruction of the splash
|
||||||
mainWin
|
mainWin
|
||||||
.loadURL(`https://${subdomain}discord.com/${uri ? new URL(uri).pathname.slice(1) || "app" : "app"}`)
|
.loadURL(`https://${subdomain}discord.com/${uri ? new URL(uri).pathname.slice(1) || "app" : "app"}`)
|
||||||
.then(() => loadEvents.emit("app-loaded"))
|
.then(() => AppEvents.emit("appLoaded"))
|
||||||
.catch(error => retryUrl(error.url, error.code));
|
.catch(error => retryUrl(error.url, error.code));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -532,7 +442,7 @@ export async function createWindows() {
|
|||||||
|
|
||||||
mainWin = createMainWindow();
|
mainWin = createMainWindow();
|
||||||
|
|
||||||
loadEvents.on("app-loaded", () => {
|
AppEvents.on("appLoaded", () => {
|
||||||
splash?.destroy();
|
splash?.destroy();
|
||||||
|
|
||||||
if (!startMinimized) {
|
if (!startMinimized) {
|
||||||
|
|||||||
92
src/main/tray.ts
Normal file
92
src/main/tray.ts
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
/*
|
||||||
|
* Vesktop, a desktop app aiming to give you a snappier Discord Experience
|
||||||
|
* Copyright (c) 2025 Vendicated and Vesktop contributors
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { app, BrowserWindow, Menu, Tray } from "electron";
|
||||||
|
|
||||||
|
import { createAboutWindow } from "./about";
|
||||||
|
import { AppEvents } from "./events";
|
||||||
|
import { Settings } from "./settings";
|
||||||
|
import { resolveAssetPath } from "./userAssets";
|
||||||
|
import { clearData } from "./utils/clearData";
|
||||||
|
import { downloadVencordFiles } from "./utils/vencordLoader";
|
||||||
|
|
||||||
|
let tray: Tray;
|
||||||
|
let trayVariant: "tray" | "trayUnread" = "tray";
|
||||||
|
|
||||||
|
AppEvents.on("userAssetChanged", async asset => {
|
||||||
|
if (tray && (asset === "tray" || asset === "trayUnread")) {
|
||||||
|
tray.setImage(await resolveAssetPath(trayVariant));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
AppEvents.on("setTrayVariant", async variant => {
|
||||||
|
if (trayVariant === variant) return;
|
||||||
|
|
||||||
|
trayVariant = variant;
|
||||||
|
if (!tray) return;
|
||||||
|
|
||||||
|
tray.setImage(await resolveAssetPath(trayVariant));
|
||||||
|
});
|
||||||
|
|
||||||
|
export function destroyTray() {
|
||||||
|
tray?.destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function initTray(win: BrowserWindow, setIsQuitting: (val: boolean) => void) {
|
||||||
|
const onTrayClick = () => {
|
||||||
|
if (Settings.store.clickTrayToShowHide && win.isVisible()) win.hide();
|
||||||
|
else win.show();
|
||||||
|
};
|
||||||
|
|
||||||
|
const trayMenu = Menu.buildFromTemplate([
|
||||||
|
{
|
||||||
|
label: "Open",
|
||||||
|
click() {
|
||||||
|
win.show();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "About",
|
||||||
|
click: createAboutWindow
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Repair Vencord",
|
||||||
|
async click() {
|
||||||
|
await downloadVencordFiles();
|
||||||
|
app.relaunch();
|
||||||
|
app.quit();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Reset Vesktop",
|
||||||
|
async click() {
|
||||||
|
await clearData(win);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "separator"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Restart",
|
||||||
|
click() {
|
||||||
|
app.relaunch();
|
||||||
|
app.quit();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Quit",
|
||||||
|
click() {
|
||||||
|
setIsQuitting(true);
|
||||||
|
app.quit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
|
||||||
|
tray = new Tray(await resolveAssetPath(trayVariant));
|
||||||
|
tray.setToolTip("Vesktop");
|
||||||
|
tray.setContextMenu(trayMenu);
|
||||||
|
tray.on("click", onTrayClick);
|
||||||
|
}
|
||||||
110
src/main/userAssets.ts
Normal file
110
src/main/userAssets.ts
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
/*
|
||||||
|
* Vesktop, a desktop app aiming to give you a snappier Discord Experience
|
||||||
|
* Copyright (c) 2025 Vendicated and Vesktop contributors
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { app, dialog, net, protocol } from "electron";
|
||||||
|
import { copyFile, mkdir, rm } from "fs/promises";
|
||||||
|
import { join } from "path";
|
||||||
|
import { IpcEvents } from "shared/IpcEvents";
|
||||||
|
import { STATIC_DIR } from "shared/paths";
|
||||||
|
import { pathToFileURL } from "url";
|
||||||
|
|
||||||
|
import { DATA_DIR } from "./constants";
|
||||||
|
import { AppEvents } from "./events";
|
||||||
|
import { mainWin } from "./mainWindow";
|
||||||
|
import { fileExistsAsync } from "./utils/fileExists";
|
||||||
|
import { handle } from "./utils/ipcWrappers";
|
||||||
|
|
||||||
|
const CUSTOMIZABLE_ASSETS = ["splash", "tray", "trayUnread"] as const;
|
||||||
|
export type UserAssetType = (typeof CUSTOMIZABLE_ASSETS)[number];
|
||||||
|
|
||||||
|
const DEFAULT_ASSETS: Record<UserAssetType, string> = {
|
||||||
|
splash: "splash.webp",
|
||||||
|
tray: `tray/${process.platform === "darwin" ? "trayTemplate" : "tray"}.png`,
|
||||||
|
trayUnread: "tray/trayUnread.png"
|
||||||
|
};
|
||||||
|
|
||||||
|
const UserAssetFolder = join(DATA_DIR, "userAssets");
|
||||||
|
|
||||||
|
export async function resolveAssetPath(asset: UserAssetType) {
|
||||||
|
if (!CUSTOMIZABLE_ASSETS.includes(asset)) {
|
||||||
|
throw new Error(`Invalid asset: ${asset}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const assetPath = join(UserAssetFolder, asset);
|
||||||
|
if (await fileExistsAsync(assetPath)) {
|
||||||
|
return assetPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
return join(STATIC_DIR, DEFAULT_ASSETS[asset]);
|
||||||
|
}
|
||||||
|
|
||||||
|
app.whenReady().then(() => {
|
||||||
|
protocol.handle("vesktop", async req => {
|
||||||
|
if (!req.url.startsWith("vesktop://assets/")) {
|
||||||
|
return new Response(null, { status: 404 });
|
||||||
|
}
|
||||||
|
|
||||||
|
const asset = decodeURI(req.url)
|
||||||
|
.slice("vesktop://assets/".length)
|
||||||
|
.replace(/\?v=\d+$/, "")
|
||||||
|
.replace(/\/+$/, "");
|
||||||
|
|
||||||
|
// @ts-expect-error dumb types
|
||||||
|
if (!CUSTOMIZABLE_ASSETS.includes(asset)) {
|
||||||
|
return new Response(null, { status: 404 });
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await net.fetch(pathToFileURL(join(UserAssetFolder, asset)).href);
|
||||||
|
if (res.ok) return res;
|
||||||
|
} catch {}
|
||||||
|
|
||||||
|
return net.fetch(pathToFileURL(join(STATIC_DIR, DEFAULT_ASSETS[asset])).href);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
handle(IpcEvents.CHOOSE_USER_ASSET, async (_event, asset: UserAssetType, value?: null) => {
|
||||||
|
if (!CUSTOMIZABLE_ASSETS.includes(asset)) {
|
||||||
|
throw `Invalid asset: ${asset}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const assetPath = join(UserAssetFolder, asset);
|
||||||
|
|
||||||
|
if (value === null) {
|
||||||
|
try {
|
||||||
|
await rm(assetPath, { force: true });
|
||||||
|
AppEvents.emit("userAssetChanged", asset);
|
||||||
|
return "ok";
|
||||||
|
} catch (e) {
|
||||||
|
console.error(`Failed to remove user asset ${asset}:`, e);
|
||||||
|
return "failed";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const res = await dialog.showOpenDialog(mainWin, {
|
||||||
|
properties: ["openFile"],
|
||||||
|
title: `Select an image to use as ${asset}`,
|
||||||
|
defaultPath: app.getPath("pictures"),
|
||||||
|
filters: [
|
||||||
|
{
|
||||||
|
name: "Images",
|
||||||
|
extensions: ["png", "jpg", "jpeg", "webp", "gif", "avif", "svg"]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
|
if (res.canceled || !res.filePaths.length) return "cancelled";
|
||||||
|
|
||||||
|
try {
|
||||||
|
await mkdir(UserAssetFolder, { recursive: true });
|
||||||
|
await copyFile(res.filePaths[0], assetPath);
|
||||||
|
AppEvents.emit("userAssetChanged", asset);
|
||||||
|
return "ok";
|
||||||
|
} catch (e) {
|
||||||
|
console.error(`Failed to copy user asset ${asset}:`, e);
|
||||||
|
return "failed";
|
||||||
|
}
|
||||||
|
});
|
||||||
32
src/main/utils/clearData.ts
Normal file
32
src/main/utils/clearData.ts
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
/*
|
||||||
|
* Vesktop, a desktop app aiming to give you a snappier Discord Experience
|
||||||
|
* Copyright (c) 2025 Vendicated and Vesktop contributors
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { app, BrowserWindow, dialog } from "electron";
|
||||||
|
import { rm } from "fs/promises";
|
||||||
|
import { DATA_DIR, MessageBoxChoice } from "main/constants";
|
||||||
|
|
||||||
|
export async function clearData(win: BrowserWindow) {
|
||||||
|
const { response } = await dialog.showMessageBox(win, {
|
||||||
|
message: "Are you sure you want to reset Vesktop?",
|
||||||
|
detail: "This will log you out, clear caches and reset all your settings!\n\nVesktop will automatically restart after this operation.",
|
||||||
|
buttons: ["Yes", "No"],
|
||||||
|
cancelId: MessageBoxChoice.Cancel,
|
||||||
|
defaultId: MessageBoxChoice.Default,
|
||||||
|
type: "warning"
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response === MessageBoxChoice.Cancel) return;
|
||||||
|
|
||||||
|
win.close();
|
||||||
|
|
||||||
|
await win.webContents.session.clearStorageData();
|
||||||
|
await win.webContents.session.clearCache();
|
||||||
|
await win.webContents.session.clearCodeCaches({});
|
||||||
|
await rm(DATA_DIR, { force: true, recursive: true });
|
||||||
|
|
||||||
|
app.relaunch();
|
||||||
|
app.quit();
|
||||||
|
}
|
||||||
13
src/main/utils/fileExists.ts
Normal file
13
src/main/utils/fileExists.ts
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
/*
|
||||||
|
* Vesktop, a desktop app aiming to give you a snappier Discord Experience
|
||||||
|
* Copyright (c) 2025 Vendicated and Vesktop contributors
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { access, constants } from "fs/promises";
|
||||||
|
|
||||||
|
export async function fileExistsAsync(path: string) {
|
||||||
|
return await access(path, constants.F_OK)
|
||||||
|
.then(() => true)
|
||||||
|
.catch(() => false);
|
||||||
|
}
|
||||||
@@ -42,7 +42,9 @@ export const VesktopNative = {
|
|||||||
fileManager: {
|
fileManager: {
|
||||||
showItemInFolder: (path: string) => invoke<void>(IpcEvents.SHOW_ITEM_IN_FOLDER, path),
|
showItemInFolder: (path: string) => invoke<void>(IpcEvents.SHOW_ITEM_IN_FOLDER, path),
|
||||||
getVencordDir: () => sendSync<string | undefined>(IpcEvents.GET_VENCORD_DIR),
|
getVencordDir: () => sendSync<string | undefined>(IpcEvents.GET_VENCORD_DIR),
|
||||||
selectVencordDir: (value?: null) => invoke<"cancelled" | "invalid" | "ok">(IpcEvents.SELECT_VENCORD_DIR, value)
|
selectVencordDir: (value?: null) => invoke<"cancelled" | "invalid" | "ok">(IpcEvents.SELECT_VENCORD_DIR, value),
|
||||||
|
chooseUserAsset: (asset: string, value?: null) =>
|
||||||
|
invoke<"cancelled" | "invalid" | "ok" | "failed">(IpcEvents.CHOOSE_USER_ASSET, asset, value)
|
||||||
},
|
},
|
||||||
settings: {
|
settings: {
|
||||||
get: () => sendSync<Settings>(IpcEvents.GET_SETTINGS),
|
get: () => sendSync<Settings>(IpcEvents.GET_SETTINGS),
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import { AutoStartToggle } from "./AutoStartToggle";
|
|||||||
import { DeveloperOptionsButton } from "./DeveloperOptions";
|
import { DeveloperOptionsButton } from "./DeveloperOptions";
|
||||||
import { DiscordBranchPicker } from "./DiscordBranchPicker";
|
import { DiscordBranchPicker } from "./DiscordBranchPicker";
|
||||||
import { NotificationBadgeToggle } from "./NotificationBadgeToggle";
|
import { NotificationBadgeToggle } from "./NotificationBadgeToggle";
|
||||||
|
import { UserAssetsButton } from "./UserAssets";
|
||||||
import { VesktopSettingsSwitch } from "./VesktopSettingsSwitch";
|
import { VesktopSettingsSwitch } from "./VesktopSettingsSwitch";
|
||||||
import { WindowsTransparencyControls } from "./WindowsTransparencyControls";
|
import { WindowsTransparencyControls } from "./WindowsTransparencyControls";
|
||||||
|
|
||||||
@@ -82,7 +83,8 @@ const SettingsOptions: Record<string, Array<BooleanSetting | SettingsComponent>>
|
|||||||
description: "Adapt the splash window colors to your custom theme",
|
description: "Adapt the splash window colors to your custom theme",
|
||||||
defaultValue: true
|
defaultValue: true
|
||||||
},
|
},
|
||||||
WindowsTransparencyControls
|
WindowsTransparencyControls,
|
||||||
|
UserAssetsButton
|
||||||
],
|
],
|
||||||
Behaviour: [
|
Behaviour: [
|
||||||
{
|
{
|
||||||
|
|||||||
25
src/renderer/components/settings/UserAssets.css
Normal file
25
src/renderer/components/settings/UserAssets.css
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
.vcd-user-assets {
|
||||||
|
display: flex;
|
||||||
|
margin-block: 1em 2em;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vcd-user-assets-asset {
|
||||||
|
display: flex;
|
||||||
|
margin-top: 0.5em;
|
||||||
|
align-items: center;
|
||||||
|
gap: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vcd-user-assets-actions {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vcd-user-assets-image {
|
||||||
|
height: 7.5em;
|
||||||
|
width: 7.5em;
|
||||||
|
object-fit: contain;
|
||||||
|
}
|
||||||
80
src/renderer/components/settings/UserAssets.tsx
Normal file
80
src/renderer/components/settings/UserAssets.tsx
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
/*
|
||||||
|
* Vesktop, a desktop app aiming to give you a snappier Discord Experience
|
||||||
|
* Copyright (c) 2025 Vendicated and Vencord contributors
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
import "./UserAssets.css";
|
||||||
|
|
||||||
|
import {
|
||||||
|
ModalCloseButton,
|
||||||
|
ModalContent,
|
||||||
|
ModalHeader,
|
||||||
|
ModalRoot,
|
||||||
|
ModalSize,
|
||||||
|
openModal,
|
||||||
|
wordsFromCamel,
|
||||||
|
wordsToTitle
|
||||||
|
} from "@vencord/types/utils";
|
||||||
|
import { Button, showToast, Text, useState } from "@vencord/types/webpack/common";
|
||||||
|
import { UserAssetType } from "main/userAssets";
|
||||||
|
|
||||||
|
import { SettingsComponent } from "./Settings";
|
||||||
|
|
||||||
|
const CUSTOMIZABLE_ASSETS: UserAssetType[] = ["splash", "tray", "trayUnread"];
|
||||||
|
|
||||||
|
export const UserAssetsButton: SettingsComponent = () => {
|
||||||
|
return <Button onClick={() => openAssetsModal()}>Customize App Assets</Button>;
|
||||||
|
};
|
||||||
|
|
||||||
|
function openAssetsModal() {
|
||||||
|
openModal(props => (
|
||||||
|
<ModalRoot {...props} size={ModalSize.MEDIUM}>
|
||||||
|
<ModalHeader>
|
||||||
|
<Text variant="heading-lg/semibold" style={{ flexGrow: 1 }}>
|
||||||
|
User Assets
|
||||||
|
</Text>
|
||||||
|
<ModalCloseButton onClick={props.onClose} />
|
||||||
|
</ModalHeader>
|
||||||
|
|
||||||
|
<ModalContent>
|
||||||
|
<div className="vcd-user-assets">
|
||||||
|
{CUSTOMIZABLE_ASSETS.map(asset => (
|
||||||
|
<Asset key={asset} asset={asset} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</ModalContent>
|
||||||
|
</ModalRoot>
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
function Asset({ asset }: { asset: UserAssetType }) {
|
||||||
|
// cache busting
|
||||||
|
const [version, setVersion] = useState(Date.now());
|
||||||
|
|
||||||
|
const onChooseAsset = (value?: null) => async () => {
|
||||||
|
const res = await VesktopNative.fileManager.chooseUserAsset(asset, value);
|
||||||
|
if (res === "ok") {
|
||||||
|
setVersion(Date.now());
|
||||||
|
} else if (res === "failed") {
|
||||||
|
showToast("Something went wrong. Please try again");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<section>
|
||||||
|
<Text tag="h3" variant="text-md/semibold">
|
||||||
|
{wordsToTitle(wordsFromCamel(asset))}
|
||||||
|
</Text>
|
||||||
|
<div className="vcd-user-assets-asset">
|
||||||
|
<img className="vcd-user-assets-image" src={`vesktop://assets/${asset}?v=${version}`} alt="" />
|
||||||
|
<div className="vcd-user-assets-actions">
|
||||||
|
<Button onClick={onChooseAsset()}>Customize</Button>
|
||||||
|
<Button color={Button.Colors.PRIMARY} onClick={onChooseAsset(null)}>
|
||||||
|
Reset to default
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -57,7 +57,9 @@ export const enum IpcEvents {
|
|||||||
IPC_COMMAND = "VCD_IPC_COMMAND",
|
IPC_COMMAND = "VCD_IPC_COMMAND",
|
||||||
|
|
||||||
DEVTOOLS_OPENED = "VCD_DEVTOOLS_OPENED",
|
DEVTOOLS_OPENED = "VCD_DEVTOOLS_OPENED",
|
||||||
DEVTOOLS_CLOSED = "VCD_DEVTOOLS_CLOSED"
|
DEVTOOLS_CLOSED = "VCD_DEVTOOLS_CLOSED",
|
||||||
|
|
||||||
|
CHOOSE_USER_ASSET = "VCD_CHOOSE_USER_ASSET"
|
||||||
}
|
}
|
||||||
|
|
||||||
export const enum IpcCommands {
|
export const enum IpcCommands {
|
||||||
|
|||||||
@@ -9,4 +9,3 @@ import { join } from "path";
|
|||||||
export const STATIC_DIR = /* @__PURE__ */ join(__dirname, "..", "..", "static");
|
export const STATIC_DIR = /* @__PURE__ */ join(__dirname, "..", "..", "static");
|
||||||
export const VIEW_DIR = /* @__PURE__ */ join(STATIC_DIR, "views");
|
export const VIEW_DIR = /* @__PURE__ */ join(STATIC_DIR, "views");
|
||||||
export const BADGE_DIR = /* @__PURE__ */ join(STATIC_DIR, "badges");
|
export const BADGE_DIR = /* @__PURE__ */ join(STATIC_DIR, "badges");
|
||||||
export const TRAY_ICON_PATH = /* @__PURE__ */ join(STATIC_DIR, "tray");
|
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 16 KiB |
BIN
static/splash.webp
Normal file
BIN
static/splash.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 117 KiB |
BIN
static/tray.png
Normal file
BIN
static/tray.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.5 KiB |
|
Before Width: | Height: | Size: 3.9 KiB After Width: | Height: | Size: 3.9 KiB |
@@ -36,19 +36,14 @@
|
|||||||
img {
|
img {
|
||||||
width: 128px;
|
width: 128px;
|
||||||
height: 128px;
|
height: 128px;
|
||||||
image-rendering: pixelated;
|
object-fit: contain;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<div class="wrapper">
|
<div class="wrapper">
|
||||||
<img
|
<img draggable="false" src="vesktop://assets/splash" alt="" role="presentation" />
|
||||||
draggable="false"
|
|
||||||
src="../shiggy.gif"
|
|
||||||
alt="shiggy"
|
|
||||||
role="presentation"
|
|
||||||
/>
|
|
||||||
<p>Loading Vesktop...</p>
|
<p>Loading Vesktop...</p>
|
||||||
<p class="message"></p>
|
<p class="message"></p>
|
||||||
</div>
|
</div>
|
||||||
@@ -61,4 +56,4 @@
|
|||||||
messageElement.textContent = message;
|
messageElement.textContent = message;
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
Reference in New Issue
Block a user