feat(desktop): add manual download fallback to UpdateToast
Extends UpdateToast to handle update-error events from the preload bridge, showing a "Download" link to the GitHub release URL when auto-update fails.
This commit is contained in:
@@ -1,44 +1,88 @@
|
|||||||
import React, { useState, useEffect } from 'react';
|
import { useState, useEffect } from 'react';
|
||||||
import { isElectron } from '../../platform/platform';
|
import { isElectron } from '../../platform/platform';
|
||||||
|
|
||||||
|
interface UpdateError {
|
||||||
|
message: string;
|
||||||
|
releaseUrl: string;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Persistent toast shown when an Electron auto-update has been downloaded.
|
* Persistent toast shown when an Electron auto-update has been downloaded
|
||||||
|
* or when auto-update fails (offers manual download link).
|
||||||
* Renders nothing in browser environments.
|
* Renders nothing in browser environments.
|
||||||
*/
|
*/
|
||||||
export function UpdateToast() {
|
export function UpdateToast() {
|
||||||
const [downloadedVersion, setDownloadedVersion] = useState<string | null>(null);
|
const [downloadedVersion, setDownloadedVersion] = useState<string | null>(null);
|
||||||
|
const [failedUpdate, setFailedUpdate] = useState<UpdateError | null>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!isElectron() || !window.backspace) return;
|
if (!isElectron() || !window.backspace) return;
|
||||||
|
|
||||||
window.backspace.onUpdateDownloaded((info) => {
|
window.backspace.onUpdateDownloaded((info) => {
|
||||||
setDownloadedVersion(info.version);
|
setDownloadedVersion(info.version);
|
||||||
|
// Auto-download succeeded — clear any previous error state
|
||||||
|
setFailedUpdate(null);
|
||||||
|
});
|
||||||
|
|
||||||
|
window.backspace.onUpdateError((error) => {
|
||||||
|
setFailedUpdate(error);
|
||||||
});
|
});
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
if (!downloadedVersion) return null;
|
// Nothing to show
|
||||||
|
if (!downloadedVersion && !failedUpdate) return null;
|
||||||
|
|
||||||
const handleRestart = () => {
|
// Auto-download succeeded — show restart toast
|
||||||
window.backspace?.installUpdate();
|
if (downloadedVersion) {
|
||||||
};
|
return (
|
||||||
|
<div className="fixed bottom-6 left-6 z-[300] animate-slide-up">
|
||||||
|
<div className="glass-pill rounded-xl px-4 py-3 flex items-center gap-3 max-w-[340px]">
|
||||||
|
<div className="flex-1 min-w-0">
|
||||||
|
<p className="text-sm font-medium text-txt-primary">Update ready</p>
|
||||||
|
<p className="text-xs text-txt-secondary truncate">
|
||||||
|
Version {downloadedVersion} has been downloaded
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
onClick={() => window.backspace?.installUpdate()}
|
||||||
|
className="shrink-0 px-3 py-1.5 text-xs font-medium rounded-lg bg-accent-primary hover:bg-accent-primary/80 text-white transition-colors"
|
||||||
|
>
|
||||||
|
Restart
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => setDownloadedVersion(null)}
|
||||||
|
className="shrink-0 p-1 text-txt-tertiary hover:text-txt-secondary transition-colors"
|
||||||
|
aria-label="Dismiss"
|
||||||
|
>
|
||||||
|
<svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
|
||||||
|
<path strokeLinecap="round" strokeLinejoin="round" d="M6 18L18 6M6 6l12 12" />
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Auto-download failed — show manual download toast
|
||||||
return (
|
return (
|
||||||
<div className="fixed bottom-6 left-6 z-[300] animate-slide-up">
|
<div className="fixed bottom-6 left-6 z-[300] animate-slide-up">
|
||||||
<div className="glass-pill rounded-xl px-4 py-3 flex items-center gap-3 max-w-[340px]">
|
<div className="glass-pill rounded-xl px-4 py-3 flex items-center gap-3 max-w-[380px]">
|
||||||
<div className="flex-1 min-w-0">
|
<div className="flex-1 min-w-0">
|
||||||
<p className="text-sm font-medium text-txt-primary">Update ready</p>
|
<p className="text-sm font-medium text-txt-primary">Update available</p>
|
||||||
<p className="text-xs text-txt-secondary truncate">
|
<p className="text-xs text-txt-secondary truncate">
|
||||||
Version {downloadedVersion} has been downloaded
|
Auto-install failed — download manually
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<button
|
<a
|
||||||
onClick={handleRestart}
|
href={failedUpdate!.releaseUrl}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
className="shrink-0 px-3 py-1.5 text-xs font-medium rounded-lg bg-accent-primary hover:bg-accent-primary/80 text-white transition-colors"
|
className="shrink-0 px-3 py-1.5 text-xs font-medium rounded-lg bg-accent-primary hover:bg-accent-primary/80 text-white transition-colors"
|
||||||
>
|
>
|
||||||
Restart
|
Download
|
||||||
</button>
|
</a>
|
||||||
<button
|
<button
|
||||||
onClick={() => setDownloadedVersion(null)}
|
onClick={() => setFailedUpdate(null)}
|
||||||
className="shrink-0 p-1 text-txt-tertiary hover:text-txt-secondary transition-colors"
|
className="shrink-0 p-1 text-txt-tertiary hover:text-txt-secondary transition-colors"
|
||||||
aria-label="Dismiss"
|
aria-label="Dismiss"
|
||||||
>
|
>
|
||||||
|
|||||||
Reference in New Issue
Block a user