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

View File

@@ -38,7 +38,7 @@
"@stylistic/eslint-plugin": "^5.6.1",
"@types/node": "^24.10.1",
"@types/react": "19.2.1",
"@vencord/types": "^1.13.2",
"@vencord/types": "^1.13.7",
"dotenv": "^17.2.3",
"electron": "^39.2.3",
"electron-builder": "^26.0.12",

31
pnpm-lock.yaml generated
View File

@@ -36,8 +36,8 @@ importers:
specifier: 19.2.1
version: 19.2.1
'@vencord/types':
specifier: ^1.13.2
version: 1.13.2(@types/react-dom@18.3.1)(@types/react@19.2.1)
specifier: ^1.13.7
version: 1.13.7(@types/react-dom@18.3.1)(@types/react@19.2.1)
dotenv:
specifier: ^17.2.3
version: 17.2.3
@@ -629,6 +629,9 @@ packages:
'@types/node@22.19.1':
resolution: {integrity: sha512-LCCV0HdSZZZb34qifBsyWlUmok6W7ouER+oQIGBScS8EsZsQbrtFTUrDX4hOl+CS6p7cnNC4td+qrSVGSCTUfQ==}
'@types/node@22.19.2':
resolution: {integrity: sha512-LPM2G3Syo1GLzXLGJAKdqoU35XvrWzGJ21/7sgZTUpbkBaOasTj8tjwn6w+hCkqaa1TfJ/w67rJSwYItlJ2mYw==}
'@types/node@24.10.1':
resolution: {integrity: sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==}
@@ -714,8 +717,8 @@ packages:
peerDependencies:
'@types/react': ^19.0.10
'@vencord/types@1.13.2':
resolution: {integrity: sha512-5RifSCC1X4xzoNaDTMANRLVZreEsHWWsGs6iBooqnBkLeigfP7FqAQfn6onogk/2+YQKNMTQT8N+MD+HGes5EA==}
'@vencord/types@1.13.7':
resolution: {integrity: sha512-MThrBwMEJ0CvKMrVgJC4cvJuDwwFXEhgSOJnmDZxmJeKOByVWLo7xxjn1suHkE1/euw9J5lGTfz1QTiYVXbLqA==}
peerDependencies:
'@types/react': 18.3.1
'@types/react-dom': 18.3.1
@@ -2983,8 +2986,8 @@ packages:
peerDependencies:
typescript: '>=4.8.4'
ts-pattern@5.8.0:
resolution: {integrity: sha512-kIjN2qmWiHnhgr5DAkAafF9fwb0T5OhMVSWrm8XEdTFnX6+wfXwYOFjeF86UZ54vduqiR7BfqScFmXSzSaH8oA==}
ts-pattern@5.9.0:
resolution: {integrity: sha512-6s5V71mX8qBUmlgbrfL33xDUwO0fq48rxAu2LBE11WBeGdpCPOsXksQbZJHvHwhrd3QjUusd3mAOM5Gg0mFBLg==}
tsconfig-paths@3.15.0:
resolution: {integrity: sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==}
@@ -3236,7 +3239,7 @@ snapshots:
make-fetch-happen: 10.2.1
nopt: 6.0.0
proc-log: 2.0.1
semver: 7.7.3
semver: 7.7.2
tar: 6.2.1
which: 2.0.2
transitivePeerDependencies:
@@ -3574,7 +3577,7 @@ snapshots:
'@npmcli/fs@2.1.2':
dependencies:
'@gar/promisify': 1.1.3
semver: 7.7.3
semver: 7.7.2
'@npmcli/move-file@2.0.1':
dependencies:
@@ -3641,6 +3644,10 @@ snapshots:
dependencies:
undici-types: 6.21.0
'@types/node@22.19.2':
dependencies:
undici-types: 6.21.0
'@types/node@24.10.1':
dependencies:
undici-types: 7.16.0
@@ -3770,16 +3777,16 @@ snapshots:
moment: 2.30.1
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:
'@types/lodash': 4.17.15
'@types/node': 22.19.1
'@types/node': 22.19.2
'@types/react': 19.2.1
'@types/react-dom': 18.3.1
'@vencord/discord-types': 1.0.0(@types/react@19.2.1)
highlight.js: 11.11.1
moment: 2.30.1
ts-pattern: 5.8.0
ts-pattern: 5.9.0
type-fest: 4.41.0
'@vencord/venmic@6.1.0':
@@ -6597,7 +6604,7 @@ snapshots:
dependencies:
typescript: 5.9.3
ts-pattern@5.8.0: {}
ts-pattern@5.9.0: {}
tsconfig-paths@3.15.0:
dependencies:

