fix ScreensharePicker ui & modernise all UI

This commit is contained in:
Vendicated
2025-12-12 18:43:30 +01:00
parent 02ab7165aa
commit 5c992d66a6
15 changed files with 232 additions and 176 deletions
+1 -1
View File
@@ -38,7 +38,7 @@
"@stylistic/eslint-plugin": "^5.6.1", "@stylistic/eslint-plugin": "^5.6.1",
"@types/node": "^24.10.1", "@types/node": "^24.10.1",
"@types/react": "19.2.1", "@types/react": "19.2.1",
"@vencord/types": "^1.13.2", "@vencord/types": "^1.13.7",
"dotenv": "^17.2.3", "dotenv": "^17.2.3",
"electron": "^39.2.3", "electron": "^39.2.3",
"electron-builder": "^26.0.12", "electron-builder": "^26.0.12",
+19 -12
View File
@@ -36,8 +36,8 @@ importers:
specifier: 19.2.1 specifier: 19.2.1
version: 19.2.1 version: 19.2.1
'@vencord/types': '@vencord/types':
specifier: ^1.13.2 specifier: ^1.13.7
version: 1.13.2(@types/react-dom@18.3.1)(@types/react@19.2.1) version: 1.13.7(@types/react-dom@18.3.1)(@types/react@19.2.1)
dotenv: dotenv:
specifier: ^17.2.3 specifier: ^17.2.3
version: 17.2.3 version: 17.2.3
@@ -629,6 +629,9 @@ packages:
'@types/node@22.19.1': '@types/node@22.19.1':
resolution: {integrity: sha512-LCCV0HdSZZZb34qifBsyWlUmok6W7ouER+oQIGBScS8EsZsQbrtFTUrDX4hOl+CS6p7cnNC4td+qrSVGSCTUfQ==} resolution: {integrity: sha512-LCCV0HdSZZZb34qifBsyWlUmok6W7ouER+oQIGBScS8EsZsQbrtFTUrDX4hOl+CS6p7cnNC4td+qrSVGSCTUfQ==}
'@types/node@22.19.2':
resolution: {integrity: sha512-LPM2G3Syo1GLzXLGJAKdqoU35XvrWzGJ21/7sgZTUpbkBaOasTj8tjwn6w+hCkqaa1TfJ/w67rJSwYItlJ2mYw==}
'@types/node@24.10.1': '@types/node@24.10.1':
resolution: {integrity: sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==} resolution: {integrity: sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==}
@@ -714,8 +717,8 @@ packages:
peerDependencies: peerDependencies:
'@types/react': ^19.0.10 '@types/react': ^19.0.10
'@vencord/types@1.13.2': '@vencord/types@1.13.7':
resolution: {integrity: sha512-5RifSCC1X4xzoNaDTMANRLVZreEsHWWsGs6iBooqnBkLeigfP7FqAQfn6onogk/2+YQKNMTQT8N+MD+HGes5EA==} resolution: {integrity: sha512-MThrBwMEJ0CvKMrVgJC4cvJuDwwFXEhgSOJnmDZxmJeKOByVWLo7xxjn1suHkE1/euw9J5lGTfz1QTiYVXbLqA==}
peerDependencies: peerDependencies:
'@types/react': 18.3.1 '@types/react': 18.3.1
'@types/react-dom': 18.3.1 '@types/react-dom': 18.3.1
@@ -2983,8 +2986,8 @@ packages:
peerDependencies: peerDependencies:
typescript: '>=4.8.4' typescript: '>=4.8.4'
ts-pattern@5.8.0: ts-pattern@5.9.0:
resolution: {integrity: sha512-kIjN2qmWiHnhgr5DAkAafF9fwb0T5OhMVSWrm8XEdTFnX6+wfXwYOFjeF86UZ54vduqiR7BfqScFmXSzSaH8oA==} resolution: {integrity: sha512-6s5V71mX8qBUmlgbrfL33xDUwO0fq48rxAu2LBE11WBeGdpCPOsXksQbZJHvHwhrd3QjUusd3mAOM5Gg0mFBLg==}
tsconfig-paths@3.15.0: tsconfig-paths@3.15.0:
resolution: {integrity: sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==} resolution: {integrity: sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==}
@@ -3236,7 +3239,7 @@ snapshots:
make-fetch-happen: 10.2.1 make-fetch-happen: 10.2.1
nopt: 6.0.0 nopt: 6.0.0
proc-log: 2.0.1 proc-log: 2.0.1
semver: 7.7.3 semver: 7.7.2
tar: 6.2.1 tar: 6.2.1
which: 2.0.2 which: 2.0.2
transitivePeerDependencies: transitivePeerDependencies:
@@ -3574,7 +3577,7 @@ snapshots:
'@npmcli/fs@2.1.2': '@npmcli/fs@2.1.2':
dependencies: dependencies:
'@gar/promisify': 1.1.3 '@gar/promisify': 1.1.3
semver: 7.7.3 semver: 7.7.2
'@npmcli/move-file@2.0.1': '@npmcli/move-file@2.0.1':
dependencies: dependencies:
@@ -3641,6 +3644,10 @@ snapshots:
dependencies: dependencies:
undici-types: 6.21.0 undici-types: 6.21.0
'@types/node@22.19.2':
dependencies:
undici-types: 6.21.0
'@types/node@24.10.1': '@types/node@24.10.1':
dependencies: dependencies:
undici-types: 7.16.0 undici-types: 7.16.0
@@ -3770,16 +3777,16 @@ snapshots:
moment: 2.30.1 moment: 2.30.1
type-fest: 4.41.0 type-fest: 4.41.0
'@vencord/types@1.13.2(@types/react-dom@18.3.1)(@types/react@19.2.1)': '@vencord/types@1.13.7(@types/react-dom@18.3.1)(@types/react@19.2.1)':
dependencies: dependencies:
'@types/lodash': 4.17.15 '@types/lodash': 4.17.15
'@types/node': 22.19.1 '@types/node': 22.19.2
'@types/react': 19.2.1 '@types/react': 19.2.1
'@types/react-dom': 18.3.1 '@types/react-dom': 18.3.1
'@vencord/discord-types': 1.0.0(@types/react@19.2.1) '@vencord/discord-types': 1.0.0(@types/react@19.2.1)
highlight.js: 11.11.1 highlight.js: 11.11.1
moment: 2.30.1 moment: 2.30.1
ts-pattern: 5.8.0 ts-pattern: 5.9.0
type-fest: 4.41.0 type-fest: 4.41.0
'@vencord/venmic@6.1.0': '@vencord/venmic@6.1.0':
@@ -6597,7 +6604,7 @@ snapshots:
dependencies: dependencies:
typescript: 5.9.3 typescript: 5.9.3
ts-pattern@5.8.0: {} ts-pattern@5.9.0: {}
tsconfig-paths@3.15.0: tsconfig-paths@3.15.0:
dependencies: dependencies:
+71 -55
View File
@@ -7,10 +7,31 @@
import "./screenSharePicker.css"; import "./screenSharePicker.css";
import { classNameFactory } from "@vencord/types/api/Styles"; import { classNameFactory } from "@vencord/types/api/Styles";
import { CogWheel, FormSwitch, RestartIcon } from "@vencord/types/components"; import {
import { closeModal, Logger, Modals, ModalSize, openModal, useAwaiter, useForceUpdater } from "@vencord/types/utils"; BaseText,
Button,
Card,
CogWheel,
FormSwitch,
Heading,
HeadingTertiary,
Margins,
Paragraph,
RestartIcon,
Span
} from "@vencord/types/components";
import {
closeModal,
Logger,
ModalCloseButton,
Modals,
ModalSize,
openModal,
useAwaiter,
useForceUpdater
} from "@vencord/types/utils";
import { onceReady } from "@vencord/types/webpack"; import { onceReady } from "@vencord/types/webpack";
import { Button, Card, FluxDispatcher, Forms, Select, Text, UserStore, useState } from "@vencord/types/webpack/common"; import { FluxDispatcher, Select, UserStore, useState } from "@vencord/types/webpack/common";
import { Node } from "@vencord/venmic"; import { Node } from "@vencord/venmic";
import type { Dispatch, SetStateAction } from "react"; import type { Dispatch, SetStateAction } from "react";
import { MediaEngineStore } from "renderer/common"; import { MediaEngineStore } from "renderer/common";
@@ -18,6 +39,8 @@ import { addPatch } from "renderer/patches/shared";
import { State, useSettings, useVesktopState } from "renderer/settings"; import { State, useSettings, useVesktopState } from "renderer/settings";
import { isLinux, isWindows } from "renderer/utils"; import { isLinux, isWindows } from "renderer/utils";
import { SimpleErrorBoundary } from "./SimpleErrorBoundary";
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;
@@ -145,6 +168,9 @@ export function openScreenSharePicker(screens: Source[], skipPicker: boolean) {
onCloseRequest() { onCloseRequest() {
closeModal(key); closeModal(key);
reject("Aborted"); reject("Aborted");
},
onCloseCallback() {
reject("Aborted");
} }
} }
); );
@@ -165,9 +191,7 @@ function ScreenPicker({ screens, chooseScreen }: { screens: Source[]; chooseScre
/> />
<img src={url} alt="" /> <img src={url} alt="" />
<Text className={cl("screen-name")} variant="text-sm/normal"> <Paragraph className={cl("screen-name")}>{name}</Paragraph>
{name}
</Text>
</label> </label>
))} ))}
</div> </div>
@@ -188,19 +212,16 @@ function AudioSettingsModal({
return ( return (
<Modals.ModalRoot {...modalProps} size={ModalSize.MEDIUM}> <Modals.ModalRoot {...modalProps} size={ModalSize.MEDIUM}>
<Modals.ModalHeader className={cl("header")}> <Modals.ModalHeader className={cl("header")}>
<Forms.FormTitle tag="h2">Venmic Settings</Forms.FormTitle> <BaseText size="lg" weight="semibold" tag="h3" style={{ flexGrow: 1 }}>
<Modals.ModalCloseButton onClick={close} className={cl("header-close-button")} /> Audio Settings
</BaseText>
<ModalCloseButton onClick={close} />
</Modals.ModalHeader> </Modals.ModalHeader>
<Modals.ModalContent className={cl("modal", "venmic-settings")}> <Modals.ModalContent className={cl("modal", "venmic-settings")}>
<FormSwitch <FormSwitch
title="Microphone Workaround" title="Microphone Workaround"
description={ description="Work around an issue that causes the microphone to be shared instead of the correct audio. Only enable if you're experiencing this issue."
<>
Work around an issue that causes the microphone to be shared instead of the correct audio.
Only enable if you're experiencing this issue.
</>
}
hideBorder hideBorder
onChange={v => (Settings.audio = { ...Settings.audio, workaround: v })} onChange={v => (Settings.audio = { ...Settings.audio, workaround: v })}
value={Settings.audio?.workaround ?? false} value={Settings.audio?.workaround ?? false}
@@ -208,10 +229,7 @@ function AudioSettingsModal({
<FormSwitch <FormSwitch
title="Only Speakers" title="Only Speakers"
description={ description={
<> 'When sharing entire desktop audio, only share apps that play to a speaker. You may want to disable this when using "mix bussing".'
When sharing entire desktop audio, only share apps that play to a speaker. You may want to
disable this when using "mix bussing".
</>
} }
hideBorder hideBorder
onChange={v => (Settings.audio = { ...Settings.audio, onlySpeakers: v })} onChange={v => (Settings.audio = { ...Settings.audio, onlySpeakers: v })}
@@ -231,7 +249,7 @@ function AudioSettingsModal({
/> />
<FormSwitch <FormSwitch
title="Ignore Inputs" title="Ignore Inputs"
description={<>Exclude nodes that are intended to capture audio.</>} description="Exclude nodes that are intended to capture audio."
hideBorder hideBorder
onChange={v => (Settings.audio = { ...Settings.audio, ignoreInputMedia: v })} onChange={v => (Settings.audio = { ...Settings.audio, ignoreInputMedia: v })}
value={Settings.audio?.ignoreInputMedia ?? true} value={Settings.audio?.ignoreInputMedia ?? true}
@@ -239,10 +257,7 @@ function AudioSettingsModal({
<FormSwitch <FormSwitch
title="Ignore Virtual" title="Ignore Virtual"
description={ description={
<> 'Exclude virtual nodes, such as nodes belonging to loopbacks. This might be useful when using "mix bussing".'
Exclude virtual nodes, such as nodes belonging to loopbacks. This might be useful when using
"mix bussing".
</>
} }
hideBorder hideBorder
onChange={v => (Settings.audio = { ...Settings.audio, ignoreVirtual: v })} onChange={v => (Settings.audio = { ...Settings.audio, ignoreVirtual: v })}
@@ -250,7 +265,7 @@ function AudioSettingsModal({
/> />
<FormSwitch <FormSwitch
title="Ignore Devices" title="Ignore Devices"
description={<>Exclude device nodes, such as nodes belonging to microphones or speakers.</>} description="Exclude device nodes, such as nodes belonging to microphones or speakers."
hideBorder hideBorder
onChange={v => onChange={v =>
(Settings.audio = { (Settings.audio = {
@@ -263,7 +278,7 @@ function AudioSettingsModal({
/> />
<FormSwitch <FormSwitch
title="Granular Selection" title="Granular Selection"
description={<>Allow to select applications more granularly.</>} description="Allow to select applications more granularly."
hideBorder hideBorder
onChange={value => { onChange={value => {
Settings.audio = { ...Settings.audio, granularSelect: value }; Settings.audio = { ...Settings.audio, granularSelect: value };
@@ -289,7 +304,7 @@ function AudioSettingsModal({
/> />
</Modals.ModalContent> </Modals.ModalContent>
<Modals.ModalFooter className={cl("footer")}> <Modals.ModalFooter className={cl("footer")}>
<Button color={Button.Colors.TRANSPARENT} onClick={close}> <Button variant="secondary" onClick={close}>
Back Back
</Button> </Button>
</Modals.ModalFooter> </Modals.ModalFooter>
@@ -310,7 +325,7 @@ function OptionRadio<Settings extends object, Key extends keyof Settings>(props:
<div className={cl("option-radios")}> <div className={cl("option-radios")}>
{(options as string[]).map((option, idx) => ( {(options as string[]).map((option, idx) => (
<label className={cl("option-radio")} data-checked={settings[settingsKey] === option} key={option}> <label className={cl("option-radio")} data-checked={settings[settingsKey] === option} key={option}>
<Text variant="text-sm/bold">{labels?.[idx] ?? option}</Text> <Span weight="bold">{labels?.[idx] ?? option}</Span>
<input <input
className={cl("option-input")} className={cl("option-input")}
type="radio" type="radio"
@@ -361,18 +376,18 @@ function StreamSettingsUi({
return ( return (
<div> <div>
<Forms.FormTitle>What you're streaming</Forms.FormTitle> <HeadingTertiary className={Margins.bottom8}>What you're streaming</HeadingTertiary>
<Card className={cl("card", "preview")}> <Card className={cl("card", "preview")}>
<img src={thumb} alt="" className={cl(isLinux ? "preview-img-linux" : "preview-img")} /> <img src={thumb} alt="" className={cl(isLinux ? "preview-img-linux" : "preview-img")} />
<Text variant="text-sm/normal">{source.name}</Text> <Paragraph>{source.name}</Paragraph>
</Card> </Card>
<Forms.FormTitle>Stream Settings</Forms.FormTitle> <HeadingTertiary className={Margins.bottom8}>Stream Settings</HeadingTertiary>
<Card className={cl("card")}> <Card className={cl("card")}>
<div className={cl("quality")}> <div className={cl("quality")}>
<section className={cl("quality-section")}> <section className={cl("quality-section")}>
<Forms.FormTitle>Resolution</Forms.FormTitle> <Heading tag="h5">Resolution</Heading>
<OptionRadio <OptionRadio
options={StreamResolutions} options={StreamResolutions}
settings={qualitySettings} settings={qualitySettings}
@@ -382,7 +397,7 @@ function StreamSettingsUi({
</section> </section>
<section className={cl("quality-section")}> <section className={cl("quality-section")}>
<Forms.FormTitle>Frame Rate</Forms.FormTitle> <Heading tag="h5">Frame Rate</Heading>
<OptionRadio <OptionRadio
options={StreamFps} options={StreamFps}
settings={qualitySettings} settings={qualitySettings}
@@ -393,7 +408,7 @@ function StreamSettingsUi({
</div> </div>
<div className={cl("quality")}> <div className={cl("quality")}>
<section className={cl("quality-section")}> <section className={cl("quality-section")}>
<Forms.FormTitle>Content Type</Forms.FormTitle> <Heading tag="h5">Content Type</Heading>
<div> <div>
<OptionRadio <OptionRadio
options={["motion", "detail"]} options={["motion", "detail"]}
@@ -402,12 +417,11 @@ function StreamSettingsUi({
settingsKey="contentHint" settingsKey="contentHint"
onChange={option => setSettings(s => ({ ...s, contentHint: option }))} onChange={option => setSettings(s => ({ ...s, contentHint: option }))}
/> />
<div className={cl("hint-description")}>
<p> <Paragraph className={Margins.top8}>
Choosing "Prefer Clarity" will result in a significantly lower framerate in exchange Choosing "Prefer Clarity" will result in a significantly lower framerate in exchange for
for a much sharper and clearer image. a much sharper and clearer image.
</p> </Paragraph>
</div>
</div> </div>
{isWindows && ( {isWindows && (
<FormSwitch <FormSwitch
@@ -578,7 +592,7 @@ function AudioSourcePickerLinux({
if (!sources.ok && sources.isGlibCxxOutdated) { if (!sources.ok && sources.isGlibCxxOutdated) {
return ( return (
<Forms.FormText> <Paragraph>
Failed to retrieve Audio Sources because your C++ library is too old to run Failed to retrieve Audio Sources because your C++ library is too old to run
<a href="https://github.com/Vencord/venmic" target="_blank" rel="noreferrer"> <a href="https://github.com/Vencord/venmic" target="_blank" rel="noreferrer">
venmic venmic
@@ -592,13 +606,13 @@ function AudioSourcePickerLinux({
this guide this guide
</a>{" "} </a>{" "}
for possible solutions. for possible solutions.
</Forms.FormText> </Paragraph>
); );
} }
if (!hasPipewirePulse && !ignorePulseWarning) { if (!hasPipewirePulse && !ignorePulseWarning) {
return ( return (
<Text variant="text-sm/normal"> <Paragraph>
Could not find pipewire-pulse. See{" "} Could not find pipewire-pulse. See{" "}
<a <a
href="https://gist.github.com/the-spyke/2de98b22ff4f978ebf0650c90e82027e#install" href="https://gist.github.com/the-spyke/2de98b22ff4f978ebf0650c90e82027e#install"
@@ -611,7 +625,7 @@ function AudioSourcePickerLinux({
You can still continue, however, please{" "} You can still continue, however, please{" "}
<b>beware that you can only share audio of apps that are running under pipewire</b>.{" "} <b>beware that you can only share audio of apps that are running under pipewire</b>.{" "}
<a onClick={() => setIgnorePulseWarning(true)}>I know what I'm doing!</a> <a onClick={() => setIgnorePulseWarning(true)}>I know what I'm doing!</a>
</Text> </Paragraph>
); );
} }
@@ -631,7 +645,8 @@ function AudioSourcePickerLinux({
<> <>
<div className={cl("audio-sources")}> <div className={cl("audio-sources")}>
<section> <section>
<Forms.FormTitle>{loading ? "Loading Sources..." : "Audio Sources"}</Forms.FormTitle> <Heading tag="h5">{loading ? "Loading Sources..." : "Audio Sources"}</Heading>
<SimpleErrorBoundary>
<Select <Select
options={allSources.map(({ name, value }) => ({ options={allSources.map(({ name, value }) => ({
label: name, label: name,
@@ -644,10 +659,12 @@ function AudioSourcePickerLinux({
popoutPosition="top" popoutPosition="top"
closeOnSelect={false} closeOnSelect={false}
/> />
</SimpleErrorBoundary>
</section> </section>
{includeSources === "Entire System" && ( {includeSources === "Entire System" && (
<section> <section>
<Forms.FormTitle>Exclude Sources</Forms.FormTitle> <Heading tag="h5">Exclude Sources</Heading>
<SimpleErrorBoundary>
<Select <Select
options={allSources options={allSources
.filter(x => x.name !== "Entire System") .filter(x => x.name !== "Entire System")
@@ -662,19 +679,16 @@ function AudioSourcePickerLinux({
popoutPosition="top" popoutPosition="top"
closeOnSelect={false} closeOnSelect={false}
/> />
</SimpleErrorBoundary>
</section> </section>
)} )}
</div> </div>
<div className={cl("settings-buttons")}> <div className={cl("settings-buttons")}>
<Button <Button variant="secondary" onClick={refreshAudioSources} className={cl("settings-button")}>
color={Button.Colors.TRANSPARENT}
onClick={refreshAudioSources}
className={cl("settings-button")}
>
<RestartIcon className={cl("settings-button-icon")} /> <RestartIcon className={cl("settings-button-icon")} />
Refresh Audio Sources Refresh Audio Sources
</Button> </Button>
<Button color={Button.Colors.TRANSPARENT} onClick={openSettings} className={cl("settings-button")}> <Button variant="secondary" onClick={openSettings} className={cl("settings-button")}>
<CogWheel className={cl("settings-button-icon")} /> <CogWheel className={cl("settings-button-icon")} />
Open Audio Settings Open Audio Settings
</Button> </Button>
@@ -710,8 +724,10 @@ function ModalComponent({
return ( return (
<Modals.ModalRoot {...modalProps} size={ModalSize.MEDIUM}> <Modals.ModalRoot {...modalProps} size={ModalSize.MEDIUM}>
<Modals.ModalHeader className={cl("header")}> <Modals.ModalHeader className={cl("header")}>
<Forms.FormTitle tag="h2">ScreenShare</Forms.FormTitle> <BaseText size="lg" weight="semibold" tag="h3" style={{ flexGrow: 1 }}>
<Modals.ModalCloseButton onClick={close} /> Screen Share Picker
</BaseText>
<ModalCloseButton onClick={close} />
</Modals.ModalHeader> </Modals.ModalHeader>
<Modals.ModalContent className={cl("modal")}> <Modals.ModalContent className={cl("modal")}>
{!selected ? ( {!selected ? (
@@ -789,11 +805,11 @@ function ModalComponent({
</Button> </Button>
{selected && !skipPicker ? ( {selected && !skipPicker ? (
<Button color={Button.Colors.TRANSPARENT} onClick={() => setSelected(void 0)}> <Button variant="secondary" onClick={() => setSelected(void 0)}>
Back Back
</Button> </Button>
) : ( ) : (
<Button color={Button.Colors.TRANSPARENT} onClick={close}> <Button variant="secondary" onClick={close}>
Cancel Cancel
</Button> </Button>
)} )}
@@ -0,0 +1,46 @@
/*
* 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 { Card, ErrorBoundary, HeadingTertiary, Paragraph, TextButton } from "@vencord/types/components";
import { FluxDispatcher, InviteActions } from "@vencord/types/webpack/common";
import type { PropsWithChildren } from "react";
async function openSupportChannel() {
const code = "YVbdG2ZRG4";
try {
const { invite } = await InviteActions.resolveInvite(code, "Desktop Modal");
if (!invite) throw 0;
await FluxDispatcher.dispatch({
type: "INVITE_MODAL_OPEN",
invite,
code,
context: "APP"
});
} catch {
window.open(`https://discord.gg/${code}`, "_blank");
}
}
function Fallback() {
return (
<Card variant="danger">
<HeadingTertiary>Something went wrong.</HeadingTertiary>
<Paragraph>
Please make sure Vencord and Vesktop are fully up to date. You can get help in our{" "}
<TextButton variant="link" onClick={openSupportChannel}>
Support Channel
</TextButton>
</Paragraph>
</Card>
);
}
export function SimpleErrorBoundary({ children }: PropsWithChildren<{}>) {
return <ErrorBoundary fallback={Fallback}>{children}</ErrorBoundary>;
}
@@ -81,13 +81,13 @@
/* Option Radios */ /* Option Radios */
.vcd-screen-picker-option-radios { .vcd-screen-picker-option-radios {
display: flex; display: grid;
grid-template-columns: repeat(auto-fit, minmax(0, 1fr));
width: 100%; width: 100%;
border-radius: 3px; border-radius: 3px;
} }
.vcd-screen-picker-option-radio { .vcd-screen-picker-option-radio {
flex: 1 1 auto;
text-align: center; text-align: center;
background-color: var(--background-secondary); background-color: var(--background-secondary);
border: 1px solid var(--primary-800); border: 1px solid var(--primary-800);
@@ -4,6 +4,7 @@
* SPDX-License-Identifier: GPL-3.0-or-later * SPDX-License-Identifier: GPL-3.0-or-later
*/ */
import { BaseText, Button, Heading, Paragraph, TextButton } from "@vencord/types/components";
import { import {
Margins, Margins,
ModalCloseButton, ModalCloseButton,
@@ -14,7 +15,7 @@ import {
openModal, openModal,
useForceUpdater useForceUpdater
} from "@vencord/types/utils"; } from "@vencord/types/utils";
import { Button, Forms, Text, Toasts } from "@vencord/types/webpack/common"; import { Toasts } from "@vencord/types/webpack/common";
import { Settings } from "shared/settings"; import { Settings } from "shared/settings";
import { cl, SettingsComponent } from "./Settings"; import { cl, SettingsComponent } from "./Settings";
@@ -27,20 +28,20 @@ function openDeveloperOptionsModal(settings: Settings) {
openModal(props => ( openModal(props => (
<ModalRoot {...props} size={ModalSize.MEDIUM}> <ModalRoot {...props} size={ModalSize.MEDIUM}>
<ModalHeader> <ModalHeader>
<Text variant="heading-lg/semibold" style={{ flexGrow: 1 }}> <BaseText size="lg" weight="semibold" tag="h3" style={{ flexGrow: 1 }}>
Vesktop Developer Options Vesktop Developer Options
</Text> </BaseText>
<ModalCloseButton onClick={props.onClose} /> <ModalCloseButton onClick={props.onClose} />
</ModalHeader> </ModalHeader>
<ModalContent> <ModalContent>
<div style={{ padding: "1em 0" }}> <div style={{ padding: "1em 0" }}>
<Forms.FormTitle tag="h5">Vencord Location</Forms.FormTitle> <Heading tag="h5">Vencord Location</Heading>
<VencordLocationPicker settings={settings} /> <VencordLocationPicker settings={settings} />
<Forms.FormTitle tag="h5" className={Margins.top16}> <Heading tag="h5" className={Margins.top16}>
Debugging Debugging
</Forms.FormTitle> </Heading>
<div className={cl("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()}>
@@ -59,25 +60,24 @@ const VencordLocationPicker: SettingsComponent = ({ settings }) => {
return ( return (
<> <>
<Forms.FormText> <Paragraph>
Vencord files are loaded from{" "} Vencord files are loaded from{" "}
{vencordDir ? ( {vencordDir ? (
<a <TextButton
href="about:blank" variant="link"
onClick={e => { onClick={e => {
e.preventDefault(); e.preventDefault();
VesktopNative.fileManager.showItemInFolder(vencordDir!); VesktopNative.fileManager.showItemInFolder(vencordDir!);
}} }}
> >
{vencordDir} {vencordDir}
</a> </TextButton>
) : ( ) : (
"the default location" "the default location"
)} )}
</Forms.FormText> </Paragraph>
<div className={cl("button-grid")}> <div className={cl("button-grid")}>
<Button <Button
size={Button.Sizes.SMALL}
onClick={async () => { onClick={async () => {
const choice = await VesktopNative.fileManager.selectVencordDir(); const choice = await VesktopNative.fileManager.selectVencordDir();
switch (choice) { switch (choice) {
@@ -105,8 +105,7 @@ const VencordLocationPicker: SettingsComponent = ({ settings }) => {
Change Change
</Button> </Button>
<Button <Button
size={Button.Sizes.SMALL} variant="dangerPrimary"
color={Button.Colors.RED}
onClick={async () => { onClick={async () => {
await VesktopNative.fileManager.selectVencordDir(null); await VesktopNative.fileManager.selectVencordDir(null);
forceUpdate(); forceUpdate();
@@ -4,14 +4,14 @@
* SPDX-License-Identifier: GPL-3.0-or-later * SPDX-License-Identifier: GPL-3.0-or-later
*/ */
import { ErrorBoundary } from "@vencord/types/components";
import { Select } from "@vencord/types/webpack/common"; import { Select } from "@vencord/types/webpack/common";
import { SimpleErrorBoundary } from "../SimpleErrorBoundary";
import { SettingsComponent } from "./Settings"; import { SettingsComponent } from "./Settings";
export const DiscordBranchPicker: SettingsComponent = ({ settings }) => { export const DiscordBranchPicker: SettingsComponent = ({ settings }) => {
return ( return (
<ErrorBoundary noop> <SimpleErrorBoundary>
<Select <Select
placeholder="Stable" placeholder="Stable"
options={[ options={[
@@ -24,6 +24,6 @@ export const DiscordBranchPicker: SettingsComponent = ({ settings }) => {
isSelected={v => v === settings.discordBranch} isSelected={v => v === settings.discordBranch}
serialize={s => s} serialize={s => s}
/> />
</ErrorBoundary> </SimpleErrorBoundary>
); );
}; };
@@ -0,0 +1,27 @@
/*
* 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 { Button, Card, HeadingTertiary, Paragraph } from "@vencord/types/components";
import { useAwaiter } from "@vencord/types/utils";
import { cl } from "./Settings";
export function OutdatedVesktopWarning() {
const [isOutdated] = useAwaiter(VesktopNative.app.isOutdated);
if (!isOutdated) return null;
return (
<Card variant="warning" className={cl("updater-card")}>
<HeadingTertiary>Your Vesktop is outdated!</HeadingTertiary>
<Paragraph>Staying up to date is important for security and stability.</Paragraph>
<Button onClick={() => VesktopNative.app.openUpdater()} variant="secondary">
Open Updater
</Button>
</Card>
);
}
+6 -10
View File
@@ -7,8 +7,7 @@
import "./settings.css"; import "./settings.css";
import { classNameFactory } from "@vencord/types/api/Styles"; import { classNameFactory } from "@vencord/types/api/Styles";
import { ErrorBoundary } from "@vencord/types/components"; import { BaseText, Divider, ErrorBoundary } from "@vencord/types/components";
import { Forms, Text } from "@vencord/types/webpack/common";
import { ComponentType } from "react"; import { ComponentType } from "react";
import { Settings, useSettings } from "renderer/settings"; import { Settings, useSettings } from "renderer/settings";
import { isMac, isWindows } from "renderer/utils"; import { isMac, isWindows } from "renderer/utils";
@@ -17,7 +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 { Updater } from "./Updater"; import { OutdatedVesktopWarning } from "./OutdatedVesktopWarning";
import { UserAssetsButton } from "./UserAssets"; import { UserAssetsButton } from "./UserAssets";
import { VesktopSettingsSwitch } from "./VesktopSettingsSwitch"; import { VesktopSettingsSwitch } from "./VesktopSettingsSwitch";
import { WindowsTransparencyControls } from "./WindowsTransparencyControls"; import { WindowsTransparencyControls } from "./WindowsTransparencyControls";
@@ -149,9 +148,9 @@ function SettingsSections() {
const sections = Object.entries(SettingsOptions).map(([title, settings], i, arr) => ( const sections = Object.entries(SettingsOptions).map(([title, settings], i, arr) => (
<div key={title} className={cl("category")}> <div key={title} className={cl("category")}>
<Text variant="heading-lg/semibold" color="header-primary" className={cl("category-title")}> <BaseText size="lg" weight="semibold" tag="h3" className={cl("category-title")}>
{title} {title}
</Text> </BaseText>
<div className={cl("category-content")}> <div className={cl("category-content")}>
{settings.map((Setting, i) => { {settings.map((Setting, i) => {
@@ -173,7 +172,7 @@ function SettingsSections() {
})} })}
</div> </div>
{i < arr.length - 1 && <Forms.FormDivider className={cl("category-divider")} />} {i < arr.length - 1 && <Divider className={cl("category-divider")} />}
</div> </div>
)); ));
@@ -184,10 +183,7 @@ export default ErrorBoundary.wrap(
function SettingsUI() { function SettingsUI() {
return ( return (
<section> <section>
<Text variant="heading-xl/semibold" color="header-primary" className={cl("title")}> <OutdatedVesktopWarning />
Vesktop Settings
</Text>
<Updater />
<SettingsSections /> <SettingsSections />
</section> </section>
); );
@@ -1,31 +0,0 @@
/*
* 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>
);
}
@@ -6,7 +6,7 @@
import "./UserAssets.css"; import "./UserAssets.css";
import { FormSwitch } from "@vencord/types/components"; import { BaseText, Button, FormSwitch } from "@vencord/types/components";
import { import {
Margins, Margins,
ModalCloseButton, ModalCloseButton,
@@ -18,7 +18,7 @@ import {
wordsFromCamel, wordsFromCamel,
wordsToTitle wordsToTitle
} from "@vencord/types/utils"; } from "@vencord/types/utils";
import { Button, showToast, Text, useState } from "@vencord/types/webpack/common"; import { showToast, useState } from "@vencord/types/webpack/common";
import { UserAssetType } from "main/userAssets"; import { UserAssetType } from "main/userAssets";
import { useSettings } from "renderer/settings"; import { useSettings } from "renderer/settings";
@@ -34,9 +34,9 @@ function openAssetsModal() {
openModal(props => ( openModal(props => (
<ModalRoot {...props} size={ModalSize.MEDIUM}> <ModalRoot {...props} size={ModalSize.MEDIUM}>
<ModalHeader> <ModalHeader>
<Text variant="heading-lg/semibold" style={{ flexGrow: 1 }}> <BaseText size="lg" weight="semibold" tag="h3" style={{ flexGrow: 1 }}>
User Assets User Assets
</Text> </BaseText>
<ModalCloseButton onClick={props.onClose} /> <ModalCloseButton onClick={props.onClose} />
</ModalHeader> </ModalHeader>
@@ -73,9 +73,9 @@ function Asset({ asset }: { asset: UserAssetType }) {
return ( return (
<section> <section>
<Text tag="h3" variant="text-md/semibold"> <BaseText size="md" weight="medium" tag="h3">
{wordsToTitle(wordsFromCamel(asset))} {wordsToTitle(wordsFromCamel(asset))}
</Text> </BaseText>
<div className="vcd-user-assets-asset"> <div className="vcd-user-assets-asset">
<img <img
className="vcd-user-assets-image" className="vcd-user-assets-image"
@@ -86,7 +86,7 @@ function Asset({ asset }: { asset: UserAssetType }) {
<div className="vcd-user-assets-actions"> <div className="vcd-user-assets-actions">
<div className="vcd-user-assets-buttons"> <div className="vcd-user-assets-buttons">
<Button onClick={onChooseAsset()}>Customize</Button> <Button onClick={onChooseAsset()}>Customize</Button>
<Button color={Button.Colors.PRIMARY} onClick={onChooseAsset(null)}> <Button variant="secondary" onClick={onChooseAsset(null)}>
Reset to default Reset to default
</Button> </Button>
</div> </div>
@@ -5,7 +5,7 @@
*/ */
import { FormSwitch } from "@vencord/types/components"; import { FormSwitch } from "@vencord/types/components";
import { ComponentProps } from "react"; import type { ComponentProps } from "react";
import { cl } from "./Settings"; import { cl } from "./Settings";
@@ -4,23 +4,24 @@
* SPDX-License-Identifier: GPL-3.0-or-later * SPDX-License-Identifier: GPL-3.0-or-later
*/ */
import { ErrorBoundary } from "@vencord/types/components"; import { Heading, Paragraph } from "@vencord/types/components";
import { Margins } from "@vencord/types/utils"; import { Margins } from "@vencord/types/utils";
import { Forms, Select } from "@vencord/types/webpack/common"; import { Select } from "@vencord/types/webpack/common";
import { SimpleErrorBoundary } from "../SimpleErrorBoundary";
import { SettingsComponent } from "./Settings"; import { SettingsComponent } from "./Settings";
export const WindowsTransparencyControls: SettingsComponent = ({ settings }) => { export const WindowsTransparencyControls: SettingsComponent = ({ settings }) => {
if (!VesktopNative.app.supportsWindowsTransparency()) return null; if (!VesktopNative.app.supportsWindowsTransparency()) return null;
return ( return (
<ErrorBoundary noop>
<div> <div>
<Forms.FormTitle className={Margins.bottom8}>Transparency Options</Forms.FormTitle> <Heading tag="h5">Transparency Options</Heading>
<Forms.FormText className={Margins.bottom8}> <Paragraph className={Margins.bottom8}>
Requires a full restart. You will need a theme that supports transparency for this to work. Requires a full restart. You will need a theme that supports transparency for this to work.
</Forms.FormText> </Paragraph>
<SimpleErrorBoundary>
<Select <Select
placeholder="None" placeholder="None"
options={[ options={[
@@ -44,7 +45,7 @@ export const WindowsTransparencyControls: SettingsComponent = ({ settings }) =>
isSelected={v => v === settings.transparencyOption} isSelected={v => v === settings.transparencyOption}
serialize={s => s} serialize={s => s}
/> />
</SimpleErrorBoundary>
</div> </div>
</ErrorBoundary>
); );
}; };
@@ -38,10 +38,4 @@
margin-bottom: 1em; margin-bottom: 1em;
display: grid; display: grid;
gap: 0.5em; 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);
} }
+1
View File
@@ -23,6 +23,7 @@ import type SettingsPlugin from "@vencord/types/plugins/_core/settings";
VesktopLogger.log("read if cute :3"); VesktopLogger.log("read if cute :3");
VesktopLogger.log("Vesktop v" + VesktopNative.app.getVersion()); VesktopLogger.log("Vesktop v" + VesktopNative.app.getVersion());
// TODO
const customSettingsSections = (Vencord.Plugins.plugins.Settings as any as typeof SettingsPlugin).customSections; const customSettingsSections = (Vencord.Plugins.plugins.Settings as any as typeof SettingsPlugin).customSections;
customSettingsSections.push(() => ({ customSettingsSections.push(() => ({