- Removes the option to disable update notifications. Users really should not be outdated, so this option was never good. To disable notifications, turn on auto update - Enables auto update by default. Users keep complaining about issues while being outdated, so this should help - Update Notification now opens Updater in a modal to remove dependency on Settings patch. This makes it slightly more failsafe, it's unlikely that both modals and our settings patch break
268 lines
9.8 KiB
TypeScript
268 lines
9.8 KiB
TypeScript
/*
|
|
* 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 { ModalCloseButton, ModalContent, ModalProps, ModalRoot, ModalSize, openModal } from "@utils/modal";
|
|
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 { handleSettingsTabError, 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);
|
|
|
|
let err: string;
|
|
if (!e) {
|
|
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")
|
|
err = `Command \`${path}\` not found.\nPlease install it and try again`;
|
|
else {
|
|
err = `An error occurred while running \`${cmd}\`:\n`;
|
|
err += stderr || `Code \`${code}\`. See the console for more info`;
|
|
}
|
|
|
|
} else {
|
|
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: "0 0.5em" }}>
|
|
{updates.map(({ hash, author, message }) => (
|
|
<div style={{
|
|
marginTop: "0.5em",
|
|
marginBottom: "0.5em"
|
|
}}>
|
|
<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 ? (updates.length === 1 ? "There is 1 Update" : `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(["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.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_UPDATER_DISABLED ? null : wrapTab(Updater, "Updater");
|
|
|
|
export const openUpdaterModal = IS_UPDATER_DISABLED ? null : function () {
|
|
const UpdaterTab = wrapTab(Updater, "Updater");
|
|
|
|
try {
|
|
openModal(wrapTab((modalProps: ModalProps) => (
|
|
<ModalRoot {...modalProps} size={ModalSize.MEDIUM}>
|
|
<ModalContent className="vc-updater-modal">
|
|
<ModalCloseButton onClick={modalProps.onClose} className="vc-updater-modal-close-button" />
|
|
<UpdaterTab />
|
|
</ModalContent>
|
|
</ModalRoot>
|
|
), "UpdaterModal"));
|
|
} catch {
|
|
handleSettingsTabError();
|
|
}
|
|
};
|