View File

@@ -7,10 +7,31 @@
import "./screenSharePicker.css";
import { classNameFactory } from "@vencord/types/api/Styles";
import { CogWheel, FormSwitch, RestartIcon } from "@vencord/types/components";
import { closeModal, Logger, Modals, ModalSize, openModal, useAwaiter, useForceUpdater } from "@vencord/types/utils";
import {
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 { 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 type { Dispatch, SetStateAction } from "react";
import { MediaEngineStore } from "renderer/common";
@@ -18,6 +39,8 @@ import { addPatch } from "renderer/patches/shared";
import { State, useSettings, useVesktopState } from "renderer/settings";
import { isLinux, isWindows } from "renderer/utils";
import { SimpleErrorBoundary } from "./SimpleErrorBoundary";
const StreamResolutions = ["480", "720", "1080", "1440", "2160"] as const;
const StreamFps = ["15", "30", "60"] as const;
@@ -145,6 +168,9 @@ export function openScreenSharePicker(screens: Source[], skipPicker: boolean) {
onCloseRequest() {
closeModal(key);
reject("Aborted");
},
onCloseCallback() {
reject("Aborted");
}
}
);
@@ -165,9 +191,7 @@ function ScreenPicker({ screens, chooseScreen }: { screens: Source[]; chooseScre
/>
<img src={url} alt="" />
<Text className={cl("screen-name")} variant="text-sm/normal">
{name}
</Text>
<Paragraph className={cl("screen-name")}>{name}</Paragraph>
</label>
))}
</div>
@@ -188,19 +212,16 @@ function AudioSettingsModal({
return (
<Modals.ModalRoot {...modalProps} size={ModalSize.MEDIUM}>
<Modals.ModalHeader className={cl("header")}>
<Forms.FormTitle tag="h2">Venmic Settings</Forms.FormTitle>
<Modals.ModalCloseButton onClick={close} className={cl("header-close-button")} />
<BaseText size="lg" weight="semibold" tag="h3" style={{ flexGrow: 1 }}>
Audio Settings
</BaseText>
<ModalCloseButton onClick={close} />
</Modals.ModalHeader>
<Modals.ModalContent className={cl("modal", "venmic-settings")}>
<FormSwitch
title="Microphone Workaround"
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.
</>
}
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."
hideBorder
onChange={v => (Settings.audio = { ...Settings.audio, workaround: v })}
value={Settings.audio?.workaround ?? false}
@@ -208,10 +229,7 @@ function AudioSettingsModal({
<FormSwitch
title="Only Speakers"
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
onChange={v => (Settings.audio = { ...Settings.audio, onlySpeakers: v })}
@@ -231,7 +249,7 @@ function AudioSettingsModal({
/>
<FormSwitch
title="Ignore Inputs"
description={<>Exclude nodes that are intended to capture audio.</>}
description="Exclude nodes that are intended to capture audio."
hideBorder
onChange={v => (Settings.audio = { ...Settings.audio, ignoreInputMedia: v })}
value={Settings.audio?.ignoreInputMedia ?? true}
@@ -239,10 +257,7 @@ function AudioSettingsModal({
<FormSwitch
title="Ignore Virtual"
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
onChange={v => (Settings.audio = { ...Settings.audio, ignoreVirtual: v })}
@@ -250,7 +265,7 @@ function AudioSettingsModal({
/>
<FormSwitch
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
onChange={v =>
(Settings.audio = {
@@ -263,7 +278,7 @@ function AudioSettingsModal({
/>
<FormSwitch
title="Granular Selection"
description={<>Allow to select applications more granularly.</>}
description="Allow to select applications more granularly."
hideBorder
onChange={value => {
Settings.audio = { ...Settings.audio, granularSelect: value };
@@ -289,7 +304,7 @@ function AudioSettingsModal({
/>
</Modals.ModalContent>
<Modals.ModalFooter className={cl("footer")}>
<Button color={Button.Colors.TRANSPARENT} onClick={close}>
<Button variant="secondary" onClick={close}>
Back
</Button>
</Modals.ModalFooter>
@@ -310,7 +325,7 @@ function OptionRadio<Settings extends object, Key extends keyof Settings>(props:
<div className={cl("option-radios")}>
{(options as string[]).map((option, idx) => (
<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
className={cl("option-input")}
type="radio"
@@ -361,18 +376,18 @@ function StreamSettingsUi({
return (
<div>
<Forms.FormTitle>What you're streaming</Forms.FormTitle>
<HeadingTertiary className={Margins.bottom8}>What you're streaming</HeadingTertiary>
<Card className={cl("card", "preview")}>
<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>
<Forms.FormTitle>Stream Settings</Forms.FormTitle>
<HeadingTertiary className={Margins.bottom8}>Stream Settings</HeadingTertiary>
<Card className={cl("card")}>
<div className={cl("quality")}>
<section className={cl("quality-section")}>
<Forms.FormTitle>Resolution</Forms.FormTitle>
<Heading tag="h5">Resolution</Heading>
<OptionRadio
options={StreamResolutions}
settings={qualitySettings}
@@ -382,7 +397,7 @@ function StreamSettingsUi({
</section>
<section className={cl("quality-section")}>
<Forms.FormTitle>Frame Rate</Forms.FormTitle>
<Heading tag="h5">Frame Rate</Heading>
<OptionRadio
options={StreamFps}
settings={qualitySettings}
@@ -393,7 +408,7 @@ function StreamSettingsUi({
</div>
<div className={cl("quality")}>
<section className={cl("quality-section")}>
<Forms.FormTitle>Content Type</Forms.FormTitle>
<Heading tag="h5">Content Type</Heading>
<div>
<OptionRadio
options={["motion", "detail"]}
@@ -402,12 +417,11 @@ function StreamSettingsUi({
settingsKey="contentHint"
onChange={option => setSettings(s => ({ ...s, contentHint: option }))}
/>
<div className={cl("hint-description")}>
<p>
Choosing "Prefer Clarity" will result in a significantly lower framerate in exchange
for a much sharper and clearer image.
</p>
</div>
<Paragraph className={Margins.top8}>
Choosing "Prefer Clarity" will result in a significantly lower framerate in exchange for
a much sharper and clearer image.
</Paragraph>
</div>
{isWindows && (
<FormSwitch
@@ -578,7 +592,7 @@ function AudioSourcePickerLinux({
if (!sources.ok && sources.isGlibCxxOutdated) {
return (
<Forms.FormText>
<Paragraph>
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">
venmic
@@ -592,13 +606,13 @@ function AudioSourcePickerLinux({
this guide
</a>{" "}
for possible solutions.
</Forms.FormText>
</Paragraph>
);
}
if (!hasPipewirePulse && !ignorePulseWarning) {
return (
<Text variant="text-sm/normal">
<Paragraph>
Could not find pipewire-pulse. See{" "}
<a
href="https://gist.github.com/the-spyke/2de98b22ff4f978ebf0650c90e82027e#install"
@@ -611,7 +625,7 @@ function AudioSourcePickerLinux({
You can still continue, however, please{" "}
<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>
</Text>
</Paragraph>
);
}
@@ -631,50 +645,50 @@ function AudioSourcePickerLinux({
<>
<div className={cl("audio-sources")}>
<section>
<Forms.FormTitle>{loading ? "Loading Sources..." : "Audio Sources"}</Forms.FormTitle>
<Select
options={allSources.map(({ name, value }) => ({
label: name,
value: value,
default: name === "None"
}))}
isSelected={isItemSelected(includeSources)}
select={updateItems(setIncludeSources, includeSources)}
serialize={String}
popoutPosition="top"
closeOnSelect={false}
/>
</section>
{includeSources === "Entire System" && (
<section>
<Forms.FormTitle>Exclude Sources</Forms.FormTitle>
<Heading tag="h5">{loading ? "Loading Sources..." : "Audio Sources"}</Heading>
<SimpleErrorBoundary>
<Select
options={allSources
.filter(x => x.name !== "Entire System")
.map(({ name, value }) => ({
label: name,
value: value,
default: name === "None"
}))}
isSelected={isItemSelected(excludeSources)}
select={updateItems(setExcludeSources, excludeSources)}
options={allSources.map(({ name, value }) => ({
label: name,
value: value,
default: name === "None"
}))}
isSelected={isItemSelected(includeSources)}
select={updateItems(setIncludeSources, includeSources)}
serialize={String}
popoutPosition="top"
closeOnSelect={false}
/>
</SimpleErrorBoundary>
</section>
{includeSources === "Entire System" && (
<section>
<Heading tag="h5">Exclude Sources</Heading>
<SimpleErrorBoundary>
<Select
options={allSources
.filter(x => x.name !== "Entire System")
.map(({ name, value }) => ({
label: name,
value: value,
default: name === "None"
}))}
isSelected={isItemSelected(excludeSources)}
select={updateItems(setExcludeSources, excludeSources)}
serialize={String}
popoutPosition="top"
closeOnSelect={false}
/>
</SimpleErrorBoundary>
</section>
)}
</div>
<div className={cl("settings-buttons")}>
<Button
color={Button.Colors.TRANSPARENT}
onClick={refreshAudioSources}
className={cl("settings-button")}
>
<Button variant="secondary" onClick={refreshAudioSources} className={cl("settings-button")}>
<RestartIcon className={cl("settings-button-icon")} />
Refresh Audio Sources
</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")} />
Open Audio Settings
</Button>
@@ -710,8 +724,10 @@ function ModalComponent({
return (
<Modals.ModalRoot {...modalProps} size={ModalSize.MEDIUM}>
<Modals.ModalHeader className={cl("header")}>
<Forms.FormTitle tag="h2">ScreenShare</Forms.FormTitle>
<Modals.ModalCloseButton onClick={close} />
<BaseText size="lg" weight="semibold" tag="h3" style={{ flexGrow: 1 }}>
Screen Share Picker
</BaseText>
<ModalCloseButton onClick={close} />
</Modals.ModalHeader>
<Modals.ModalContent className={cl("modal")}>
{!selected ? (
@@ -789,11 +805,11 @@ function ModalComponent({
</Button>
{selected && !skipPicker ? (
<Button color={Button.Colors.TRANSPARENT} onClick={() => setSelected(void 0)}>
<Button variant="secondary" onClick={() => setSelected(void 0)}>
Back
</Button>
) : (
<Button color={Button.Colors.TRANSPARENT} onClick={close}>
<Button variant="secondary" onClick={close}>
Cancel
</Button>
)}

View File

@@ -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>;
}

View File

@@ -81,13 +81,13 @@
/* Option Radios */
.vcd-screen-picker-option-radios {
display: flex;
display: grid;
grid-template-columns: repeat(auto-fit, minmax(0, 1fr));
width: 100%;
border-radius: 3px;
}
.vcd-screen-picker-option-radio {
flex: 1 1 auto;
text-align: center;
background-color: var(--background-secondary);
border: 1px solid var(--primary-800);

View File

@@ -4,6 +4,7 @@
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import { BaseText, Button, Heading, Paragraph, TextButton } from "@vencord/types/components";
import {
Margins,
ModalCloseButton,
@@ -14,7 +15,7 @@ import {
openModal,
useForceUpdater
} 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 { cl, SettingsComponent } from "./Settings";
@@ -27,20 +28,20 @@ function openDeveloperOptionsModal(settings: Settings) {
openModal(props => (
<ModalRoot {...props} size={ModalSize.MEDIUM}>
<ModalHeader>
<Text variant="heading-lg/semibold" style={{ flexGrow: 1 }}>
<BaseText size="lg" weight="semibold" tag="h3" style={{ flexGrow: 1 }}>
Vesktop Developer Options
</Text>
</BaseText>
<ModalCloseButton onClick={props.onClose} />
</ModalHeader>
<ModalContent>
<div style={{ padding: "1em 0" }}>
<Forms.FormTitle tag="h5">Vencord Location</Forms.FormTitle>
<Heading tag="h5">Vencord Location</Heading>
<VencordLocationPicker settings={settings} />
<Forms.FormTitle tag="h5" className={Margins.top16}>
<Heading tag="h5" className={Margins.top16}>
Debugging
</Forms.FormTitle>
</Heading>
<div className={cl("button-grid")}>
<Button onClick={() => VesktopNative.debug.launchGpu()}>Open chrome://gpu</Button>
<Button onClick={() => VesktopNative.debug.launchWebrtcInternals()}>
@@ -59,25 +60,24 @@ const VencordLocationPicker: SettingsComponent = ({ settings }) => {
return (
<>
<Forms.FormText>
<Paragraph>
Vencord files are loaded from{" "}
{vencordDir ? (
<a
href="about:blank"
<TextButton
variant="link"
onClick={e => {
e.preventDefault();
VesktopNative.fileManager.showItemInFolder(vencordDir!);
}}
>
{vencordDir}
</a>
</TextButton>
) : (
"the default location"
)}
</Forms.FormText>
</Paragraph>
<div className={cl("button-grid")}>
<Button
size={Button.Sizes.SMALL}
onClick={async () => {
const choice = await VesktopNative.fileManager.selectVencordDir();
switch (choice) {
@@ -105,8 +105,7 @@ const VencordLocationPicker: SettingsComponent = ({ settings }) => {
Change
</Button>
<Button
size={Button.Sizes.SMALL}
color={Button.Colors.RED}
variant="dangerPrimary"
onClick={async () => {
await VesktopNative.fileManager.selectVencordDir(null);
forceUpdate();

View File

@@ -4,14 +4,14 @@
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import { ErrorBoundary } from "@vencord/types/components";
import { Select } from "@vencord/types/webpack/common";
import { SimpleErrorBoundary } from "../SimpleErrorBoundary";
import { SettingsComponent } from "./Settings";
export const DiscordBranchPicker: SettingsComponent = ({ settings }) => {
return (
<ErrorBoundary noop>
<SimpleErrorBoundary>
<Select
placeholder="Stable"
options={[
@@ -24,6 +24,6 @@ export const DiscordBranchPicker: SettingsComponent = ({ settings }) => {
isSelected={v => v === settings.discordBranch}
serialize={s => s}
/>
</ErrorBoundary>
</SimpleErrorBoundary>
);
};

View File

@@ -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>
);
}

View File

@@ -7,8 +7,7 @@
import "./settings.css";
import { classNameFactory } from "@vencord/types/api/Styles";
import { ErrorBoundary } from "@vencord/types/components";
import { Forms, Text } from "@vencord/types/webpack/common";
import { BaseText, Divider, ErrorBoundary } from "@vencord/types/components";
import { ComponentType } from "react";
import { Settings, useSettings } from "renderer/settings";
import { isMac, isWindows } from "renderer/utils";
@@ -17,7 +16,7 @@ import { AutoStartToggle } from "./AutoStartToggle";
import { DeveloperOptionsButton } from "./DeveloperOptions";
import { DiscordBranchPicker } from "./DiscordBranchPicker";
import { NotificationBadgeToggle } from "./NotificationBadgeToggle";
import { Updater } from "./Updater";
import { OutdatedVesktopWarning } from "./OutdatedVesktopWarning";
import { UserAssetsButton } from "./UserAssets";
import { VesktopSettingsSwitch } from "./VesktopSettingsSwitch";
import { WindowsTransparencyControls } from "./WindowsTransparencyControls";
@@ -149,9 +148,9 @@ function SettingsSections() {
const sections = Object.entries(SettingsOptions).map(([title, settings], i, arr) => (
<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}
</Text>
</BaseText>
<div className={cl("category-content")}>
{settings.map((Setting, i) => {
@@ -173,7 +172,7 @@ function SettingsSections() {
})}
</div>
{i < arr.length - 1 && <Forms.FormDivider className={cl("category-divider")} />}
{i < arr.length - 1 && <Divider className={cl("category-divider")} />}
</div>
));
@@ -184,10 +183,7 @@ export default ErrorBoundary.wrap(
function SettingsUI() {
return (
<section>
<Text variant="heading-xl/semibold" color="header-primary" className={cl("title")}>
Vesktop Settings
</Text>
<Updater />
<OutdatedVesktopWarning />
<SettingsSections />
</section>
);

View File

@@ -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>
);
}

View File

@@ -6,7 +6,7 @@
import "./UserAssets.css";
import { FormSwitch } from "@vencord/types/components";
import { BaseText, Button, FormSwitch } from "@vencord/types/components";
import {
Margins,
ModalCloseButton,
@@ -18,7 +18,7 @@ import {
wordsFromCamel,
wordsToTitle
} 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 { useSettings } from "renderer/settings";
@@ -34,9 +34,9 @@ function openAssetsModal() {
openModal(props => (
<ModalRoot {...props} size={ModalSize.MEDIUM}>
<ModalHeader>
<Text variant="heading-lg/semibold" style={{ flexGrow: 1 }}>
<BaseText size="lg" weight="semibold" tag="h3" style={{ flexGrow: 1 }}>
User Assets
</Text>
</BaseText>
<ModalCloseButton onClick={props.onClose} />
</ModalHeader>
@@ -73,9 +73,9 @@ function Asset({ asset }: { asset: UserAssetType }) {
return (
<section>
<Text tag="h3" variant="text-md/semibold">
<BaseText size="md" weight="medium" tag="h3">
{wordsToTitle(wordsFromCamel(asset))}
</Text>
</BaseText>
<div className="vcd-user-assets-asset">
<img
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-buttons">
<Button onClick={onChooseAsset()}>Customize</Button>
<Button color={Button.Colors.PRIMARY} onClick={onChooseAsset(null)}>
<Button variant="secondary" onClick={onChooseAsset(null)}>
Reset to default
</Button>
</div>

View File

@@ -5,7 +5,7 @@
*/
import { FormSwitch } from "@vencord/types/components";
import { ComponentProps } from "react";
import type { ComponentProps } from "react";
import { cl } from "./Settings";

View File

@@ -4,23 +4,24 @@
* 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 { Forms, Select } from "@vencord/types/webpack/common";
import { Select } from "@vencord/types/webpack/common";
import { SimpleErrorBoundary } from "../SimpleErrorBoundary";
import { SettingsComponent } from "./Settings";
export const WindowsTransparencyControls: SettingsComponent = ({ settings }) => {
if (!VesktopNative.app.supportsWindowsTransparency()) return null;
return (
<ErrorBoundary noop>
<div>
<Forms.FormTitle className={Margins.bottom8}>Transparency Options</Forms.FormTitle>
<Forms.FormText className={Margins.bottom8}>
Requires a full restart. You will need a theme that supports transparency for this to work.
</Forms.FormText>
<div>
<Heading tag="h5">Transparency Options</Heading>
<Paragraph className={Margins.bottom8}>
Requires a full restart. You will need a theme that supports transparency for this to work.
</Paragraph>
<SimpleErrorBoundary>
<Select
placeholder="None"
options={[
@@ -44,7 +45,7 @@ export const WindowsTransparencyControls: SettingsComponent = ({ settings }) =>
isSelected={v => v === settings.transparencyOption}
serialize={s => s}
/>
</div>
</ErrorBoundary>
</SimpleErrorBoundary>
</div>
);
};

View File

@@ -38,10 +38,4 @@
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);
}

View File

@@ -23,6 +23,7 @@ import type SettingsPlugin from "@vencord/types/plugins/_core/settings";
VesktopLogger.log("read if cute :3");
VesktopLogger.log("Vesktop v" + VesktopNative.app.getVersion());
// TODO
const customSettingsSections = (Vencord.Plugins.plugins.Settings as any as typeof SettingsPlugin).customSections;
customSettingsSections.push(() => ({