fix broken Settings ui

This commit is contained in:
Vendicated 2025-10-09 21:01:48 +02:00
parent db077f307b
commit ae65883335
No known key found for this signature in database
GPG key ID: D66986BAF75ECF18
17 changed files with 151 additions and 127 deletions

View file

@ -25,7 +25,7 @@ import type { ComponentType, PropsWithChildren } from "react";
export function SettingsTab({ title, children }: PropsWithChildren<{ title: string; }>) {
return (
<Forms.FormSection>
<section>
<Text
variant="heading-lg/semibold"
tag="h2"
@ -35,7 +35,7 @@ export function SettingsTab({ title, children }: PropsWithChildren<{ title: stri
</Text>
{children}
</Forms.FormSection>
</section>
);
}

View file

@ -105,14 +105,15 @@ export default function PluginModal({ plugin, onRestartNeeded, onClose, transiti
const Component = OptionComponentMap[setting.type];
return (
<Component
id={key}
key={key}
option={setting}
onChange={debounce(onChange)}
pluginSettings={pluginSettings}
definedSettings={plugin.settings}
/>
<ErrorBoundary noop key={key}>
<Component
id={key}
option={setting}
onChange={debounce(onChange)}
pluginSettings={pluginSettings}
definedSettings={plugin.settings}
/>
</ErrorBoundary>
);
});
@ -153,7 +154,7 @@ export default function PluginModal({ plugin, onRestartNeeded, onClose, transiti
</ModalHeader>
<ModalContent className={Margins.bottom16}>
<Forms.FormSection>
<section>
<Flex className={cl("info")}>
<Forms.FormText className={cl("description")}>{plugin.description}</Forms.FormText>
{!pluginMeta.userPlugin && (
@ -193,22 +194,22 @@ export default function PluginModal({ plugin, onRestartNeeded, onClose, transiti
)}
/>
</div>
</Forms.FormSection>
</section>
{!!plugin.settingsAboutComponent && (
<div className={Margins.top16}>
<Forms.FormSection>
<section>
<ErrorBoundary message="An error occurred while rendering this plugin's custom Info Component">
<plugin.settingsAboutComponent />
</ErrorBoundary>
</Forms.FormSection>
</section>
</div>
)}
<Forms.FormSection>
<section>
<Text variant="heading-lg/semibold" className={classes(Margins.top16, Margins.bottom8)}>Settings</Text>
{renderSettings()}
</Forms.FormSection>
</section>
</ModalContent>
</ModalRoot>
);

View file

@ -21,6 +21,7 @@ import "./styles.css";
import * as DataStore from "@api/DataStore";
import { useSettings } from "@api/Settings";
import { classNameFactory } from "@api/Styles";
import ErrorBoundary from "@components/ErrorBoundary";
import { SettingsTab, wrapTab } from "@components/settings/tabs/BaseTab";
import { ChangeList } from "@utils/ChangeList";
import { Logger } from "@utils/Logger";
@ -249,20 +250,24 @@ function PluginSettings() {
</Forms.FormTitle>
<div className={classes(Margins.bottom20, cl("filter-controls"))}>
<TextInput autoFocus value={searchValue.value} placeholder="Search for a plugin..." onChange={onSearch} />
<ErrorBoundary noop>
<TextInput autoFocus value={searchValue.value} placeholder="Search for a plugin..." onChange={onSearch} />
</ErrorBoundary>
<div className={InputStyles.inputWrapper}>
<Select
options={[
{ label: "Show All", value: SearchStatus.ALL, default: true },
{ label: "Show Enabled", value: SearchStatus.ENABLED },
{ label: "Show Disabled", value: SearchStatus.DISABLED },
{ label: "Show New", value: SearchStatus.NEW }
]}
serialize={String}
select={onStatusChange}
isSelected={v => v === searchValue.status}
closeOnSelect={true}
/>
<ErrorBoundary noop>
<Select
options={[
{ label: "Show All", value: SearchStatus.ALL, default: true },
{ label: "Show Enabled", value: SearchStatus.ENABLED },
{ label: "Show Disabled", value: SearchStatus.DISABLED },
{ label: "Show New", value: SearchStatus.NEW }
]}
serialize={String}
select={onStatusChange}
isSelected={v => v === searchValue.status}
closeOnSelect={true}
/>
</ErrorBoundary>
</div>
</div>

View file

@ -70,7 +70,9 @@ function SettingsSyncSection() {
const sectionEnabled = cloud.authenticated && cloud.settingsSync;
return (
<Forms.FormSection title="Settings Sync" className={Margins.top16}>
<section className={Margins.top16}>
<Forms.FormTitle tag="h5">Settings Sync</Forms.FormTitle>
<Forms.FormText variant="text-md/normal" className={Margins.bottom20}>
Synchronize your settings to the cloud. This allows easy synchronization across multiple devices with
minimal effort.
@ -113,7 +115,7 @@ function SettingsSyncSection() {
Delete Cloud Settings
</Button>
</div>
</Forms.FormSection>
</section>
);
}
@ -122,7 +124,9 @@ function CloudTab() {
return (
<SettingsTab title="Vencord Cloud">
<Forms.FormSection title="Cloud Settings" className={Margins.top16}>
<section className={Margins.top16}>
<Forms.FormTitle tag="h5">Cloud Settings</Forms.FormTitle>
<Forms.FormText variant="text-md/normal" className={Margins.bottom20}>
Vencord comes with a cloud integration that adds goodies like settings sync across devices.
It <Link href="https://vencord.dev/cloud/privacy">respects your privacy</Link>, and
@ -186,7 +190,7 @@ function CloudTab() {
</Grid>
<Forms.FormDivider className={Margins.top16} />
</Forms.FormSection >
</section >
<SettingsSyncSection />
</SettingsTab>
);

View file

@ -99,7 +99,8 @@ export function LocalThemesTab() {
<Forms.FormText>Make sure all your assets are hosted on GitHub, GitLab, Codeberg, Imgur, Discord or Google Fonts.</Forms.FormText>
</Card>
<Forms.FormSection title="Local Themes">
<section>
<Forms.FormTitle tag="h5">Local Themes</Forms.FormTitle>
<QuickActionCard>
<>
{IS_WEB ?
@ -164,7 +165,7 @@ export function LocalThemesTab() {
/>
))}
</div>
</Forms.FormSection>
</section>
</>
);
}

View file

@ -40,7 +40,8 @@ export function OnlineThemesTab() {
<Forms.FormText>Make sure to use direct links to files (raw or github.io)!</Forms.FormText>
</Card>
<Forms.FormSection title="Online Themes" tag="h5">
<section>
<Forms.FormTitle tag="h5">Online Themes</Forms.FormTitle>
<TextArea
value={themeText}
onChange={setThemeText}
@ -50,7 +51,7 @@ export function OnlineThemesTab() {
onBlur={onBlur}
rows={10}
/>
</Forms.FormSection>
</section>
</>
);
}

View file

@ -5,6 +5,7 @@
*/
import { useSettings } from "@api/Settings";
import ErrorBoundary from "@components/ErrorBoundary";
import { Margins } from "@utils/margins";
import { identity } from "@utils/misc";
import { Forms, Select } from "@webpack/common";
@ -15,66 +16,69 @@ export function VibrancySettings() {
return (
<>
<Forms.FormTitle tag="h5">Window vibrancy style (requires restart)</Forms.FormTitle>
<Select
className={Margins.bottom20}
placeholder="Window vibrancy style"
options={[
// Sorted from most opaque to most transparent
{
label: "No vibrancy", value: undefined
},
{
label: "Under Page (window tinting)",
value: "under-page"
},
{
label: "Content",
value: "content"
},
{
label: "Window",
value: "window"
},
{
label: "Selection",
value: "selection"
},
{
label: "Titlebar",
value: "titlebar"
},
{
label: "Header",
value: "header"
},
{
label: "Sidebar",
value: "sidebar"
},
{
label: "Tooltip",
value: "tooltip"
},
{
label: "Menu",
value: "menu"
},
{
label: "Popover",
value: "popover"
},
{
label: "Fullscreen UI (transparent but slightly muted)",
value: "fullscreen-ui"
},
{
label: "HUD (Most transparent)",
value: "hud"
},
]}
select={v => settings.macosVibrancyStyle = v}
isSelected={v => settings.macosVibrancyStyle === v}
serialize={identity} />
<ErrorBoundary noop>
<Select
className={Margins.bottom20}
placeholder="Window vibrancy style"
options={[
// Sorted from most opaque to most transparent
{
label: "No vibrancy", value: undefined
},
{
label: "Under Page (window tinting)",
value: "under-page"
},
{
label: "Content",
value: "content"
},
{
label: "Window",
value: "window"
},
{
label: "Selection",
value: "selection"
},
{
label: "Titlebar",
value: "titlebar"
},
{
label: "Header",
value: "header"
},
{
label: "Sidebar",
value: "sidebar"
},
{
label: "Tooltip",
value: "tooltip"
},
{
label: "Menu",
value: "menu"
},
{
label: "Popover",
value: "popover"
},
{
label: "Fullscreen UI (transparent but slightly muted)",
value: "fullscreen-ui"
},
{
label: "HUD (Most transparent)",
value: "hud"
},
]}
select={v => settings.macosVibrancyStyle = v}
isSelected={v => settings.macosVibrancyStyle === v}
serialize={identity}
/>
</ErrorBoundary>
</>
);
}

View file

@ -15,7 +15,8 @@ import { Button, Forms, Select, Slider, Text } from "@webpack/common";
export function NotificationSection() {
return (
<Forms.FormSection className={Margins.top16} title="Vencord Notifications" tag="h5">
<section className={Margins.top16}>
<Forms.FormTitle tag="h5">Vencord Notifications</Forms.FormTitle>
<Flex>
<Button onClick={openNotificationSettingsModal}>
Notification Settings
@ -24,7 +25,7 @@ export function NotificationSection() {
View Notification Log
</Button>
</Flex>
</Forms.FormSection>
</section>
);
}

View file

@ -153,7 +153,9 @@ function VencordSettings() {
/>
)}
<Forms.FormSection title="Quick Actions">
<section>
<Forms.FormTitle tag="h5">Quick Actions</Forms.FormTitle>
<QuickActionCard>
<QuickAction
Icon={LogIcon}
@ -185,11 +187,12 @@ function VencordSettings() {
action={() => VencordNative.native.openExternal("https://github.com/" + gitRemote)}
/>
</QuickActionCard>
</Forms.FormSection>
</section>
<Forms.FormDivider />
<Forms.FormSection className={Margins.top16} title="Settings" tag="h5">
<section className={Margins.top16}>
<Forms.FormTitle tag="h5">Settings</Forms.FormTitle>
<Forms.FormText className={Margins.bottom20} style={{ color: "var(--text-muted)" }}>
Hint: You can change the position of this settings section in the{" "}
<a onClick={() => openPluginModal(Vencord.Plugins.plugins.Settings)}>
@ -198,7 +201,7 @@ function VencordSettings() {
</Forms.FormText>
<Switches />
</Forms.FormSection>
</section>
{needsVibrancySettings && <VibrancySettings />}

View file

@ -89,7 +89,8 @@ function CreateDecorationModal(props: ModalProps) {
<div className={cl("create-decoration-modal-form-preview-container")}>
<div className={cl("create-decoration-modal-form")}>
{error !== null && <Text color="text-danger" variant="text-xs/normal">{error.message}</Text>}
<Forms.FormSection title="File">
<section>
<Forms.FormTitle tag="h5">File</Forms.FormTitle>
<FileUpload
filename={file?.name}
placeholder="Choose a file"
@ -100,8 +101,9 @@ function CreateDecorationModal(props: ModalProps) {
<Forms.FormText className={Margins.top8}>
File should be APNG or PNG.
</Forms.FormText>
</Forms.FormSection>
<Forms.FormSection title="Name">
</section>
<section>
<Forms.FormTitle tag="h5">Name</Forms.FormTitle>
<TextInput
placeholder="Companion Cube"
value={name}
@ -110,7 +112,7 @@ function CreateDecorationModal(props: ModalProps) {
<Forms.FormText className={Margins.top8}>
This name will be used when referring to this decoration.
</Forms.FormText>
</Forms.FormSection>
</section>
</div>
<div>
<AvatarDecorationModalPreview

View file

@ -113,7 +113,7 @@ function SettingsAboutComponent() {
const [color2, setColor2] = useState(existingColors[1]);
return (
<Forms.FormSection>
<section>
<Forms.FormTitle tag="h3">Usage</Forms.FormTitle>
<Forms.FormText>
After enabling this plugin, you will see custom colors in
@ -191,7 +191,7 @@ function SettingsAboutComponent() {
/>
</div>
</Forms.FormText>
</Forms.FormSection>);
</section>);
}
export default definePlugin({

View file

@ -131,7 +131,7 @@ function IdsListComponent(props: { setValue: (value: string) => void; }) {
}
return (
<Forms.FormSection>
<section>
<Forms.FormTitle tag="h3">Filter List</Forms.FormTitle>
<Forms.FormText className={Margins.bottom8}>Comma separated list of activity IDs to filter (Useful for filtering specific RPC activities and CustomRPC</Forms.FormText>
<TextInput
@ -140,7 +140,7 @@ function IdsListComponent(props: { setValue: (value: string) => void; }) {
onChange={handleChange}
placeholder="235834946571337729, 343383572805058560"
/>
</Forms.FormSection>
</section>
);
}

View file

@ -81,15 +81,15 @@ export function NewCategoryModal({ categoryId, modalProps, initialChannelId }: P
{/* form is here so when you press enter while in the text input it submits */}
<form onSubmit={onSave}>
<ModalContent className={cl("content")}>
<Forms.FormSection>
<section>
<Forms.FormTitle>Name</Forms.FormTitle>
<TextInput
value={name}
onChange={e => setName(e)}
/>
</Forms.FormSection>
</section>
<Forms.FormDivider />
<Forms.FormSection>
<section>
<Forms.FormTitle>Color</Forms.FormTitle>
<ColorPickerWithSwatches
key={category.id}
@ -107,7 +107,7 @@ export function NewCategoryModal({ categoryId, modalProps, initialChannelId }: P
/>
)}
/>
</Forms.FormSection>
</section>
</ModalContent>
<ModalFooter>
<Button type="submit" onClick={onSave} disabled={!name}>{categoryId ? "Save" : "Create"}</Button>

View file

@ -88,7 +88,8 @@ function TimingSection({ title, logs, traceEnd }: TimingSectionProps) {
});
return (
<Forms.FormSection title={title} tag="h1">
<section>
<Forms.FormTitle tag="h2">{title}</Forms.FormTitle>
<code>
{traceEnd && (
<div style={{ color: "var(--header-primary)", marginBottom: 5, userSelect: "text" }}>
@ -105,7 +106,7 @@ function TimingSection({ title, logs, traceEnd }: TimingSectionProps) {
))}
</div>
</code>
</Forms.FormSection>
</section>
);
}
@ -117,7 +118,8 @@ function ServerTrace({ trace }: ServerTraceProps) {
const lines = trace.split("\n");
return (
<Forms.FormSection title="Server Trace" tag="h2">
<section>
<Forms.FormTitle tag="h3">Server Trace</Forms.FormTitle>
<code>
<Flex flexDirection="column" style={{ color: "var(--header-primary)", gap: 5, userSelect: "text" }}>
{lines.map((line, idx) => (
@ -125,7 +127,7 @@ function ServerTrace({ trace }: ServerTraceProps) {
))}
</Flex>
</code>
</Forms.FormSection>
</section>
);
}

View file

@ -118,9 +118,9 @@ function VoiceSetting() {
export function VoiceSettingSection() {
return (
<Forms.FormSection>
<section>
<Forms.FormTitle>Voice</Forms.FormTitle>
<VoiceSetting />
</Forms.FormSection>
</section>
);
}

View file

@ -242,7 +242,7 @@ export default definePlugin({
}
return (
<Forms.FormSection>
<section>
<Forms.FormText>
You can customise the spoken messages below. You can disable specific messages by setting them to nothing
</Forms.FormText>
@ -270,7 +270,7 @@ export default definePlugin({
</>
)}
{errorComponent}
</Forms.FormSection>
</section>
);
}
});

View file

@ -26,13 +26,13 @@ import { waitForComponent } from "./internal";
const FormTitle = waitForComponent<t.FormTitle>("FormTitle", filters.componentByCode('["defaultMargin".concat', '="h5"'));
const FormSection = waitForComponent<t.FormSection>("FormSection", filters.componentByCode(".titleId)"));
const FormDivider = waitForComponent<t.FormDivider>("FormDivider", filters.componentByCode(".divider,", ",style:", '"div"', /\.divider,\i\),style:/));
export const Forms = {
FormTitle,
FormText,
FormSection,
/** @deprecated don't use this */
FormSection: "section" as never, // Backwards compat since Vesktop uses this
FormDivider
};
@ -57,8 +57,8 @@ export const TextInput = waitForComponent<t.TextInput>("TextInput", filters.comp
export const TextArea = waitForComponent<t.TextArea>("TextArea", filters.componentByCode("this.getPaddingRight()},id:"));
export const Text = waitForComponent<t.Text>("Text", filters.componentByCode('case"always-white"'));
export const Heading = waitForComponent<t.Heading>("Heading", filters.componentByCode(">6?{", "variant:"));
export const Select = waitForComponent<t.Select>("Select", filters.componentByCode('="bottom",', ".select,", '"Escape"==='));
export const SearchableSelect = waitForComponent<t.SearchableSelect>("SearchableSelect", filters.componentByCode(".setSelectionRange(", ".multi]:"));
export const Select = waitForComponent<t.Select>("Select", filters.componentByCode('"Select"', ".newOptionLabel"));
export const SearchableSelect = waitForComponent<t.SearchableSelect>("SearchableSelect", filters.componentByCode(".searchableSelect"));
export const Slider = waitForComponent<t.Slider>("Slider", filters.componentByCode('"markDash".concat('));
export const Popout = waitForComponent<t.Popout>("Popout", filters.componentByCode("ref:this.ref,", "renderPopout:this.renderPopout,"));
export const Dialog = waitForComponent<t.Dialog>("Dialog", filters.componentByCode('role:"dialog",tabIndex:-1'));