|
1 | 1 | import { invoke } from "@tauri-apps/api/core";
|
2 | 2 | import { getVersion } from "@tauri-apps/api/app";
|
3 |
| -import { useEffect, useState } from "react"; |
| 3 | +import { useEffect, useRef, useState } from "react"; |
4 | 4 | import { backendFormatToShortcut, Shortcut } from "../utils/keyboardUtils";
|
5 | 5 | import { updateManager, UpdateInfo } from "../utils/updater";
|
6 | 6 | import miloLogo from "../assets/icon.png";
|
7 | 7 |
|
8 | 8 | export function InfoPage() {
|
| 9 | + const isDev = import.meta.env.DEV; |
9 | 10 | const [shortcut, setShortcut] = useState<Shortcut>([]);
|
10 | 11 | const [shortcutEnabled, setShortcutEnabled] = useState(true);
|
11 | 12 | const [updateInfo, setUpdateInfo] = useState<UpdateInfo | null>(null);
|
12 | 13 | const [isCheckingUpdate, setIsCheckingUpdate] = useState(false);
|
13 | 14 | const [isDownloading, setIsDownloading] = useState(false);
|
14 | 15 | const [downloadProgress, setDownloadProgress] = useState(0);
|
15 | 16 | const [statusMessage, setStatusMessage] = useState<string | null>(null);
|
| 17 | + const [statusVariant, setStatusVariant] = useState<'neutral' | 'success' | 'error'>('neutral'); |
16 | 18 | const [currentVersion, setCurrentVersion] = useState<string>("");
|
| 19 | + const totalBytesRef = useRef<number | null>(null); |
| 20 | + const downloadedBytesRef = useRef(0); |
| 21 | + const initRef = useRef(false); |
17 | 22 |
|
18 | 23 | // Load initial settings and check for updates
|
19 | 24 | useEffect(() => {
|
| 25 | + if (initRef.current) { |
| 26 | + return; |
| 27 | + } |
| 28 | + initRef.current = true; |
| 29 | + |
20 | 30 | const loadSettings = async () => {
|
21 | 31 | try {
|
22 | 32 | console.log("🔄 Frontend: Loading initial settings...");
|
@@ -57,47 +67,88 @@ export function InfoPage() {
|
57 | 67 | console.error("❌ Failed to get app version:", error);
|
58 | 68 | });
|
59 | 69 | // Check for updates 2 seconds after loading to avoid blocking UI
|
60 |
| - setTimeout(checkForUpdatesOnLaunch, 2000); |
61 |
| - }, []); |
| 70 | + if (isDev) { |
| 71 | + setStatusVariant('neutral'); |
| 72 | + setStatusMessage('Auto-updater is disabled in dev builds. Run a packaged app to test updates.'); |
| 73 | + } else { |
| 74 | + setTimeout(checkForUpdatesOnLaunch, 2000); |
| 75 | + } |
| 76 | + }, [isDev]); |
62 | 77 |
|
63 | 78 | const checkForUpdates = async () => {
|
| 79 | + if (isDev) { |
| 80 | + setStatusVariant('neutral'); |
| 81 | + setStatusMessage('Auto-updater is disabled in dev builds. Run a packaged app to test updates.'); |
| 82 | + return; |
| 83 | + } |
| 84 | + |
64 | 85 | try {
|
65 | 86 | setIsCheckingUpdate(true);
|
66 | 87 | const update = await updateManager.checkForUpdates();
|
67 | 88 | setUpdateInfo(update);
|
68 | 89 | if (!update) {
|
69 | 90 | // Show a message that no updates are available
|
70 | 91 | console.log('No updates available');
|
| 92 | + setStatusVariant('success'); |
71 | 93 | setStatusMessage(currentVersion ? `Your Milo version v${currentVersion} is up to date!` : 'Milo is up to date!');
|
72 | 94 | } else {
|
73 | 95 | setStatusMessage(null);
|
74 | 96 | }
|
75 | 97 | } catch (error) {
|
76 | 98 | console.error('Failed to check for updates:', error);
|
| 99 | + setStatusVariant('error'); |
77 | 100 | setStatusMessage('Failed to check for updates. Please try again.');
|
78 | 101 | } finally {
|
79 | 102 | setIsCheckingUpdate(false);
|
80 | 103 | }
|
81 | 104 | };
|
82 | 105 |
|
83 | 106 | const downloadUpdate = async () => {
|
| 107 | + if (isDev) { |
| 108 | + setStatusVariant('neutral'); |
| 109 | + setStatusMessage('Auto-updater is disabled in dev builds. Run a packaged app to test updates.'); |
| 110 | + return; |
| 111 | + } |
| 112 | + |
84 | 113 | if (!updateInfo) return;
|
85 | 114 |
|
86 | 115 | try {
|
87 | 116 | setIsDownloading(true);
|
| 117 | + totalBytesRef.current = null; |
| 118 | + downloadedBytesRef.current = 0; |
| 119 | + setDownloadProgress(0); |
| 120 | + console.log('⬇️ Frontend: Starting update download'); |
88 | 121 | updateManager.setProgressCallback((event) => {
|
| 122 | + console.log('⬇️ Frontend: Download event received', event); |
89 | 123 | if (event.event === 'Started') {
|
| 124 | + totalBytesRef.current = event.data.contentLength ?? null; |
| 125 | + downloadedBytesRef.current = 0; |
90 | 126 | setDownloadProgress(0);
|
91 | 127 | } else if (event.event === 'Progress') {
|
92 |
| - const progress = Math.round((event.data.chunkLength / event.data.contentLength) * 100); |
93 |
| - setDownloadProgress(progress); |
| 128 | + downloadedBytesRef.current += event.data.chunkLength ?? 0; |
| 129 | + if (totalBytesRef.current && totalBytesRef.current > 0) { |
| 130 | + const progress = Math.min(100, Math.round((downloadedBytesRef.current / totalBytesRef.current) * 100)); |
| 131 | + setDownloadProgress(progress); |
| 132 | + } else { |
| 133 | + setDownloadProgress((prev) => (prev < 99 ? prev + 1 : prev)); |
| 134 | + } |
| 135 | + } else if (event.event === 'Finished') { |
| 136 | + setDownloadProgress(100); |
| 137 | + setIsDownloading(false); |
| 138 | + setUpdateInfo(null); |
| 139 | + setStatusVariant('success'); |
| 140 | + setStatusMessage('Update downloaded. Milo will restart once installation finishes.'); |
94 | 141 | }
|
95 | 142 | });
|
96 | 143 |
|
97 | 144 | await updateManager.downloadAndInstall();
|
98 | 145 | } catch (error) {
|
99 | 146 | console.error('Failed to download update:', error);
|
100 | 147 | setIsDownloading(false);
|
| 148 | + setStatusVariant('error'); |
| 149 | + setStatusMessage(`Failed to download update. ${error instanceof Error ? error.message : ''}`.trim()); |
| 150 | + } finally { |
| 151 | + downloadedBytesRef.current = 0; |
101 | 152 | }
|
102 | 153 | };
|
103 | 154 |
|
@@ -207,8 +258,16 @@ export function InfoPage() {
|
207 | 258 | </div>
|
208 | 259 | )}
|
209 | 260 |
|
210 |
| - {statusMessage && !updateInfo && !isDownloading && ( |
211 |
| - <div className="p-3 bg-green-50 dark:bg-green-900/20 border border-green-200 dark:border-green-800 rounded-lg text-xs text-green-800 dark:text-green-200"> |
| 261 | + {statusMessage && ( |
| 262 | + <div |
| 263 | + className={`p-3 rounded-lg text-xs ${ |
| 264 | + statusVariant === 'error' |
| 265 | + ? 'bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800 text-red-800 dark:text-red-200' |
| 266 | + : statusVariant === 'success' |
| 267 | + ? 'bg-green-50 dark:bg-green-900/20 border border-green-200 dark:border-green-800 text-green-800 dark:text-green-200' |
| 268 | + : 'bg-background-secondary/80 border border-border-primary text-text-secondary' |
| 269 | + }`} |
| 270 | + > |
212 | 271 | {statusMessage}
|
213 | 272 | </div>
|
214 | 273 | )}
|
|
0 commit comments