/**
* The check-and-notify update banner: a slim, dismissible strip shown below the header when a newer
* GitHub release exists than the running build. Edition-aware — the call to action fits how THIS
* edition updates (download the new installer/portable/release, or, for a git checkout, a copyable
* `git pull` command). It never auto-installs anything: this is the lowest-fragility first rung of
* the updates design (see notes/plans/updates-upgrades.md); the full desktop auto-installer is the
* scaffolded Phase 2. Local/desktop only — the online build never renders it.
*
* SSR-safe: it renders `null` until the client-side check resolves (the effect never runs during the
* online build's prerender), so it adds nothing to the server markup and can't cause a hydration
* mismatch.
* @module gui/components/UpdateBanner
*/
import { useState } from "react";
import { useIntl, defineMessages } from "react-intl";
import { useUpdateCheck } from "../lib/useUpdateCheck.js";
import { DownloadIcon, ExternalLinkIcon, CloseIcon, CopyIcon, SparkleIcon } from "./icons.jsx";
import { jsxDEV as _jsxDEV, Fragment as _Fragment } from "react/jsx-dev-runtime";
const msgs = defineMessages({
available: {
id: "update.available",
defaultMessage: "Version {version} is available.",
description: "Update banner headline; {version} is the new release number"
},
download: {
id: "update.download",
defaultMessage: "Download",
description: "Update banner button that opens the release download page"
},
viewRelease: {
id: "update.viewRelease",
defaultMessage: "View release",
description: "Update banner button that opens the release page (git edition)"
},
gitHint: {
id: "update.gitHint",
defaultMessage: "Update your checkout:",
description: "Label before the copyable git update command"
},
copy: {
id: "update.copy",
defaultMessage: "Copy command",
description: "aria-label/title for the button that copies the git update command"
},
copied: {
id: "update.copied",
defaultMessage: "Copied",
description: "Confirmation shown briefly after copying the git command"
},
dismiss: {
id: "update.dismiss",
defaultMessage: "Dismiss",
description: "aria-label/title for the button that dismisses the update banner"
}
});
// The exact command a git-checkout user runs to update safely (never clobbers the working tree).
const GIT_COMMAND = "git pull --ff-only && npm install";
/**
* @returns {JSX.Element|null} The update banner, or `null` when there's nothing to offer.
*/
export default function UpdateBanner() {
const intl = useIntl();
const {
update,
dismiss
} = useUpdateCheck();
const [copied, setCopied] = useState(false);
if (!update) return null;
const isGit = update.edition === "git";
const copyCommand = async () => {
try {
await navigator.clipboard.writeText(GIT_COMMAND);
setCopied(true);
setTimeout(() => setCopied(false), 1600);
} catch {
/* clipboard blocked — the command is visible to copy by hand */
}
};
return /*#__PURE__*/_jsxDEV("div", {
className: "update-banner",
role: "status",
"aria-live": "polite",
children: [/*#__PURE__*/_jsxDEV("div", {
className: "update-banner-head",
children: [/*#__PURE__*/_jsxDEV("span", {
className: "update-banner-icon",
"aria-hidden": "true",
children: /*#__PURE__*/_jsxDEV(SparkleIcon, {}, void 0, false)
}, void 0, false), /*#__PURE__*/_jsxDEV("span", {
className: "update-banner-text",
children: intl.formatMessage(msgs.available, {
version: update.version
})
}, void 0, false)]
}, void 0, true), isGit && /*#__PURE__*/_jsxDEV("span", {
className: "update-banner-cmd",
children: [/*#__PURE__*/_jsxDEV("span", {
className: "update-banner-cmd-label",
children: intl.formatMessage(msgs.gitHint)
}, void 0, false), /*#__PURE__*/_jsxDEV("code", {
children: GIT_COMMAND
}, void 0, false), /*#__PURE__*/_jsxDEV("button", {
type: "button",
className: "update-banner-copy",
onClick: copyCommand,
title: intl.formatMessage(msgs.copy),
"aria-label": intl.formatMessage(msgs.copy),
children: [/*#__PURE__*/_jsxDEV(CopyIcon, {}, void 0, false), copied && /*#__PURE__*/_jsxDEV("span", {
className: "update-banner-copied",
children: intl.formatMessage(msgs.copied)
}, void 0, false)]
}, void 0, true)]
}, void 0, true), /*#__PURE__*/_jsxDEV("a", {
className: "update-banner-cta",
href: update.url,
target: "_blank",
rel: "noopener noreferrer",
onClick: dismiss,
children: isGit ? /*#__PURE__*/_jsxDEV(_Fragment, {
children: [intl.formatMessage(msgs.viewRelease), /*#__PURE__*/_jsxDEV(ExternalLinkIcon, {}, void 0, false)]
}, void 0, true) : /*#__PURE__*/_jsxDEV(_Fragment, {
children: [/*#__PURE__*/_jsxDEV(DownloadIcon, {}, void 0, false), intl.formatMessage(msgs.download)]
}, void 0, true)
}, void 0, false), /*#__PURE__*/_jsxDEV("button", {
type: "button",
className: "update-banner-close",
onClick: dismiss,
title: intl.formatMessage(msgs.dismiss),
"aria-label": intl.formatMessage(msgs.dismiss),
children: /*#__PURE__*/_jsxDEV(CloseIcon, {}, void 0, false)
}, void 0, false)]
}, void 0, true);
}