diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index cc65d94..5794073 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -30,6 +30,11 @@ fn greet(name: &str) -> String { format!("Hello, {}! You've been greeted from Rust!", name) } +#[tauri::command] +fn get_app_version(app: tauri::AppHandle) -> String { + app.package_info().version.to_string() +} + #[derive(Clone, serde::Serialize)] struct LogEntry { ts: i64, @@ -103,6 +108,7 @@ pub fn run() { }))) .invoke_handler(tauri::generate_handler![ greet, + get_app_version, get_recent_logs, database::get_platform_info, diff --git a/src/App.vue b/src/App.vue index dbbba97..700d859 100644 --- a/src/App.vue +++ b/src/App.vue @@ -13,6 +13,8 @@ import Login from "./views/Login.vue"; import UserProfileCard from "./components/UserProfileCard.vue"; import CustomTitlebar from "./components/CustomTitlebar.vue"; import WakatimeSetupModal from "./components/WakatimeSetupModal.vue"; +import UpdateNotification from "./components/UpdateNotification.vue"; +import UpdateModal from "./components/UpdateModal.vue"; if (!(window as any).__hackatimeConsoleWrapped) { (window as any).__hackatimeConsoleWrapped = true; @@ -69,6 +71,13 @@ const showWakatimeSetupModal = ref(false); const wakatimeConfigCheck = ref(null); const hasCheckedConfigThisSession = ref(false); +const updateAvailable = ref(false); +const updateVersion = ref(''); +const updateData = ref(null); +const showUpdateModal = ref(false); +const isInstallingUpdate = ref(false); +const currentVersion = ref('1.5.1'); + const weeklyChartData = computed(() => { if (!userStats.value?.weekly_stats?.daily_hours) { @@ -103,6 +112,13 @@ onMounted(async () => { await loadApiConfig(); await loadHackatimeInfo(); + try { + const appVersion = await invoke("get_app_version") as string; + currentVersion.value = appVersion; + } catch (error) { + console.warn("Failed to get app version:", error); + } + isDevMode.value = apiConfig.value.base_url.includes('localhost') || apiConfig.value.base_url.includes('127.0.0.1') || window.location.hostname === 'localhost' || @@ -466,30 +482,9 @@ async function checkForUpdatesAndInstall() { if (update) { console.info(`[AUTO-UPDATE] Update available: ${update.version}`); - console.info('[AUTO-UPDATE] Downloading and installing update...'); - - let downloaded = 0; - let contentLength = 0; - - await update.downloadAndInstall((event) => { - switch (event.event) { - case 'Started': - contentLength = event.data.contentLength ?? 0; - console.info(`[AUTO-UPDATE] Started downloading ${event.data.contentLength} bytes`); - break; - case 'Progress': - downloaded += event.data.chunkLength; - const percentage = contentLength > 0 ? Math.round((downloaded / contentLength) * 100) : 0; - console.info(`[AUTO-UPDATE] Download progress: ${percentage}% (${downloaded} / ${contentLength} bytes)`); - break; - case 'Finished': - console.info('[AUTO-UPDATE] Download finished'); - break; - } - }); - - console.info('[AUTO-UPDATE] Update installed successfully. Restarting app...'); - await relaunch(); + updateData.value = update; + updateVersion.value = update.version; + updateAvailable.value = true; } else { console.info('[AUTO-UPDATE] No updates available - app is up to date'); } @@ -497,6 +492,56 @@ async function checkForUpdatesAndInstall() { console.error('[AUTO-UPDATE] Auto-update check failed:', error); } } + +async function installUpdate() { + if (!updateData.value || isInstallingUpdate.value) return; + + try { + isInstallingUpdate.value = true; + console.info('[AUTO-UPDATE] Downloading and installing update...'); + + let downloaded = 0; + let contentLength = 0; + + await updateData.value.downloadAndInstall((event: any) => { + switch (event.event) { + case 'Started': + contentLength = event.data.contentLength ?? 0; + console.info(`[AUTO-UPDATE] Started downloading ${event.data.contentLength} bytes`); + break; + case 'Progress': + downloaded += event.data.chunkLength; + const percentage = contentLength > 0 ? Math.round((downloaded / contentLength) * 100) : 0; + console.info(`[AUTO-UPDATE] Download progress: ${percentage}% (${downloaded} / ${contentLength} bytes)`); + break; + case 'Finished': + console.info('[AUTO-UPDATE] Download finished'); + break; + } + }); + + console.info('[AUTO-UPDATE] Update installed successfully. Restarting app...'); + await relaunch(); + } catch (error) { + console.error('[AUTO-UPDATE] Update installation failed:', error); + alert('Failed to install update: ' + error); + isInstallingUpdate.value = false; + } +} + +function dismissUpdate() { + updateAvailable.value = false; + showUpdateModal.value = false; +} + +function showMoreInfo() { + showUpdateModal.value = true; +} + +async function handleInstallNow() { + showUpdateModal.value = false; + await installUpdate(); +} + diff --git a/src/components/UpdateNotification.vue b/src/components/UpdateNotification.vue new file mode 100644 index 0000000..b0ebc7e --- /dev/null +++ b/src/components/UpdateNotification.vue @@ -0,0 +1,119 @@ + + + + + +