aboutsummaryrefslogtreecommitdiff
path: root/src/components/VencordSettings/UpdaterTab.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'src/components/VencordSettings/UpdaterTab.tsx')
-rw-r--r--src/components/VencordSettings/UpdaterTab.tsx252
1 files changed, 252 insertions, 0 deletions
diff --git a/src/components/VencordSettings/UpdaterTab.tsx b/src/components/VencordSettings/UpdaterTab.tsx
new file mode 100644
index 0000000..4d0b86c
--- /dev/null
+++ b/src/components/VencordSettings/UpdaterTab.tsx
@@ -0,0 +1,252 @@
+/*
+ * 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 { ErrorCard } from "@components/ErrorCard";
+import { Flex } from "@components/Flex";
+import { Link } from "@components/Link";
+import { Margins } from "@utils/margins";
+import { classes } from "@utils/misc";
+import { relaunch } from "@utils/native";
+import { useAwaiter } from "@utils/react";
+import { changes, checkForUpdates, getRepo, isNewer, update, updateError, UpdateLogger } from "@utils/updater";
+import { Alerts, Button, Card, Forms, Parser, React, Switch, Toasts } from "@webpack/common";
+
+import gitHash from "~git-hash";
+
+import { SettingsTab, wrapTab } from "./shared";
+
+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 HashLink({ repo, hash, disabled = false }: { repo: string, hash: string, disabled?: boolean; }) {
+ return <Link href={`${repo}/commit/${hash}`} disabled={disabled}>
+ {hash}
+ </Link>;
+}
+
+function Changes({ updates, repo, repoPending }: CommonProps & { updates: typeof changes; }) {
+ return (
+ <Card style={{ padding: ".5em" }}>
+ {updates.map(({ hash, author, message }) => (
+ <div>
+ <code><HashLink {...{ repo, hash }} disabled={repoPending} /></code>
+ <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.bottom8}>
+ {isOutdated ? `There are ${updates.length} Updates` : "Up to Date!"}
+ </Forms.FormText>
+ )}
+
+ {isOutdated && <Changes updates={updates} {...props} />}
+
+ <Flex className={classes(Margins.bottom8, Margins.top8)}>
+ {isOutdated && <Button
+ size={Button.Sizes.SMALL}
+ disabled={isUpdating || isChecking}
+ onClick={withDispatcher(setIsUpdating, async () => {
+ if (await update()) {
+ setUpdates([]);
+ 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() {
+ relaunch();
+ 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.bottom8}>
+ Your local copy has more recent commits. Please stash or reset them.
+ </Forms.FormText>
+ <Changes {...props} updates={changes} />
+ </>
+ );
+}
+
+function Updater() {
+ const settings = useSettings(["notifyAboutUpdates", "autoUpdate", "autoUpdateNotification"]);
+
+ const [repo, err, repoPending] = useAwaiter(getRepo, { fallbackValue: "Loading..." });
+
+ React.useEffect(() => {
+ if (err)
+ UpdateLogger.error("Failed to retrieve repo", err);
+ }, [err]);
+
+ const commonProps: CommonProps = {
+ repo,
+ repoPending
+ };
+
+ return (
+ <SettingsTab title="Vencord Updater">
+ <Forms.FormTitle tag="h5">Updater Settings</Forms.FormTitle>
+ <Switch
+ value={settings.notifyAboutUpdates}
+ onChange={(v: boolean) => settings.notifyAboutUpdates = v}
+ note="Shows a notification on startup"
+ disabled={settings.autoUpdate}
+ >
+ Get notified about new updates
+ </Switch>
+ <Switch
+ value={settings.autoUpdate}
+ onChange={(v: boolean) => settings.autoUpdate = v}
+ note="Automatically update Vencord without confirmation prompt"
+ >
+ Automatically update
+ </Switch>
+ <Switch
+ value={settings.autoUpdateNotification}
+ onChange={(v: boolean) => settings.autoUpdateNotification = v}
+ note="Shows a notification when Vencord automatically updates"
+ disabled={!settings.autoUpdate}
+ >
+ Get notified when an automatic update completes
+ </Switch>
+
+ <Forms.FormTitle tag="h5">Repo</Forms.FormTitle>
+
+ <Forms.FormText className="vc-text-selectable">
+ {repoPending
+ ? repo
+ : err
+ ? "Failed to retrieve - check console"
+ : (
+ <Link href={repo}>
+ {repo.split("/").slice(-2).join("/")}
+ </Link>
+ )
+ }
+ {" "}(<HashLink hash={gitHash} repo={repo} disabled={repoPending} />)
+ </Forms.FormText>
+
+ <Forms.FormDivider className={Margins.top8 + " " + Margins.bottom8} />
+
+ <Forms.FormTitle tag="h5">Updates</Forms.FormTitle>
+
+ {isNewer ? <Newer {...commonProps} /> : <Updatable {...commonProps} />}
+ </SettingsTab>
+ );
+}
+
+export default IS_WEB ? null : wrapTab(Updater, "Updater");