Feat/automatic update installation (#5345)
Signed-off-by: AdemolaAri <ademola.ari@gmail.com>
This commit is contained in:
committed by
GitHub
parent
8e9b6c9bc6
commit
c3c71a2ff4
@@ -1,4 +1,4 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { Button } from '../../ui/button';
|
||||
import { Loader2, Download, CheckCircle, AlertCircle } from 'lucide-react';
|
||||
|
||||
@@ -29,6 +29,9 @@ export default function UpdateSection() {
|
||||
currentVersion: '',
|
||||
});
|
||||
const [progress, setProgress] = useState<number>(0);
|
||||
const [isUsingGitHubFallback, setIsUsingGitHubFallback] = useState<boolean>(false);
|
||||
const progressTimeoutRef = React.useRef<ReturnType<typeof setTimeout> | null>(null);
|
||||
const lastProgressRef = React.useRef<number>(0); // Track last progress to prevent backward jumps
|
||||
|
||||
useEffect(() => {
|
||||
// Get current version on mount
|
||||
@@ -47,6 +50,11 @@ export default function UpdateSection() {
|
||||
}
|
||||
});
|
||||
|
||||
// Check if using GitHub fallback
|
||||
window.electron.isUsingGitHubFallback().then((isGitHub) => {
|
||||
setIsUsingGitHubFallback(isGitHub);
|
||||
});
|
||||
|
||||
// Listen for updater events
|
||||
window.electron.onUpdaterEvent((event) => {
|
||||
console.log('Updater event:', event);
|
||||
@@ -63,6 +71,10 @@ export default function UpdateSection() {
|
||||
latestVersion: (event.data as UpdateEventData)?.version,
|
||||
isUpdateAvailable: true,
|
||||
}));
|
||||
// Check if GitHub fallback is being used
|
||||
window.electron.isUsingGitHubFallback().then((isGitHub) => {
|
||||
setIsUsingGitHubFallback(isGitHub);
|
||||
});
|
||||
break;
|
||||
|
||||
case 'update-not-available':
|
||||
@@ -73,10 +85,29 @@ export default function UpdateSection() {
|
||||
}));
|
||||
break;
|
||||
|
||||
case 'download-progress':
|
||||
case 'download-progress': {
|
||||
setUpdateStatus('downloading');
|
||||
setProgress((event.data as UpdateEventData)?.percent || 0);
|
||||
|
||||
// Get the new progress value (ensure it's a valid number)
|
||||
const rawPercent = (event.data as UpdateEventData)?.percent;
|
||||
const newProgress = typeof rawPercent === 'number' ? Math.round(rawPercent) : 0;
|
||||
|
||||
// Only update if progress increased (prevents backward jumps from out-of-order events)
|
||||
if (newProgress > lastProgressRef.current) {
|
||||
lastProgressRef.current = newProgress;
|
||||
|
||||
// Cancel any pending update
|
||||
if (progressTimeoutRef.current) {
|
||||
clearTimeout(progressTimeoutRef.current);
|
||||
}
|
||||
|
||||
// Use a small delay to batch rapid updates
|
||||
progressTimeoutRef.current = setTimeout(() => {
|
||||
setProgress(newProgress);
|
||||
}, 50); // 50ms delay for smoother batching
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case 'update-downloaded':
|
||||
setUpdateStatus('ready');
|
||||
@@ -93,11 +124,19 @@ export default function UpdateSection() {
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
// Cleanup timeout on unmount
|
||||
return () => {
|
||||
if (progressTimeoutRef.current) {
|
||||
clearTimeout(progressTimeoutRef.current);
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
|
||||
const checkForUpdates = async () => {
|
||||
setUpdateStatus('checking');
|
||||
setProgress(0);
|
||||
lastProgressRef.current = 0; // Reset progress tracking for new download
|
||||
|
||||
try {
|
||||
const result = await window.electron.checkForUpdates();
|
||||
@@ -123,29 +162,6 @@ export default function UpdateSection() {
|
||||
}
|
||||
};
|
||||
|
||||
const downloadAndInstallUpdate = async () => {
|
||||
setUpdateStatus('downloading');
|
||||
setProgress(0);
|
||||
|
||||
try {
|
||||
const result = await window.electron.downloadUpdate();
|
||||
|
||||
if (!result.success) {
|
||||
throw new Error(result.error || 'Failed to download update');
|
||||
}
|
||||
|
||||
// The download progress and completion will be handled by updater events
|
||||
} catch (error) {
|
||||
console.error('Error downloading update:', error);
|
||||
setUpdateInfo((prev) => ({
|
||||
...prev,
|
||||
error: error instanceof Error ? error.message : 'Failed to download update',
|
||||
}));
|
||||
setUpdateStatus('error');
|
||||
setTimeout(() => setUpdateStatus('idle'), 5000);
|
||||
}
|
||||
};
|
||||
|
||||
const installUpdate = () => {
|
||||
window.electron.installUpdate();
|
||||
};
|
||||
@@ -216,13 +232,6 @@ export default function UpdateSection() {
|
||||
Check for Updates
|
||||
</Button>
|
||||
|
||||
{updateInfo.isUpdateAvailable && updateStatus === 'idle' && (
|
||||
<Button onClick={downloadAndInstallUpdate} variant="secondary" size="sm">
|
||||
<Download className="w-3 h-3 mr-1" />
|
||||
Download Update
|
||||
</Button>
|
||||
)}
|
||||
|
||||
{updateStatus === 'ready' && (
|
||||
<Button onClick={installUpdate} variant="default" size="sm">
|
||||
Install & Restart
|
||||
@@ -238,21 +247,57 @@ export default function UpdateSection() {
|
||||
)}
|
||||
|
||||
{updateStatus === 'downloading' && (
|
||||
<div className="w-full bg-gray-200 rounded-full h-1.5">
|
||||
<div
|
||||
className="bg-blue-500 h-1.5 rounded-full transition-all duration-300"
|
||||
style={{ width: `${progress}%` }}
|
||||
/>
|
||||
<div className="w-full mt-2">
|
||||
<div className="flex justify-between text-xs text-text-muted mb-1">
|
||||
<span>Downloading update...</span>
|
||||
<span>{progress}%</span>
|
||||
</div>
|
||||
<div className="w-full bg-gray-200 dark:bg-gray-700 rounded-full h-2 overflow-hidden">
|
||||
<div
|
||||
className="bg-blue-500 h-2 rounded-full transition-[width] duration-150 ease-out"
|
||||
style={{ width: `${Math.max(progress, 0)}%`, minWidth: progress > 0 ? '8px' : '0' }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Update information */}
|
||||
{updateInfo.isUpdateAvailable && (
|
||||
{updateInfo.isUpdateAvailable && updateStatus === 'idle' && (
|
||||
<div className="text-xs text-text-muted mt-4 space-y-1">
|
||||
<p>Update will be downloaded to your Downloads folder.</p>
|
||||
<p className="text-xs text-amber-600">
|
||||
Note: After downloading, you'll need to close the app and manually install the update.
|
||||
</p>
|
||||
<p>Update will be downloaded automatically in the background.</p>
|
||||
{isUsingGitHubFallback ? (
|
||||
<p className="text-xs text-amber-600">
|
||||
After download, you'll need to manually install the update.
|
||||
</p>
|
||||
) : (
|
||||
<p className="text-xs text-green-600">
|
||||
The update will be installed automatically when you quit the app.
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{updateStatus === 'ready' && (
|
||||
<div className="text-xs text-text-muted mt-4 space-y-1">
|
||||
{isUsingGitHubFallback ? (
|
||||
<>
|
||||
<p className="text-xs text-green-600">
|
||||
✓ Update is ready! Click "Install & Restart" for installation instructions.
|
||||
</p>
|
||||
<p className="text-xs text-text-muted">
|
||||
Manual installation required for this update method.
|
||||
</p>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<p className="text-xs text-green-600">
|
||||
✓ Update is ready! It will be installed when you quit Goose.
|
||||
</p>
|
||||
<p className="text-xs text-text-muted">
|
||||
Or click "Install & Restart" to update now.
|
||||
</p>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user