aboutsummaryrefslogtreecommitdiff
path: root/src/components/VencordSettings
diff options
context:
space:
mode:
Diffstat (limited to 'src/components/VencordSettings')
-rw-r--r--src/components/VencordSettings/BackupRestoreTab.tsx69
-rw-r--r--src/components/VencordSettings/PluginsTab.tsx22
-rw-r--r--src/components/VencordSettings/Updater.tsx216
-rw-r--r--src/components/VencordSettings/VencordTab.tsx134
-rw-r--r--src/components/VencordSettings/index.tsx84
-rw-r--r--src/components/VencordSettings/settingsStyles.css23
6 files changed, 548 insertions, 0 deletions
diff --git a/src/components/VencordSettings/BackupRestoreTab.tsx b/src/components/VencordSettings/BackupRestoreTab.tsx
new file mode 100644
index 0000000..ce0bdaa
--- /dev/null
+++ b/src/components/VencordSettings/BackupRestoreTab.tsx
@@ -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>&mdash; Custom QuickCSS</li>
+ <li>&mdash; 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);
diff --git a/src/components/VencordSettings/PluginsTab.tsx b/src/components/VencordSettings/PluginsTab.tsx
new file mode 100644
index 0000000..0c89686
--- /dev/null
+++ b/src/components/VencordSettings/PluginsTab.tsx
@@ -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);
diff --git a/src/components/VencordSettings/Updater.tsx b/src/components/VencordSettings/Updater.tsx
new file mode 100644
index 0000000..8f2b75b
--- /dev/null
+++ b/src/components/VencordSettings/Updater.tsx
@@ -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,
+});
diff --git a/src/components/VencordSettings/VencordTab.tsx b/src/components/VencordSettings/VencordTab.tsx
new file mode 100644
index 0000000..94add5e
--- /dev/null
+++ b/src/components/VencordSettings/VencordTab.tsx
@@ -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);
diff --git a/src/components/VencordSettings/index.tsx b/src/components/VencordSettings/index.tsx
new file mode 100644
index 0000000..3701897
--- /dev/null
+++ b/src/components/VencordSettings/index.tsx
@@ -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>;
+}
diff --git a/src/components/VencordSettings/settingsStyles.css b/src/components/VencordSettings/settingsStyles.css
new file mode 100644
index 0000000..e90ef14
--- /dev/null
+++ b/src/components/VencordSettings/settingsStyles.css
@@ -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;
+}