feat(settings): new settings design (#261)
This commit is contained in:
parent
a85ec594a7
commit
6b55dee9fb
14 changed files with 369 additions and 162 deletions
69
src/components/VencordSettings/BackupRestoreTab.tsx
Normal file
69
src/components/VencordSettings/BackupRestoreTab.tsx
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
/*
|
||||
* Vencord, a modification for Discord's desktop app
|
||||
* Copyright (c) 2022 Vendicated and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { downloadSettingsBackup, uploadSettingsBackup } from "../../utils/settingsSync";
|
||||
import { Button, Card, Forms, Margins, Text } from "../../webpack/common";
|
||||
import ErrorBoundary from "../ErrorBoundary";
|
||||
import { Flex } from "../Flex";
|
||||
|
||||
function BackupRestoreTab() {
|
||||
return (
|
||||
<Forms.FormSection title="Settings Sync">
|
||||
<Card style={{
|
||||
backgroundColor: "var(--info-warning-background)",
|
||||
borderColor: "var(--info-warning-foreground)",
|
||||
color: "var(--info-warning-text)",
|
||||
padding: "1em",
|
||||
marginBottom: "0.5em",
|
||||
}}>
|
||||
<Flex flexDirection="column">
|
||||
<strong>Warning</strong>
|
||||
<span>Importing a settings file will overwrite your current settings.</span>
|
||||
</Flex>
|
||||
</Card>
|
||||
<Text variant="text-md/normal" className={Margins.marginBottom8}>
|
||||
You can import and export your Vencord settings as a JSON file.
|
||||
This allows you to easily transfer your settings to another device,
|
||||
or recover your settings after reinstalling Vencord or Discord.
|
||||
</Text>
|
||||
<Text variant="text-md/normal" className={Margins.marginBottom8}>
|
||||
Settings Export contains:
|
||||
<ul>
|
||||
<li>— Custom QuickCSS</li>
|
||||
<li>— Plugin Settings</li>
|
||||
</ul>
|
||||
</Text>
|
||||
<Flex>
|
||||
<Button
|
||||
onClick={uploadSettingsBackup}
|
||||
size={Button.Sizes.SMALL}
|
||||
>
|
||||
Import Settings
|
||||
</Button>
|
||||
<Button
|
||||
onClick={downloadSettingsBackup}
|
||||
size={Button.Sizes.SMALL}
|
||||
>
|
||||
Export Settings
|
||||
</Button>
|
||||
</Flex>
|
||||
</Forms.FormSection>
|
||||
);
|
||||
}
|
||||
|
||||
export default ErrorBoundary.wrap(BackupRestoreTab);
|
||||
22
src/components/VencordSettings/PluginsTab.tsx
Normal file
22
src/components/VencordSettings/PluginsTab.tsx
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
/*
|
||||
* Vencord, a modification for Discord's desktop app
|
||||
* Copyright (c) 2022 Vendicated and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import ErrorBoundary from "../ErrorBoundary";
|
||||
import PluginSettings from "../PluginSettings";
|
||||
|
||||
export default ErrorBoundary.wrap(PluginSettings);
|
||||
216
src/components/VencordSettings/Updater.tsx
Normal file
216
src/components/VencordSettings/Updater.tsx
Normal file
|
|
@ -0,0 +1,216 @@
|
|||
/*
|
||||
* Vencord, a modification for Discord's desktop app
|
||||
* Copyright (c) 2022 Vendicated and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import gitHash from "~git-hash";
|
||||
|
||||
import { classes, useAwaiter } from "../../utils/misc";
|
||||
import { changes, checkForUpdates, getRepo, isNewer, rebuild, update, updateError, UpdateLogger } from "../../utils/updater";
|
||||
import { Alerts, Button, Card, Forms, Margins, Parser, React, Toasts } from "../../webpack/common";
|
||||
import ErrorBoundary from "../ErrorBoundary";
|
||||
import { ErrorCard } from "../ErrorCard";
|
||||
import { Flex } from "../Flex";
|
||||
import { handleComponentFailed } from "../handleComponentFailed";
|
||||
import { Link } from "../Link";
|
||||
|
||||
function withDispatcher(dispatcher: React.Dispatch<React.SetStateAction<boolean>>, action: () => any) {
|
||||
return async () => {
|
||||
dispatcher(true);
|
||||
try {
|
||||
await action();
|
||||
} catch (e: any) {
|
||||
UpdateLogger.error("Failed to update", e);
|
||||
if (!e) {
|
||||
var err = "An unknown error occurred (error is undefined).\nPlease try again.";
|
||||
} else if (e.code && e.cmd) {
|
||||
const { code, path, cmd, stderr } = e;
|
||||
|
||||
if (code === "ENOENT")
|
||||
var err = `Command \`${path}\` not found.\nPlease install it and try again`;
|
||||
else {
|
||||
var err = `An error occured while running \`${cmd}\`:\n`;
|
||||
err += stderr || `Code \`${code}\`. See the console for more info`;
|
||||
}
|
||||
|
||||
} else {
|
||||
var err = "An unknown error occurred. See the console for more info.";
|
||||
}
|
||||
Alerts.show({
|
||||
title: "Oops!",
|
||||
body: (
|
||||
<ErrorCard>
|
||||
{err.split("\n").map(line => <div>{Parser.parse(line)}</div>)}
|
||||
</ErrorCard>
|
||||
)
|
||||
});
|
||||
}
|
||||
finally {
|
||||
dispatcher(false);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
interface CommonProps {
|
||||
repo: string;
|
||||
repoPending: boolean;
|
||||
}
|
||||
|
||||
function Changes({ updates, repo, repoPending }: CommonProps & { updates: typeof changes; }) {
|
||||
return (
|
||||
<Card style={{ padding: ".5em" }}>
|
||||
{updates.map(({ hash, author, message }) => (
|
||||
<div>
|
||||
<Link href={`${repo}/commit/${hash}`} disabled={repoPending}>
|
||||
<code>{hash}</code>
|
||||
</Link>
|
||||
<span style={{
|
||||
marginLeft: "0.5em",
|
||||
color: "var(--text-normal)"
|
||||
}}>{message} - {author}</span>
|
||||
</div>
|
||||
))}
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
function Updatable(props: CommonProps) {
|
||||
const [updates, setUpdates] = React.useState(changes);
|
||||
const [isChecking, setIsChecking] = React.useState(false);
|
||||
const [isUpdating, setIsUpdating] = React.useState(false);
|
||||
|
||||
const isOutdated = (updates?.length ?? 0) > 0;
|
||||
|
||||
return (
|
||||
<>
|
||||
{!updates && updateError ? (
|
||||
<>
|
||||
<Forms.FormText>Failed to check updates. Check the console for more info</Forms.FormText>
|
||||
<ErrorCard style={{ padding: "1em" }}>
|
||||
<p>{updateError.stderr || updateError.stdout || "An unknown error occurred"}</p>
|
||||
</ErrorCard>
|
||||
</>
|
||||
) : (
|
||||
<Forms.FormText className={Margins.marginBottom8}>
|
||||
{isOutdated ? `There are ${updates.length} Updates` : "Up to Date!"}
|
||||
</Forms.FormText>
|
||||
)}
|
||||
|
||||
{isOutdated && <Changes updates={updates} {...props} />}
|
||||
|
||||
<Flex className={classes(Margins.marginBottom8, Margins.marginTop8)}>
|
||||
{isOutdated && <Button
|
||||
size={Button.Sizes.SMALL}
|
||||
disabled={isUpdating || isChecking}
|
||||
onClick={withDispatcher(setIsUpdating, async () => {
|
||||
if (await update()) {
|
||||
setUpdates([]);
|
||||
const needFullRestart = await rebuild();
|
||||
await new Promise<void>(r => {
|
||||
Alerts.show({
|
||||
title: "Update Success!",
|
||||
body: "Successfully updated. Restart now to apply the changes?",
|
||||
confirmText: "Restart",
|
||||
cancelText: "Not now!",
|
||||
onConfirm() {
|
||||
if (needFullRestart)
|
||||
window.DiscordNative.app.relaunch();
|
||||
else
|
||||
location.reload();
|
||||
r();
|
||||
},
|
||||
onCancel: r
|
||||
});
|
||||
});
|
||||
}
|
||||
})}
|
||||
>
|
||||
Update Now
|
||||
</Button>}
|
||||
<Button
|
||||
size={Button.Sizes.SMALL}
|
||||
disabled={isUpdating || isChecking}
|
||||
onClick={withDispatcher(setIsChecking, async () => {
|
||||
const outdated = await checkForUpdates();
|
||||
if (outdated) {
|
||||
setUpdates(changes);
|
||||
} else {
|
||||
setUpdates([]);
|
||||
Toasts.show({
|
||||
message: "No updates found!",
|
||||
id: Toasts.genId(),
|
||||
type: Toasts.Type.MESSAGE,
|
||||
options: {
|
||||
position: Toasts.Position.BOTTOM
|
||||
}
|
||||
});
|
||||
}
|
||||
})}
|
||||
>
|
||||
Check for Updates
|
||||
</Button>
|
||||
</Flex>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function Newer(props: CommonProps) {
|
||||
return (
|
||||
<>
|
||||
<Forms.FormText className={Margins.marginBottom8}>
|
||||
Your local copy has more recent commits. Please stash or reset them.
|
||||
</Forms.FormText>
|
||||
<Changes {...props} updates={changes} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function Updater() {
|
||||
const [repo, err, repoPending] = useAwaiter(getRepo, "Loading...");
|
||||
|
||||
React.useEffect(() => {
|
||||
if (err)
|
||||
UpdateLogger.error("Failed to retrieve repo", err);
|
||||
}, [err]);
|
||||
|
||||
const commonProps: CommonProps = {
|
||||
repo,
|
||||
repoPending
|
||||
};
|
||||
|
||||
return (
|
||||
<Forms.FormSection>
|
||||
<Forms.FormTitle tag="h5">Repo</Forms.FormTitle>
|
||||
|
||||
<Forms.FormText>{repoPending ? repo : err ? "Failed to retrieve - check console" : (
|
||||
<Link href={repo}>
|
||||
{repo.split("/").slice(-2).join("/")}
|
||||
</Link>
|
||||
)} ({gitHash})</Forms.FormText>
|
||||
|
||||
<Forms.FormDivider />
|
||||
|
||||
<Forms.FormTitle tag="h5">Updates</Forms.FormTitle>
|
||||
|
||||
{isNewer ? <Newer {...commonProps} /> : <Updatable {...commonProps} />}
|
||||
</Forms.FormSection >
|
||||
);
|
||||
}
|
||||
|
||||
export default IS_WEB ? null : ErrorBoundary.wrap(Updater, {
|
||||
message: "Failed to render the Updater. If this persists, try using the installer to reinstall!",
|
||||
onError: handleComponentFailed,
|
||||
});
|
||||
134
src/components/VencordSettings/VencordTab.tsx
Normal file
134
src/components/VencordSettings/VencordTab.tsx
Normal file
|
|
@ -0,0 +1,134 @@
|
|||
/*
|
||||
* Vencord, a modification for Discord's desktop app
|
||||
* Copyright (c) 2022 Vendicated and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
|
||||
import { useSettings } from "../../api/settings";
|
||||
import IpcEvents from "../../utils/IpcEvents";
|
||||
import { useAwaiter } from "../../utils/misc";
|
||||
import { Button, Card, Forms, React, Switch } from "../../webpack/common";
|
||||
import DonateButton from "../DonateButton";
|
||||
import ErrorBoundary from "../ErrorBoundary";
|
||||
|
||||
const st = (style: string) => `vcSettings${style}`;
|
||||
|
||||
function VencordSettings() {
|
||||
const [settingsDir, , settingsDirPending] = useAwaiter(() => VencordNative.ipc.invoke<string>(IpcEvents.GET_SETTINGS_DIR), "Loading...");
|
||||
const settings = useSettings();
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<DonateCard />
|
||||
<Forms.FormSection title="Quick Actions">
|
||||
<Card className={st("QuickActionCard")}>
|
||||
{IS_WEB ? (
|
||||
<Button
|
||||
onClick={() => require("./Monaco").launchMonacoEditor()}
|
||||
size={Button.Sizes.SMALL}
|
||||
disabled={settingsDir === "Loading..."}>
|
||||
Open QuickCSS File
|
||||
</Button>
|
||||
) : (
|
||||
<React.Fragment>
|
||||
<Button
|
||||
onClick={() => window.DiscordNative.app.relaunch()}
|
||||
size={Button.Sizes.SMALL}>
|
||||
Restart Client
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => VencordNative.ipc.invoke(IpcEvents.OPEN_MONACO_EDITOR)}
|
||||
size={Button.Sizes.SMALL}
|
||||
disabled={settingsDir === "Loading..."}>
|
||||
Open QuickCSS File
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => window.DiscordNative.fileManager.showItemInFolder(settingsDir)}
|
||||
size={Button.Sizes.SMALL}
|
||||
disabled={settingsDirPending}>
|
||||
Open Settings Folder
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => VencordNative.ipc.invoke(IpcEvents.OPEN_EXTERNAL, "https://github.com/Vendicated/Vencord")}
|
||||
size={Button.Sizes.SMALL}
|
||||
disabled={settingsDirPending}>
|
||||
Open in GitHub
|
||||
</Button>
|
||||
</React.Fragment>
|
||||
)}
|
||||
</Card>
|
||||
</Forms.FormSection>
|
||||
|
||||
<Forms.FormDivider />
|
||||
|
||||
<Forms.FormSection title="Settings">
|
||||
<Switch
|
||||
value={settings.useQuickCss}
|
||||
onChange={(v: boolean) => settings.useQuickCss = v}
|
||||
note="Loads styles from your QuickCss file">
|
||||
Use QuickCss
|
||||
</Switch>
|
||||
{!IS_WEB && (
|
||||
<React.Fragment>
|
||||
<Switch
|
||||
value={settings.enableReactDevtools}
|
||||
onChange={(v: boolean) => settings.enableReactDevtools = v}
|
||||
note="Requires a full restart">
|
||||
Enable React Developer Tools
|
||||
</Switch>
|
||||
<Switch
|
||||
value={settings.notifyAboutUpdates}
|
||||
onChange={(v: boolean) => settings.notifyAboutUpdates = v}
|
||||
note="Shows a Toast on StartUp">
|
||||
Get notified about new Updates
|
||||
</Switch>
|
||||
</React.Fragment>
|
||||
)}
|
||||
|
||||
</Forms.FormSection>
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
|
||||
function DonateCard() {
|
||||
return (
|
||||
<Card style={{
|
||||
padding: "1em",
|
||||
display: "flex",
|
||||
flexDirection: "row",
|
||||
marginBottom: "1em",
|
||||
marginTop: "1em"
|
||||
}}>
|
||||
<div>
|
||||
<Forms.FormTitle tag="h5">Support the Project</Forms.FormTitle>
|
||||
<Forms.FormText>
|
||||
Please consider supporting the Development of Vencord by donating!
|
||||
</Forms.FormText>
|
||||
<DonateButton style={{ transform: "translateX(-1em)" }} />
|
||||
</div>
|
||||
<img
|
||||
role="presentation"
|
||||
src="https://cdn.discordapp.com/emojis/1026533090627174460.png"
|
||||
alt=""
|
||||
style={{ marginLeft: "auto", transform: "rotate(10deg)" }}
|
||||
/>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
export default ErrorBoundary.wrap(VencordSettings);
|
||||
84
src/components/VencordSettings/index.tsx
Normal file
84
src/components/VencordSettings/index.tsx
Normal file
|
|
@ -0,0 +1,84 @@
|
|||
/*
|
||||
* Vencord, a modification for Discord's desktop app
|
||||
* Copyright (c) 2022 Vendicated and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import cssText from "~fileContent/settingsStyles.css";
|
||||
|
||||
import { lazyWebpack } from "../../utils/misc";
|
||||
import { filters } from "../../webpack";
|
||||
import { Forms, React, Router, Text } from "../../webpack/common";
|
||||
import ErrorBoundary from "../ErrorBoundary";
|
||||
import BackupRestoreTab from "./BackupRestoreTab";
|
||||
import PluginsTab from "./PluginsTab";
|
||||
import Updater from "./Updater";
|
||||
import VencordSettings from "./VencordTab";
|
||||
|
||||
const style = document.createElement("style");
|
||||
style.textContent = cssText;
|
||||
document.head.appendChild(style);
|
||||
|
||||
const st = (style: string) => `vcSettings${style}`;
|
||||
|
||||
const TabBar = lazyWebpack(filters.byCode('[role="tab"][aria-disabled="false"]'));
|
||||
|
||||
interface SettingsProps {
|
||||
tab: string;
|
||||
}
|
||||
|
||||
const SettingsTabs = {
|
||||
VencordSettings: { name: "Vencord", component: () => <VencordSettings /> },
|
||||
VencordPlugins: { name: "Plugins", component: () => <PluginsTab /> },
|
||||
VencordThemes: { name: "Themes", component: () => <Text variant="text-md/medium">Coming soon to a Vencord near you!</Text> },
|
||||
VencordUpdater: { name: "Updater", component: () => Updater ? <Updater /> : null },
|
||||
VencordSettingsSync: { name: "Backup & Restore", component: () => <BackupRestoreTab /> },
|
||||
};
|
||||
|
||||
|
||||
function Settings(props: SettingsProps) {
|
||||
const { tab = "VencordSettings" } = props;
|
||||
|
||||
const CurrentTab = SettingsTabs[tab]?.component ?? null;
|
||||
|
||||
return <Forms.FormSection>
|
||||
<Text variant="heading-md/normal" tag="h2">Vencord Settings</Text>
|
||||
|
||||
<TabBar
|
||||
type={TabBar.Types.TOP}
|
||||
look={TabBar.Looks.BRAND}
|
||||
className={st("TabBar")}
|
||||
selectedItem={tab}
|
||||
onItemSelect={Router.open}
|
||||
>
|
||||
{Object.entries(SettingsTabs).map(([key, { name }]) => {
|
||||
return <TabBar.Item
|
||||
id={key}
|
||||
className={st("TabBarItem")}
|
||||
key={key}>
|
||||
{name}
|
||||
</TabBar.Item>;
|
||||
})}
|
||||
</TabBar>
|
||||
<Forms.FormDivider />
|
||||
<CurrentTab />
|
||||
</Forms.FormSection >;
|
||||
}
|
||||
|
||||
export default function (props: SettingsProps) {
|
||||
return <ErrorBoundary>
|
||||
<Settings tab={props.tab} />
|
||||
</ErrorBoundary>;
|
||||
}
|
||||
23
src/components/VencordSettings/settingsStyles.css
Normal file
23
src/components/VencordSettings/settingsStyles.css
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
.vcSettingsTabBar {
|
||||
margin-top: 20px;
|
||||
margin-bottom: -2px;
|
||||
border-bottom: 2px solid var(--background-modifier-accent);
|
||||
}
|
||||
|
||||
.vcSettingsTabBarItem {
|
||||
margin-right: 32px;
|
||||
padding-bottom: 16px;
|
||||
margin-bottom: -2px;
|
||||
}
|
||||
|
||||
.vcSettingsQuickActionCard {
|
||||
padding: 1em;
|
||||
display: flex;
|
||||
gap: 1em;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
flex-wrap: wrap;
|
||||
flex-grow: 1;
|
||||
flex-direction: row;
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue