feat: add updater release infrastructure

This commit is contained in:
webadderall
2026-03-28 20:42:38 +11:00
parent ff93ea6099
commit 6463cdff58
12 changed files with 904 additions and 262 deletions
+82
View File
@@ -1,4 +1,6 @@
import { Gift } from "lucide-react";
import { useEffect, useState } from "react";
import { toast } from "sonner";
import { CountdownOverlay } from "./components/countdown/CountdownOverlay";
import { LaunchWindow } from "./components/launch/LaunchWindow";
import { SourceSelector } from "./components/launch/SourceSelector";
@@ -9,6 +11,13 @@ import { useI18n } from "./contexts/I18nContext";
import { ShortcutsProvider } from "./contexts/ShortcutsContext";
import { loadAllCustomFonts } from "./lib/customFonts";
const UPDATE_TOAST_ID = "recordly-update-ready";
function formatDelayHours(delayMs: number) {
const hours = Math.max(1, Math.round(delayMs / (60 * 60 * 1000)));
return `${hours}h`;
}
export default function App() {
const [windowType, setWindowType] = useState("");
const { locale, t } = useI18n();
@@ -40,6 +49,79 @@ export default function App() {
windowType === "editor" ? t("app.editorTitle", "Recordly Editor") : t("app.name", "Recordly");
}, [windowType, locale, t]);
useEffect(() => {
if (
windowType === "countdown" ||
windowType === "source-selector" ||
typeof window.electronAPI?.onUpdateReadyToast !== "function"
) {
return;
}
return window.electronAPI.onUpdateReadyToast((payload) => {
toast.custom(
(toastInstance) => (
<div className="pointer-events-auto flex w-[390px] items-start gap-3 rounded-2xl border border-sky-300/20 bg-[#0d1117]/95 p-4 text-white shadow-2xl shadow-black/40 backdrop-blur-xl">
<div className="mt-0.5 flex h-10 w-10 flex-shrink-0 items-center justify-center rounded-2xl bg-sky-400/15 text-sky-300">
<Gift className="h-5 w-5" />
</div>
<div className="min-w-0 flex-1">
<div className="flex items-center gap-2">
<p className="text-sm font-semibold tracking-tight">
{payload.isPreview ? "Update Toast Preview" : `Recordly ${payload.version} is ready`}
</p>
{payload.isPreview ? (
<span className="rounded-full border border-sky-300/20 bg-sky-400/10 px-2 py-0.5 text-[10px] font-medium uppercase tracking-[0.18em] text-sky-200">
Dev
</span>
) : null}
</div>
<p className="mt-1 text-sm leading-5 text-white/70">{payload.detail}</p>
<div className="mt-3 flex flex-wrap items-center gap-2">
<button
type="button"
onClick={async () => {
toast.dismiss(toastInstance);
if (payload.isPreview) {
toast.success("Preview only. No real update was installed.");
return;
}
await window.electronAPI.installDownloadedUpdate();
}}
className="rounded-xl bg-sky-400 px-3 py-2 text-xs font-semibold text-[#031a2c] transition-colors hover:bg-sky-300"
>
Update Now
</button>
<button
type="button"
onClick={async () => {
toast.dismiss(toastInstance);
if (payload.isPreview) {
toast.success(`Preview dismissed. We'll show it again in ${formatDelayHours(payload.delayMs)}.`);
return;
}
const result = await window.electronAPI.deferDownloadedUpdate(payload.delayMs);
if (result.success) {
toast.success(`Okay, we'll remind you in ${formatDelayHours(payload.delayMs)}.`);
} else if (result.message) {
toast.error(result.message);
}
}}
className="rounded-xl border border-white/10 bg-white/5 px-3 py-2 text-xs font-medium text-white/85 transition-colors hover:bg-white/10"
>
Update Later ({formatDelayHours(payload.delayMs)})
</button>
</div>
</div>
</div>
),
{ id: UPDATE_TOAST_ID, duration: Number.POSITIVE_INFINITY },
);
});
}, [windowType]);
switch (windowType) {
case "hud-overlay":
return (