Updater: show update dialog instead of forcing updates (#1184)
This commit is contained in:
+12
-5
@@ -80,24 +80,31 @@ await Promise.all([
|
|||||||
...NodeCommonOpts,
|
...NodeCommonOpts,
|
||||||
entryPoints: ["src/main/index.ts"],
|
entryPoints: ["src/main/index.ts"],
|
||||||
outfile: "dist/js/main.js",
|
outfile: "dist/js/main.js",
|
||||||
footer: { js: "//# sourceURL=VCDMain" }
|
footer: { js: "//# sourceURL=VesktopMain" }
|
||||||
}),
|
}),
|
||||||
createContext({
|
createContext({
|
||||||
...NodeCommonOpts,
|
...NodeCommonOpts,
|
||||||
entryPoints: ["src/main/arrpc/worker.ts"],
|
entryPoints: ["src/main/arrpc/worker.ts"],
|
||||||
outfile: "dist/js/arRpcWorker.js",
|
outfile: "dist/js/arRpcWorker.js",
|
||||||
footer: { js: "//# sourceURL=VCDArRpcWorker" }
|
footer: { js: "//# sourceURL=VesktopArRpcWorker" }
|
||||||
}),
|
}),
|
||||||
createContext({
|
createContext({
|
||||||
...NodeCommonOpts,
|
...NodeCommonOpts,
|
||||||
entryPoints: ["src/preload/index.ts"],
|
entryPoints: ["src/preload/index.ts"],
|
||||||
outfile: "dist/js/preload.js",
|
outfile: "dist/js/preload.js",
|
||||||
footer: { js: "//# sourceURL=VCDPreload" }
|
footer: { js: "//# sourceURL=VesktopPreload" }
|
||||||
}),
|
}),
|
||||||
createContext({
|
createContext({
|
||||||
...NodeCommonOpts,
|
...NodeCommonOpts,
|
||||||
entryPoints: ["src/preload/splash.ts"],
|
entryPoints: ["src/preload/splash.ts"],
|
||||||
outfile: "dist/js/splashPreload.js"
|
outfile: "dist/js/splashPreload.js",
|
||||||
|
footer: { js: "//# sourceURL=VesktopSplashPreload" }
|
||||||
|
}),
|
||||||
|
createContext({
|
||||||
|
...NodeCommonOpts,
|
||||||
|
entryPoints: ["src/preload/updater.ts"],
|
||||||
|
outfile: "dist/js/updaterPreload.js",
|
||||||
|
footer: { js: "//# sourceURL=VesktopUpdaterPreload" }
|
||||||
}),
|
}),
|
||||||
createContext({
|
createContext({
|
||||||
...CommonOpts,
|
...CommonOpts,
|
||||||
@@ -110,7 +117,7 @@ await Promise.all([
|
|||||||
jsxFragment: "VencordFragment",
|
jsxFragment: "VencordFragment",
|
||||||
external: ["@vencord/types/*"],
|
external: ["@vencord/types/*"],
|
||||||
plugins: [vencordDep, includeDirPlugin("patches", "src/renderer/patches")],
|
plugins: [vencordDep, includeDirPlugin("patches", "src/renderer/patches")],
|
||||||
footer: { js: "//# sourceURL=VCDRenderer" }
|
footer: { js: "//# sourceURL=VesktopRenderer" }
|
||||||
})
|
})
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
|||||||
+1
-5
@@ -4,11 +4,11 @@
|
|||||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import "./updater";
|
||||||
import "./ipc";
|
import "./ipc";
|
||||||
import "./userAssets";
|
import "./userAssets";
|
||||||
|
|
||||||
import { app, BrowserWindow, nativeTheme } from "electron";
|
import { app, BrowserWindow, nativeTheme } from "electron";
|
||||||
import { autoUpdater } from "electron-updater";
|
|
||||||
|
|
||||||
import { checkCommandLineForHelpOrVersion } from "./cli";
|
import { checkCommandLineForHelpOrVersion } from "./cli";
|
||||||
import { DATA_DIR } from "./constants";
|
import { DATA_DIR } from "./constants";
|
||||||
@@ -22,10 +22,6 @@ import { isDeckGameMode } from "./utils/steamOS";
|
|||||||
|
|
||||||
checkCommandLineForHelpOrVersion();
|
checkCommandLineForHelpOrVersion();
|
||||||
|
|
||||||
if (!IS_DEV) {
|
|
||||||
autoUpdater.checkForUpdatesAndNotify();
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log("Vesktop v" + app.getVersion());
|
console.log("Vesktop v" + app.getVersion());
|
||||||
|
|
||||||
// Make the Vencord files use our DATA_DIR
|
// Make the Vencord files use our DATA_DIR
|
||||||
|
|||||||
@@ -0,0 +1,81 @@
|
|||||||
|
/*
|
||||||
|
* 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, ipcMain } from "electron";
|
||||||
|
import { autoUpdater, UpdateInfo } from "electron-updater";
|
||||||
|
import { join } from "path";
|
||||||
|
import { IpcEvents, UpdaterIpcEvents } from "shared/IpcEvents";
|
||||||
|
import { VIEW_DIR } from "shared/paths";
|
||||||
|
import { Millis } from "shared/utils/millis";
|
||||||
|
|
||||||
|
import { State } from "./settings";
|
||||||
|
import { handle } from "./utils/ipcWrappers";
|
||||||
|
import { makeLinksOpenExternally } from "./utils/makeLinksOpenExternally";
|
||||||
|
|
||||||
|
let updaterWindow: BrowserWindow | null = null;
|
||||||
|
|
||||||
|
autoUpdater.on("update-available", update => {
|
||||||
|
if (State.store.updater?.ignoredVersion === update.version) return;
|
||||||
|
if ((State.store.updater?.snoozeUntil ?? 0) > Date.now()) return;
|
||||||
|
|
||||||
|
openUpdater(update);
|
||||||
|
});
|
||||||
|
|
||||||
|
autoUpdater.on("update-downloaded", () => setTimeout(() => autoUpdater.quitAndInstall(), 100));
|
||||||
|
autoUpdater.on("download-progress", p =>
|
||||||
|
updaterWindow?.webContents.send(UpdaterIpcEvents.DOWNLOAD_PROGRESS, p.percent)
|
||||||
|
);
|
||||||
|
autoUpdater.on("error", err => updaterWindow?.webContents.send(UpdaterIpcEvents.ERROR, err.message));
|
||||||
|
|
||||||
|
autoUpdater.autoDownload = false;
|
||||||
|
autoUpdater.autoInstallOnAppQuit = false;
|
||||||
|
autoUpdater.fullChangelog = true;
|
||||||
|
|
||||||
|
const isOutdated = autoUpdater.checkForUpdates().then(res => Boolean(res?.isUpdateAvailable));
|
||||||
|
|
||||||
|
handle(IpcEvents.UPDATER_IS_OUTDATED, () => isOutdated);
|
||||||
|
handle(IpcEvents.UPDATER_OPEN, async () => {
|
||||||
|
const res = await autoUpdater.checkForUpdates();
|
||||||
|
if (res?.isUpdateAvailable && res.updateInfo) openUpdater(res.updateInfo);
|
||||||
|
});
|
||||||
|
|
||||||
|
function openUpdater(update: UpdateInfo) {
|
||||||
|
updaterWindow = new BrowserWindow({
|
||||||
|
title: "Vesktop Updater",
|
||||||
|
autoHideMenuBar: true,
|
||||||
|
webPreferences: {
|
||||||
|
preload: join(__dirname, "updaterPreload.js")
|
||||||
|
},
|
||||||
|
minHeight: 400,
|
||||||
|
minWidth: 750
|
||||||
|
});
|
||||||
|
makeLinksOpenExternally(updaterWindow);
|
||||||
|
|
||||||
|
handle(UpdaterIpcEvents.GET_DATA, () => ({ update, version: app.getVersion() }));
|
||||||
|
handle(UpdaterIpcEvents.INSTALL, async () => {
|
||||||
|
await autoUpdater.downloadUpdate();
|
||||||
|
});
|
||||||
|
handle(UpdaterIpcEvents.SNOOZE_UPDATE, () => {
|
||||||
|
State.store.updater ??= {};
|
||||||
|
State.store.updater.snoozeUntil = Date.now() + 1 * Millis.DAY;
|
||||||
|
updaterWindow?.close();
|
||||||
|
});
|
||||||
|
handle(UpdaterIpcEvents.IGNORE_UPDATE, () => {
|
||||||
|
State.store.updater ??= {};
|
||||||
|
State.store.updater.ignoredVersion = update.version;
|
||||||
|
updaterWindow?.close();
|
||||||
|
});
|
||||||
|
|
||||||
|
updaterWindow.on("closed", () => {
|
||||||
|
ipcMain.removeHandler(UpdaterIpcEvents.GET_DATA);
|
||||||
|
ipcMain.removeHandler(UpdaterIpcEvents.INSTALL);
|
||||||
|
ipcMain.removeHandler(UpdaterIpcEvents.SNOOZE_UPDATE);
|
||||||
|
ipcMain.removeHandler(UpdaterIpcEvents.IGNORE_UPDATE);
|
||||||
|
updaterWindow = null;
|
||||||
|
});
|
||||||
|
|
||||||
|
updaterWindow.loadFile(join(VIEW_DIR, "updater.html"));
|
||||||
|
}
|
||||||
@@ -6,7 +6,7 @@
|
|||||||
|
|
||||||
import { ipcMain, IpcMainEvent, IpcMainInvokeEvent, WebFrameMain } from "electron";
|
import { ipcMain, IpcMainEvent, IpcMainInvokeEvent, WebFrameMain } from "electron";
|
||||||
import { DISCORD_HOSTNAMES } from "main/constants";
|
import { DISCORD_HOSTNAMES } from "main/constants";
|
||||||
import { IpcEvents } from "shared/IpcEvents";
|
import { IpcEvents, UpdaterIpcEvents } from "shared/IpcEvents";
|
||||||
|
|
||||||
export function validateSender(frame: WebFrameMain | null, event: string) {
|
export function validateSender(frame: WebFrameMain | null, event: string) {
|
||||||
if (!frame) throw new Error(`ipc[${event}]: No sender frame`);
|
if (!frame) throw new Error(`ipc[${event}]: No sender frame`);
|
||||||
@@ -25,14 +25,14 @@ export function validateSender(frame: WebFrameMain | null, event: string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function handleSync(event: IpcEvents, cb: (e: IpcMainEvent, ...args: any[]) => any) {
|
export function handleSync(event: IpcEvents | UpdaterIpcEvents, cb: (e: IpcMainEvent, ...args: any[]) => any) {
|
||||||
ipcMain.on(event, (e, ...args) => {
|
ipcMain.on(event, (e, ...args) => {
|
||||||
validateSender(e.senderFrame, event);
|
validateSender(e.senderFrame, event);
|
||||||
e.returnValue = cb(e, ...args);
|
e.returnValue = cb(e, ...args);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function handle(event: IpcEvents, cb: (e: IpcMainInvokeEvent, ...args: any[]) => any) {
|
export function handle(event: IpcEvents | UpdaterIpcEvents, cb: (e: IpcMainInvokeEvent, ...args: any[]) => any) {
|
||||||
ipcMain.handle(event, (e, ...args) => {
|
ipcMain.handle(event, (e, ...args) => {
|
||||||
validateSender(e.senderFrame, event);
|
validateSender(e.senderFrame, event);
|
||||||
return cb(e, ...args);
|
return cb(e, ...args);
|
||||||
|
|||||||
@@ -32,7 +32,9 @@ export const VesktopNative = {
|
|||||||
getVersion: () => sendSync<void>(IpcEvents.GET_VERSION),
|
getVersion: () => sendSync<void>(IpcEvents.GET_VERSION),
|
||||||
setBadgeCount: (count: number) => invoke<void>(IpcEvents.SET_BADGE_COUNT, count),
|
setBadgeCount: (count: number) => invoke<void>(IpcEvents.SET_BADGE_COUNT, count),
|
||||||
supportsWindowsTransparency: () => sendSync<boolean>(IpcEvents.SUPPORTS_WINDOWS_TRANSPARENCY),
|
supportsWindowsTransparency: () => sendSync<boolean>(IpcEvents.SUPPORTS_WINDOWS_TRANSPARENCY),
|
||||||
getEnableHardwareAcceleration: () => sendSync<boolean>(IpcEvents.GET_ENABLE_HARDWARE_ACCELERATION)
|
getEnableHardwareAcceleration: () => sendSync<boolean>(IpcEvents.GET_ENABLE_HARDWARE_ACCELERATION),
|
||||||
|
isOutdated: () => invoke<boolean>(IpcEvents.UPDATER_IS_OUTDATED),
|
||||||
|
openUpdater: () => invoke<void>(IpcEvents.UPDATER_OPEN)
|
||||||
},
|
},
|
||||||
autostart: {
|
autostart: {
|
||||||
isEnabled: () => sendSync<boolean>(IpcEvents.AUTOSTART_ENABLED),
|
isEnabled: () => sendSync<boolean>(IpcEvents.AUTOSTART_ENABLED),
|
||||||
|
|||||||
@@ -5,12 +5,12 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { ipcRenderer } from "electron";
|
import { ipcRenderer } from "electron";
|
||||||
import { IpcEvents } from "shared/IpcEvents";
|
import { IpcEvents, UpdaterIpcEvents } from "shared/IpcEvents";
|
||||||
|
|
||||||
export function invoke<T = any>(event: IpcEvents, ...args: any[]) {
|
export function invoke<T = any>(event: IpcEvents | UpdaterIpcEvents, ...args: any[]) {
|
||||||
return ipcRenderer.invoke(event, ...args) as Promise<T>;
|
return ipcRenderer.invoke(event, ...args) as Promise<T>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function sendSync<T = any>(event: IpcEvents, ...args: any[]) {
|
export function sendSync<T = any>(event: IpcEvents | UpdaterIpcEvents, ...args: any[]) {
|
||||||
return ipcRenderer.sendSync(event, ...args) as T;
|
return ipcRenderer.sendSync(event, ...args) as T;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,24 @@
|
|||||||
|
/*
|
||||||
|
* 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 { contextBridge, ipcRenderer } from "electron";
|
||||||
|
import type { UpdateInfo } from "electron-updater";
|
||||||
|
import { UpdaterIpcEvents } from "shared/IpcEvents";
|
||||||
|
|
||||||
|
import { invoke } from "./typedIpc";
|
||||||
|
|
||||||
|
contextBridge.exposeInMainWorld("VesktopUpdaterNative", {
|
||||||
|
getData: () => invoke<UpdateInfo>(UpdaterIpcEvents.GET_DATA),
|
||||||
|
installUpdate: () => invoke(UpdaterIpcEvents.INSTALL),
|
||||||
|
onProgress: (cb: (percent: number) => void) => {
|
||||||
|
ipcRenderer.on(UpdaterIpcEvents.DOWNLOAD_PROGRESS, (_, percent: number) => cb(percent));
|
||||||
|
},
|
||||||
|
onError: (cb: (message: string) => void) => {
|
||||||
|
ipcRenderer.on(UpdaterIpcEvents.ERROR, (_, message: string) => cb(message));
|
||||||
|
},
|
||||||
|
snoozeUpdate: () => invoke(UpdaterIpcEvents.SNOOZE_UPDATE),
|
||||||
|
ignoreUpdate: () => invoke(UpdaterIpcEvents.IGNORE_UPDATE)
|
||||||
|
});
|
||||||
@@ -6,6 +6,7 @@
|
|||||||
|
|
||||||
import "./screenSharePicker.css";
|
import "./screenSharePicker.css";
|
||||||
|
|
||||||
|
import { classNameFactory } from "@vencord/types/api/Styles";
|
||||||
import { FormSwitch } from "@vencord/types/components";
|
import { FormSwitch } from "@vencord/types/components";
|
||||||
import { closeModal, Logger, Modals, ModalSize, openModal, useAwaiter } from "@vencord/types/utils";
|
import { closeModal, Logger, Modals, ModalSize, openModal, useAwaiter } from "@vencord/types/utils";
|
||||||
import { onceReady } from "@vencord/types/webpack";
|
import { onceReady } from "@vencord/types/webpack";
|
||||||
@@ -15,7 +16,7 @@ import type { Dispatch, SetStateAction } from "react";
|
|||||||
import { MediaEngineStore } from "renderer/common";
|
import { MediaEngineStore } from "renderer/common";
|
||||||
import { addPatch } from "renderer/patches/shared";
|
import { addPatch } from "renderer/patches/shared";
|
||||||
import { State, useSettings, useVesktopState } from "renderer/settings";
|
import { State, useSettings, useVesktopState } from "renderer/settings";
|
||||||
import { classNameFactory, isLinux, isWindows } from "renderer/utils";
|
import { isLinux, isWindows } from "renderer/utils";
|
||||||
|
|
||||||
const StreamResolutions = ["480", "720", "1080", "1440", "2160"] as const;
|
const StreamResolutions = ["480", "720", "1080", "1440", "2160"] as const;
|
||||||
const StreamFps = ["15", "30", "60"] as const;
|
const StreamFps = ["15", "30", "60"] as const;
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ import {
|
|||||||
import { Button, Forms, Text, Toasts } from "@vencord/types/webpack/common";
|
import { Button, Forms, Text, Toasts } from "@vencord/types/webpack/common";
|
||||||
import { Settings } from "shared/settings";
|
import { Settings } from "shared/settings";
|
||||||
|
|
||||||
import { SettingsComponent } from "./Settings";
|
import { cl, SettingsComponent } from "./Settings";
|
||||||
|
|
||||||
export const DeveloperOptionsButton: SettingsComponent = ({ settings }) => {
|
export const DeveloperOptionsButton: SettingsComponent = ({ settings }) => {
|
||||||
return <Button onClick={() => openDeveloperOptionsModal(settings)}>Open Developer Settings</Button>;
|
return <Button onClick={() => openDeveloperOptionsModal(settings)}>Open Developer Settings</Button>;
|
||||||
@@ -41,7 +41,7 @@ function openDeveloperOptionsModal(settings: Settings) {
|
|||||||
<Forms.FormTitle tag="h5" className={Margins.top16}>
|
<Forms.FormTitle tag="h5" className={Margins.top16}>
|
||||||
Debugging
|
Debugging
|
||||||
</Forms.FormTitle>
|
</Forms.FormTitle>
|
||||||
<div className="vcd-settings-button-grid">
|
<div className={cl("button-grid")}>
|
||||||
<Button onClick={() => VesktopNative.debug.launchGpu()}>Open chrome://gpu</Button>
|
<Button onClick={() => VesktopNative.debug.launchGpu()}>Open chrome://gpu</Button>
|
||||||
<Button onClick={() => VesktopNative.debug.launchWebrtcInternals()}>
|
<Button onClick={() => VesktopNative.debug.launchWebrtcInternals()}>
|
||||||
Open chrome://webrtc-internals
|
Open chrome://webrtc-internals
|
||||||
@@ -75,7 +75,7 @@ const VencordLocationPicker: SettingsComponent = ({ settings }) => {
|
|||||||
"the default location"
|
"the default location"
|
||||||
)}
|
)}
|
||||||
</Forms.FormText>
|
</Forms.FormText>
|
||||||
<div className="vcd-settings-button-grid">
|
<div className={cl("button-grid")}>
|
||||||
<Button
|
<Button
|
||||||
size={Button.Sizes.SMALL}
|
size={Button.Sizes.SMALL}
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
|
|
||||||
import "./settings.css";
|
import "./settings.css";
|
||||||
|
|
||||||
|
import { classNameFactory } from "@vencord/types/api/Styles";
|
||||||
import { ErrorBoundary } from "@vencord/types/components";
|
import { ErrorBoundary } from "@vencord/types/components";
|
||||||
import { Forms, Text } from "@vencord/types/webpack/common";
|
import { Forms, Text } from "@vencord/types/webpack/common";
|
||||||
import { ComponentType } from "react";
|
import { ComponentType } from "react";
|
||||||
@@ -16,6 +17,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 { Updater } from "./Updater";
|
||||||
import { UserAssetsButton } from "./UserAssets";
|
import { UserAssetsButton } from "./UserAssets";
|
||||||
import { VesktopSettingsSwitch } from "./VesktopSettingsSwitch";
|
import { VesktopSettingsSwitch } from "./VesktopSettingsSwitch";
|
||||||
import { WindowsTransparencyControls } from "./WindowsTransparencyControls";
|
import { WindowsTransparencyControls } from "./WindowsTransparencyControls";
|
||||||
@@ -29,6 +31,8 @@ interface BooleanSetting {
|
|||||||
invisible?(): boolean;
|
invisible?(): boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const cl = classNameFactory("vcd-settings-");
|
||||||
|
|
||||||
export type SettingsComponent = ComponentType<{ settings: typeof Settings.store }>;
|
export type SettingsComponent = ComponentType<{ settings: typeof Settings.store }>;
|
||||||
|
|
||||||
const SettingsOptions: Record<string, Array<BooleanSetting | SettingsComponent>> = {
|
const SettingsOptions: Record<string, Array<BooleanSetting | SettingsComponent>> = {
|
||||||
@@ -144,12 +148,12 @@ function SettingsSections() {
|
|||||||
const Settings = useSettings();
|
const Settings = useSettings();
|
||||||
|
|
||||||
const sections = Object.entries(SettingsOptions).map(([title, settings], i, arr) => (
|
const sections = Object.entries(SettingsOptions).map(([title, settings], i, arr) => (
|
||||||
<div key={title} className="vcd-settings-category">
|
<div key={title} className={cl("category")}>
|
||||||
<Text variant="heading-lg/semibold" color="header-primary" className="vcd-settings-category-title">
|
<Text variant="heading-lg/semibold" color="header-primary" className={cl("category-title")}>
|
||||||
{title}
|
{title}
|
||||||
</Text>
|
</Text>
|
||||||
|
|
||||||
<div className="vcd-settings-category-content">
|
<div className={cl("category-content")}>
|
||||||
{settings.map(Setting => {
|
{settings.map(Setting => {
|
||||||
if (typeof Setting === "function") return <Setting settings={Settings} />;
|
if (typeof Setting === "function") return <Setting settings={Settings} />;
|
||||||
|
|
||||||
@@ -169,7 +173,7 @@ function SettingsSections() {
|
|||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{i < arr.length - 1 && <Forms.FormDivider className="vcd-settings-category-divider" />}
|
{i < arr.length - 1 && <Forms.FormDivider className={cl("category-divider")} />}
|
||||||
</div>
|
</div>
|
||||||
));
|
));
|
||||||
|
|
||||||
@@ -180,9 +184,10 @@ export default ErrorBoundary.wrap(
|
|||||||
function SettingsUI() {
|
function SettingsUI() {
|
||||||
return (
|
return (
|
||||||
<section>
|
<section>
|
||||||
<Text variant="heading-xl/semibold" color="header-primary" className="vcd-settings-title">
|
<Text variant="heading-xl/semibold" color="header-primary" className={cl("title")}>
|
||||||
Vesktop Settings
|
Vesktop Settings
|
||||||
</Text>
|
</Text>
|
||||||
|
<Updater />
|
||||||
<SettingsSections />
|
<SettingsSections />
|
||||||
</section>
|
</section>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -0,0 +1,31 @@
|
|||||||
|
/*
|
||||||
|
* 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 { useAwaiter } from "@vencord/types/utils";
|
||||||
|
import { Button, Text } from "@vencord/types/webpack/common";
|
||||||
|
|
||||||
|
import { cl } from "./Settings";
|
||||||
|
|
||||||
|
export function Updater() {
|
||||||
|
const [isOutdated] = useAwaiter(VesktopNative.app.isOutdated);
|
||||||
|
|
||||||
|
if (!isOutdated) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={cl("updater-card")}>
|
||||||
|
<Text variant="text-md/semibold">Your Vesktop is outdated!</Text>
|
||||||
|
<Text variant="text-sm/normal">Staying up to date is important for security and stability.</Text>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
onClick={() => VesktopNative.app.openUpdater()}
|
||||||
|
size={Button.Sizes.SMALL}
|
||||||
|
color={Button.Colors.TRANSPARENT}
|
||||||
|
>
|
||||||
|
Open Updater
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -7,6 +7,8 @@
|
|||||||
import { FormSwitch } from "@vencord/types/components";
|
import { FormSwitch } from "@vencord/types/components";
|
||||||
import { ComponentProps } from "react";
|
import { ComponentProps } from "react";
|
||||||
|
|
||||||
|
import { cl } from "./Settings";
|
||||||
|
|
||||||
export function VesktopSettingsSwitch(props: ComponentProps<typeof FormSwitch>) {
|
export function VesktopSettingsSwitch(props: ComponentProps<typeof FormSwitch>) {
|
||||||
return <FormSwitch {...props} hideBorder className="vcd-settings-switch" />;
|
return <FormSwitch {...props} hideBorder className={cl("switch")} />;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,3 +32,16 @@
|
|||||||
.vcd-settings-switch {
|
.vcd-settings-switch {
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.vcd-settings-updater-card {
|
||||||
|
padding: 1em;
|
||||||
|
margin-bottom: 1em;
|
||||||
|
display: grid;
|
||||||
|
gap: 0.5em;
|
||||||
|
|
||||||
|
border-radius: 8px;
|
||||||
|
background-color: var(--bg-secondary);
|
||||||
|
background: var(--background-feedback-warning);
|
||||||
|
border: 1px solid var(--info-warning-foreground);
|
||||||
|
color: var(--text-feedback-warning);
|
||||||
|
}
|
||||||
@@ -19,26 +19,3 @@ const { platform } = navigator;
|
|||||||
export const isWindows = platform.startsWith("Win");
|
export const isWindows = platform.startsWith("Win");
|
||||||
export const isMac = platform.startsWith("Mac");
|
export const isMac = platform.startsWith("Mac");
|
||||||
export const isLinux = platform.startsWith("Linux");
|
export const isLinux = platform.startsWith("Linux");
|
||||||
|
|
||||||
type ClassNameFactoryArg = string | string[] | Record<string, unknown> | false | null | undefined | 0 | "";
|
|
||||||
/**
|
|
||||||
* @param prefix The prefix to add to each class, defaults to `""`
|
|
||||||
* @returns A classname generator function
|
|
||||||
* @example
|
|
||||||
* const cl = classNameFactory("plugin-");
|
|
||||||
*
|
|
||||||
* cl("base", ["item", "editable"], { selected: null, disabled: true })
|
|
||||||
* // => "plugin-base plugin-item plugin-editable plugin-disabled"
|
|
||||||
*/
|
|
||||||
export const classNameFactory =
|
|
||||||
(prefix: string = "") =>
|
|
||||||
(...args: ClassNameFactoryArg[]) => {
|
|
||||||
const classNames = new Set<string>();
|
|
||||||
for (const arg of args) {
|
|
||||||
if (arg && typeof arg === "string") classNames.add(arg);
|
|
||||||
else if (Array.isArray(arg)) arg.forEach(name => classNames.add(name));
|
|
||||||
else if (arg && typeof arg === "object")
|
|
||||||
Object.entries(arg).forEach(([name, value]) => value && classNames.add(name));
|
|
||||||
}
|
|
||||||
return Array.from(classNames, name => prefix + name).join(" ");
|
|
||||||
};
|
|
||||||
|
|||||||
+11
-3
@@ -27,9 +27,8 @@ export const enum IpcEvents {
|
|||||||
GET_VENCORD_DIR = "VCD_GET_VENCORD_DIR",
|
GET_VENCORD_DIR = "VCD_GET_VENCORD_DIR",
|
||||||
SELECT_VENCORD_DIR = "VCD_SELECT_VENCORD_DIR",
|
SELECT_VENCORD_DIR = "VCD_SELECT_VENCORD_DIR",
|
||||||
|
|
||||||
UPDATER_GET_DATA = "VCD_UPDATER_GET_DATA",
|
UPDATER_IS_OUTDATED = "VCD_UPDATER_IS_OUTDATED",
|
||||||
UPDATER_DOWNLOAD = "VCD_UPDATER_DOWNLOAD",
|
UPDATER_OPEN = "VCD_UPDATER_OPEN",
|
||||||
UPDATE_IGNORE = "VCD_UPDATE_IGNORE",
|
|
||||||
|
|
||||||
SPELLCHECK_GET_AVAILABLE_LANGUAGES = "VCD_SPELLCHECK_GET_AVAILABLE_LANGUAGES",
|
SPELLCHECK_GET_AVAILABLE_LANGUAGES = "VCD_SPELLCHECK_GET_AVAILABLE_LANGUAGES",
|
||||||
SPELLCHECK_RESULT = "VCD_SPELLCHECK_RESULT",
|
SPELLCHECK_RESULT = "VCD_SPELLCHECK_RESULT",
|
||||||
@@ -62,6 +61,15 @@ export const enum IpcEvents {
|
|||||||
CHOOSE_USER_ASSET = "VCD_CHOOSE_USER_ASSET"
|
CHOOSE_USER_ASSET = "VCD_CHOOSE_USER_ASSET"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const enum UpdaterIpcEvents {
|
||||||
|
GET_DATA = "VCD_UPDATER_GET_DATA",
|
||||||
|
INSTALL = "VCD_UPDATER_INSTALL",
|
||||||
|
DOWNLOAD_PROGRESS = "VCD_UPDATER_DOWNLOAD_PROGRESS",
|
||||||
|
ERROR = "VCD_UPDATER_ERROR",
|
||||||
|
SNOOZE_UPDATE = "VCD_UPDATER_SNOOZE_UPDATE",
|
||||||
|
IGNORE_UPDATE = "VCD_UPDATER_IGNORE_UPDATE"
|
||||||
|
}
|
||||||
|
|
||||||
export const enum IpcCommands {
|
export const enum IpcCommands {
|
||||||
RPC_ACTIVITY = "rpc:activity",
|
RPC_ACTIVITY = "rpc:activity",
|
||||||
RPC_INVITE = "rpc:invite",
|
RPC_INVITE = "rpc:invite",
|
||||||
|
|||||||
Vendored
+5
@@ -58,4 +58,9 @@ export interface State {
|
|||||||
linuxAutoStartEnabled?: boolean;
|
linuxAutoStartEnabled?: boolean;
|
||||||
|
|
||||||
vencordDir?: string;
|
vencordDir?: string;
|
||||||
|
|
||||||
|
updater?: {
|
||||||
|
ignoredVersion?: string;
|
||||||
|
snoozeUntil?: number;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,12 @@
|
|||||||
|
/*
|
||||||
|
* 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
|
||||||
|
*/
|
||||||
|
|
||||||
|
export const enum Millis {
|
||||||
|
SECOND = 1000,
|
||||||
|
MINUTE = 60 * SECOND,
|
||||||
|
HOUR = 60 * MINUTE,
|
||||||
|
DAY = 24 * HOUR
|
||||||
|
}
|
||||||
+7
-16
@@ -1,21 +1,12 @@
|
|||||||
:root {
|
:root {
|
||||||
--bg: white;
|
color-scheme: light dark;
|
||||||
--fg: black;
|
|
||||||
--fg-secondary: #313338;
|
|
||||||
--fg-semi-trans: rgb(0 0 0 / 0.2);
|
|
||||||
--link: #006ce7;
|
|
||||||
--link-hover: #005bb5;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (prefers-color-scheme: dark) {
|
--bg: light-dark(white, hsl(223 6.7% 20.6%));
|
||||||
:root {
|
--fg: light-dark(black, white);
|
||||||
--bg: hsl(223 6.7% 20.6%);
|
--fg-secondary: light-dark(#313338, #b5bac1);
|
||||||
--fg: white;
|
--fg-semi-trans: light-dark(rgb(0 0 0 / 0.2), rgb(255 255 255 / 0.2));
|
||||||
--fg-secondary: #b5bac1;
|
--link: light-dark(#006ce7, #00a8fc);
|
||||||
--fg-semi-trans: rgb(255 255 255 / 0.2);
|
--link-hover: light-dark(#005bb5, #0086c3);
|
||||||
--link: #00a8fc;
|
|
||||||
--link-hover: #0086c3;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
|
|||||||
@@ -0,0 +1,268 @@
|
|||||||
|
<head>
|
||||||
|
<title>Vesktop Updater</title>
|
||||||
|
|
||||||
|
<link rel="stylesheet" href="./style.css" type="text/css" />
|
||||||
|
|
||||||
|
<style>
|
||||||
|
html,
|
||||||
|
body {
|
||||||
|
height: 100%;
|
||||||
|
margin: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
padding: 2em;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
height: 100vh;
|
||||||
|
|
||||||
|
header,
|
||||||
|
footer {
|
||||||
|
flex: 0 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
main {
|
||||||
|
flex: 1 1 0%;
|
||||||
|
min-height: 0;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
footer {
|
||||||
|
margin-top: 1em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#versions {
|
||||||
|
display: inline-grid;
|
||||||
|
grid-template-columns: auto 1fr;
|
||||||
|
column-gap: 0.5em;
|
||||||
|
|
||||||
|
.label {
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.value {
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
#current-version {
|
||||||
|
color: #fc8f8a;
|
||||||
|
}
|
||||||
|
|
||||||
|
#new-version {
|
||||||
|
color: #71c07f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#release-notes {
|
||||||
|
display: grid;
|
||||||
|
gap: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
#buttons {
|
||||||
|
display: flex;
|
||||||
|
gap: 0.5em;
|
||||||
|
justify-content: end;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 0.5rem 1rem;
|
||||||
|
font-size: 1.2em;
|
||||||
|
color: var(--fg);
|
||||||
|
border: none;
|
||||||
|
border-radius: 3px;
|
||||||
|
font-weight: bold;
|
||||||
|
transition: filter 0.2 ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
button:hover,
|
||||||
|
button:active {
|
||||||
|
filter: brightness(0.9);
|
||||||
|
}
|
||||||
|
|
||||||
|
.green {
|
||||||
|
background-color: #248046;
|
||||||
|
}
|
||||||
|
|
||||||
|
.grey {
|
||||||
|
background-color: rgba(151, 151, 159, 0.12);
|
||||||
|
}
|
||||||
|
|
||||||
|
h1,
|
||||||
|
h2,
|
||||||
|
h3,
|
||||||
|
h4,
|
||||||
|
h5,
|
||||||
|
h6 {
|
||||||
|
margin: 0 0 0.5em 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
margin-bottom: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
dialog {
|
||||||
|
width: 80%;
|
||||||
|
padding: 2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
progress {
|
||||||
|
width: 100%;
|
||||||
|
height: 1.5em;
|
||||||
|
margin-top: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
#error {
|
||||||
|
color: red;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spinner-wrapper {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
margin-top: 2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spinner {
|
||||||
|
width: 48px;
|
||||||
|
height: 48px;
|
||||||
|
border: 5px solid var(--fg);
|
||||||
|
border-bottom-color: transparent;
|
||||||
|
border-radius: 50%;
|
||||||
|
display: inline-block;
|
||||||
|
box-sizing: border-box;
|
||||||
|
animation: rotation 1s linear infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes rotation {
|
||||||
|
to {
|
||||||
|
transform: rotate(360deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<header>
|
||||||
|
<h1>An update is available!</h1>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<main>
|
||||||
|
<p id="versions">
|
||||||
|
<span class="label">Current version:</span> <span id="current-version" class="value"></span>
|
||||||
|
<span class="label">New version:</span> <span id="new-version" class="value"></span>
|
||||||
|
</p>
|
||||||
|
<h2>Release Notes</h2>
|
||||||
|
|
||||||
|
<div id="release-notes"></div>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<footer>
|
||||||
|
<section id="buttons">
|
||||||
|
<button id="update-button" class="green">Install update & restart</button>
|
||||||
|
<button id="later-button" class="grey">Remind me later</button>
|
||||||
|
<button id="ignore-button" class="grey">Ignore this update</button>
|
||||||
|
</section>
|
||||||
|
</footer>
|
||||||
|
|
||||||
|
<dialog id="update-dialog" closedby="none">
|
||||||
|
<h2>Downloading Update</h2>
|
||||||
|
<p>
|
||||||
|
Please wait while the update is being downloaded. Once the update finished downloading, it will
|
||||||
|
automatically install and Vesktop will restart.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p id="linux-note" style="display: none">You will likely be prompted to enter your password. Do so to finish the
|
||||||
|
update.</p>
|
||||||
|
|
||||||
|
<progress id="download-progress" value="0" max="100"></progress>
|
||||||
|
|
||||||
|
<p id="error"></p>
|
||||||
|
</dialog>
|
||||||
|
|
||||||
|
<dialog id="installing-dialog" closedby="none">
|
||||||
|
<h2>Installing Update</h2>
|
||||||
|
<p>Please wait while the update is being installed. Vesktop will restart shortly.</p>
|
||||||
|
|
||||||
|
<div class="spinner-wrapper">
|
||||||
|
<div class="spinner"></div>
|
||||||
|
</div>
|
||||||
|
</dialog>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
<script type="module">
|
||||||
|
const { update, version: currentVersion } = await VesktopUpdaterNative.getData();
|
||||||
|
|
||||||
|
document.getElementById("current-version").textContent = currentVersion;
|
||||||
|
document.getElementById("new-version").textContent = update.version;
|
||||||
|
document.getElementById("release-notes").innerHTML = update.releaseNotes
|
||||||
|
.map(({ version, note: html }) => `
|
||||||
|
<section>
|
||||||
|
<h3>Version ${version}</h3>
|
||||||
|
<div>${html.replace(/<\/?h([1-3])/g, (m, level) => m.replace(level, Number(level) + 3))}</div>
|
||||||
|
</section>
|
||||||
|
`)
|
||||||
|
.join("\n");
|
||||||
|
|
||||||
|
document.querySelectorAll("a").forEach((a) => {
|
||||||
|
a.target = "_blank";
|
||||||
|
});
|
||||||
|
|
||||||
|
// remove useless headings
|
||||||
|
document.querySelectorAll("h3, h4, h5, h6").forEach(h => {
|
||||||
|
if (h.textContent.trim().toLowerCase() === "what's changed") {
|
||||||
|
h.remove()
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
/** @type {HTMLDialogElement} */
|
||||||
|
const updateDialog = document.getElementById("update-dialog");
|
||||||
|
/** @type {HTMLDialogElement} */
|
||||||
|
const installingDialog = document.getElementById("installing-dialog");
|
||||||
|
/** @type {HTMLProgressElement} */
|
||||||
|
const downloadProgress = document.getElementById("download-progress");
|
||||||
|
/** @type {HTMLElement} */
|
||||||
|
const errorText = document.getElementById("error");
|
||||||
|
|
||||||
|
document.getElementById("update-button").addEventListener("click", () => {
|
||||||
|
downloadProgress.value = 0;
|
||||||
|
errorText.textContent = "";
|
||||||
|
|
||||||
|
if (navigator.platform.startsWith("Linux")) {
|
||||||
|
document.getElementById("linux-note").style.display = "initial";
|
||||||
|
}
|
||||||
|
|
||||||
|
updateDialog.showModal();
|
||||||
|
|
||||||
|
VesktopUpdaterNative.installUpdate().then(() => {
|
||||||
|
downloadProgress.value = 100
|
||||||
|
updateDialog.closedBy = "any";
|
||||||
|
|
||||||
|
installingDialog.showModal();
|
||||||
|
updateDialog.style.visibility = "hidden";
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
document.getElementById("later-button").addEventListener("click", () => VesktopUpdaterNative.snoozeUpdate());
|
||||||
|
document.getElementById("ignore-button").addEventListener("click", () => {
|
||||||
|
const confirmed = confirm("Are you sure you want to ignore this update? You will not be notified about this update again. Updates are important for security and stability.");
|
||||||
|
if (confirmed)
|
||||||
|
VesktopUpdaterNative.ignoreUpdate()
|
||||||
|
});
|
||||||
|
|
||||||
|
VesktopUpdaterNative.onProgress(percent => downloadProgress.value = percent);
|
||||||
|
VesktopUpdaterNative.onError(message => {
|
||||||
|
updateDialog.closedBy = "any";
|
||||||
|
errorText.textContent = `An error occurred while downloading the update: ${message}`;
|
||||||
|
installingDialog.close();
|
||||||
|
updateDialog.style.visibility = "initial";
|
||||||
|
});
|
||||||
|
</script>
|
||||||
Reference in New Issue
Block a user