fix update?
This commit is contained in:
parent
cc235fc47a
commit
80f2c72847
7 changed files with 115 additions and 43 deletions
BIN
bun.lockb
BIN
bun.lockb
Binary file not shown.
|
|
@ -17,7 +17,7 @@
|
||||||
"@tauri-apps/plugin-global-shortcut": "~2",
|
"@tauri-apps/plugin-global-shortcut": "~2",
|
||||||
"@tauri-apps/plugin-opener": "^2",
|
"@tauri-apps/plugin-opener": "^2",
|
||||||
"@tauri-apps/plugin-os": "~2",
|
"@tauri-apps/plugin-os": "~2",
|
||||||
"@tauri-apps/plugin-process": "^2.3.0",
|
"@tauri-apps/plugin-process": "~2",
|
||||||
"@tauri-apps/plugin-store": "~2",
|
"@tauri-apps/plugin-store": "~2",
|
||||||
"@tauri-apps/plugin-stronghold": "~2",
|
"@tauri-apps/plugin-stronghold": "~2",
|
||||||
"@tauri-apps/plugin-updater": "~2",
|
"@tauri-apps/plugin-updater": "~2",
|
||||||
|
|
|
||||||
11
src-tauri/Cargo.lock
generated
11
src-tauri/Cargo.lock
generated
|
|
@ -2006,6 +2006,7 @@ dependencies = [
|
||||||
"tauri-plugin-macos-permissions",
|
"tauri-plugin-macos-permissions",
|
||||||
"tauri-plugin-opener",
|
"tauri-plugin-opener",
|
||||||
"tauri-plugin-os",
|
"tauri-plugin-os",
|
||||||
|
"tauri-plugin-process",
|
||||||
"tauri-plugin-single-instance",
|
"tauri-plugin-single-instance",
|
||||||
"tauri-plugin-store",
|
"tauri-plugin-store",
|
||||||
"tauri-plugin-updater",
|
"tauri-plugin-updater",
|
||||||
|
|
@ -5101,6 +5102,16 @@ dependencies = [
|
||||||
"thiserror 2.0.11",
|
"thiserror 2.0.11",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tauri-plugin-process"
|
||||||
|
version = "2.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7461c622a5ea00eb9cd9f7a08dbd3bf79484499fd5c21aa2964677f64ca651ab"
|
||||||
|
dependencies = [
|
||||||
|
"tauri",
|
||||||
|
"tauri-plugin",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tauri-plugin-single-instance"
|
name = "tauri-plugin-single-instance"
|
||||||
version = "2.2.1"
|
version = "2.2.1"
|
||||||
|
|
|
||||||
|
|
@ -41,6 +41,7 @@ vad-rs = "0.1.5"
|
||||||
tauri-plugin-store = "2"
|
tauri-plugin-store = "2"
|
||||||
tauri-plugin-os = "2"
|
tauri-plugin-os = "2"
|
||||||
enigo = "0.5.0"
|
enigo = "0.5.0"
|
||||||
|
tauri-plugin-process = "2"
|
||||||
|
|
||||||
[target.'cfg(target_os = "macos")'.dependencies]
|
[target.'cfg(target_os = "macos")'.dependencies]
|
||||||
whisper-rs = { version = "0.13.2", features = ["metal"] }
|
whisper-rs = { version = "0.13.2", features = ["metal"] }
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,8 @@
|
||||||
"core:default",
|
"core:default",
|
||||||
"opener:default",
|
"opener:default",
|
||||||
"store:default",
|
"store:default",
|
||||||
|
"updater:default",
|
||||||
|
"process:default",
|
||||||
"global-shortcut:allow-is-registered",
|
"global-shortcut:allow-is-registered",
|
||||||
"global-shortcut:allow-register",
|
"global-shortcut:allow-register",
|
||||||
"global-shortcut:allow-unregister",
|
"global-shortcut:allow-unregister",
|
||||||
|
|
|
||||||
|
|
@ -35,6 +35,7 @@ pub fn run() {
|
||||||
env_logger::init();
|
env_logger::init();
|
||||||
|
|
||||||
tauri::Builder::default()
|
tauri::Builder::default()
|
||||||
|
.plugin(tauri_plugin_process::init())
|
||||||
.plugin(tauri_plugin_updater::Builder::new().build())
|
.plugin(tauri_plugin_updater::Builder::new().build())
|
||||||
.plugin(tauri_plugin_os::init())
|
.plugin(tauri_plugin_os::init())
|
||||||
.plugin(tauri_plugin_clipboard_manager::init())
|
.plugin(tauri_plugin_clipboard_manager::init())
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import React, { useState, useEffect } from "react";
|
import React, { useState, useEffect, useRef } from "react";
|
||||||
import { check } from "@tauri-apps/plugin-updater";
|
import { check } from "@tauri-apps/plugin-updater";
|
||||||
import { relaunch } from "@tauri-apps/plugin-process";
|
import { relaunch } from "@tauri-apps/plugin-process";
|
||||||
import { getVersion } from "@tauri-apps/api/app";
|
import { getVersion } from "@tauri-apps/api/app";
|
||||||
|
|
@ -7,40 +7,43 @@ import { listen } from "@tauri-apps/api/event";
|
||||||
const Footer: React.FC = () => {
|
const Footer: React.FC = () => {
|
||||||
const [isChecking, setIsChecking] = useState(false);
|
const [isChecking, setIsChecking] = useState(false);
|
||||||
const [updateAvailable, setUpdateAvailable] = useState(false);
|
const [updateAvailable, setUpdateAvailable] = useState(false);
|
||||||
const [isUpdating, setIsUpdating] = useState(false);
|
const [isInstalling, setIsInstalling] = useState(false);
|
||||||
|
const [downloadProgress, setDownloadProgress] = useState(0);
|
||||||
const [version, setVersion] = useState("");
|
const [version, setVersion] = useState("");
|
||||||
const [showUpToDate, setShowUpToDate] = useState(false);
|
const [showUpToDate, setShowUpToDate] = useState(false);
|
||||||
const [isManualCheck, setIsManualCheck] = useState(false);
|
|
||||||
|
const upToDateTimeoutRef = useRef<NodeJS.Timeout>();
|
||||||
|
const isManualCheckRef = useRef(false);
|
||||||
|
const downloadedBytesRef = useRef(0);
|
||||||
|
const contentLengthRef = useRef(0);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// Get version from Tauri app info
|
|
||||||
const fetchVersion = async () => {
|
const fetchVersion = async () => {
|
||||||
try {
|
try {
|
||||||
const appVersion = await getVersion();
|
const appVersion = await getVersion();
|
||||||
setVersion(appVersion);
|
setVersion(appVersion);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Failed to get app version:", error);
|
console.error("Failed to get app version:", error);
|
||||||
setVersion("0.1.3"); // fallback version
|
setVersion("0.1.2");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
fetchVersion();
|
fetchVersion();
|
||||||
|
|
||||||
// Automatically check for updates on launch
|
|
||||||
checkForUpdates();
|
checkForUpdates();
|
||||||
|
|
||||||
// Listen for menu-triggered update checks
|
|
||||||
const unlisten = listen("check-for-updates", () => {
|
const unlisten = listen("check-for-updates", () => {
|
||||||
checkForUpdates();
|
handleManualUpdateCheck();
|
||||||
});
|
});
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
|
if (upToDateTimeoutRef.current) {
|
||||||
|
clearTimeout(upToDateTimeoutRef.current);
|
||||||
|
}
|
||||||
unlisten.then((fn) => fn());
|
unlisten.then((fn) => fn());
|
||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const checkForUpdates = async () => {
|
const checkForUpdates = async () => {
|
||||||
// Don't check again if already checking
|
|
||||||
if (isChecking) return;
|
if (isChecking) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
@ -50,50 +53,100 @@ const Footer: React.FC = () => {
|
||||||
if (update) {
|
if (update) {
|
||||||
setUpdateAvailable(true);
|
setUpdateAvailable(true);
|
||||||
setShowUpToDate(false);
|
setShowUpToDate(false);
|
||||||
console.log("Update available:", update.version);
|
|
||||||
} else {
|
} else {
|
||||||
console.log("No updates available");
|
|
||||||
// Reset update available state in case it was previously true
|
|
||||||
setUpdateAvailable(false);
|
setUpdateAvailable(false);
|
||||||
|
|
||||||
// Show "up to date" message only for manual checks
|
if (isManualCheckRef.current) {
|
||||||
if (isManualCheck) {
|
|
||||||
setShowUpToDate(true);
|
setShowUpToDate(true);
|
||||||
// Hide the message after 3 seconds
|
if (upToDateTimeoutRef.current) {
|
||||||
setTimeout(() => setShowUpToDate(false), 3000);
|
clearTimeout(upToDateTimeoutRef.current);
|
||||||
|
}
|
||||||
|
upToDateTimeoutRef.current = setTimeout(() => {
|
||||||
|
setShowUpToDate(false);
|
||||||
|
}, 3000);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Failed to check for updates:", error);
|
console.error("Failed to check for updates:", error);
|
||||||
} finally {
|
} finally {
|
||||||
setIsChecking(false);
|
setIsChecking(false);
|
||||||
setIsManualCheck(false);
|
isManualCheckRef.current = false;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleManualUpdateCheck = () => {
|
const handleManualUpdateCheck = () => {
|
||||||
setIsManualCheck(true);
|
isManualCheckRef.current = true;
|
||||||
checkForUpdates();
|
checkForUpdates();
|
||||||
};
|
};
|
||||||
|
|
||||||
const installUpdate = async () => {
|
const installUpdate = async () => {
|
||||||
try {
|
try {
|
||||||
setIsUpdating(true);
|
setIsInstalling(true);
|
||||||
|
setDownloadProgress(0);
|
||||||
|
downloadedBytesRef.current = 0;
|
||||||
|
contentLengthRef.current = 0;
|
||||||
const update = await check();
|
const update = await check();
|
||||||
|
|
||||||
if (update) {
|
if (!update) {
|
||||||
console.log("Installing update...");
|
console.log("No update available during install attempt");
|
||||||
await update.downloadAndInstall();
|
return;
|
||||||
|
|
||||||
// Restart the app to apply the update
|
|
||||||
await relaunch();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await update.downloadAndInstall((event) => {
|
||||||
|
switch (event.event) {
|
||||||
|
case "Started":
|
||||||
|
downloadedBytesRef.current = 0;
|
||||||
|
contentLengthRef.current = event.data.contentLength ?? 0;
|
||||||
|
break;
|
||||||
|
case "Progress":
|
||||||
|
downloadedBytesRef.current += event.data.chunkLength;
|
||||||
|
const progress =
|
||||||
|
contentLengthRef.current > 0
|
||||||
|
? Math.round(
|
||||||
|
(downloadedBytesRef.current / contentLengthRef.current) *
|
||||||
|
100,
|
||||||
|
)
|
||||||
|
: 0;
|
||||||
|
setDownloadProgress(Math.min(progress, 100));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
await relaunch();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Failed to install update:", error);
|
console.error("Failed to install update:", error);
|
||||||
setIsUpdating(false);
|
} finally {
|
||||||
|
setIsInstalling(false);
|
||||||
|
setDownloadProgress(0);
|
||||||
|
downloadedBytesRef.current = 0;
|
||||||
|
contentLengthRef.current = 0;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getStatusText = () => {
|
||||||
|
if (isInstalling) {
|
||||||
|
return downloadProgress > 0 && downloadProgress < 100
|
||||||
|
? `Downloading... ${downloadProgress.toString().padStart(3)}%`
|
||||||
|
: downloadProgress === 100
|
||||||
|
? "Installing..."
|
||||||
|
: "Preparing...";
|
||||||
|
}
|
||||||
|
if (isChecking) return "Checking...";
|
||||||
|
if (showUpToDate) return "Up to date";
|
||||||
|
if (updateAvailable) return "Update available";
|
||||||
|
return "Check for updates";
|
||||||
|
};
|
||||||
|
|
||||||
|
const getStatusAction = () => {
|
||||||
|
if (updateAvailable && !isInstalling) return installUpdate;
|
||||||
|
if (!isChecking && !isInstalling && !updateAvailable)
|
||||||
|
return handleManualUpdateCheck;
|
||||||
|
return undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
const isDisabled = isChecking || isInstalling;
|
||||||
|
const isClickable =
|
||||||
|
!isDisabled && (updateAvailable || (!isChecking && !showUpToDate));
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="w-full border-t border-mid-gray/20 mt-4 pt-3">
|
<div className="w-full border-t border-mid-gray/20 mt-4 pt-3">
|
||||||
<div className="flex justify-between items-center text-xs px-4 pb-3 text-text/60">
|
<div className="flex justify-between items-center text-xs px-4 pb-3 text-text/60">
|
||||||
|
|
@ -101,25 +154,29 @@ const Footer: React.FC = () => {
|
||||||
<span>v{version}</span>
|
<span>v{version}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-3">
|
||||||
{updateAvailable ? (
|
{isClickable ? (
|
||||||
<button
|
<button
|
||||||
onClick={installUpdate}
|
onClick={getStatusAction()}
|
||||||
disabled={isUpdating}
|
disabled={isDisabled}
|
||||||
className="text-logo-primary hover:text-logo-primary/80 transition-colors disabled:opacity-50 font-medium"
|
className={`transition-colors disabled:opacity-50 tabular-nums ${
|
||||||
|
updateAvailable
|
||||||
|
? "text-logo-primary hover:text-logo-primary/80 font-medium"
|
||||||
|
: "text-text/60 hover:text-text/80"
|
||||||
|
}`}
|
||||||
>
|
>
|
||||||
{isUpdating ? "Installing..." : "Update available"}
|
{getStatusText()}
|
||||||
</button>
|
</button>
|
||||||
) : showUpToDate ? (
|
|
||||||
<span className="text-text/60">Up to date</span>
|
|
||||||
) : (
|
) : (
|
||||||
<button
|
<span className="text-text/60 tabular-nums">{getStatusText()}</span>
|
||||||
onClick={handleManualUpdateCheck}
|
)}
|
||||||
disabled={isChecking}
|
|
||||||
className="text-text/60 hover:text-text/80 transition-colors disabled:opacity-50"
|
{isInstalling && downloadProgress > 0 && downloadProgress < 100 && (
|
||||||
>
|
<progress
|
||||||
{isChecking ? "Checking..." : "Check for updates"}
|
value={downloadProgress}
|
||||||
</button>
|
max={100}
|
||||||
|
className="w-20 h-2 [&::-webkit-progress-bar]:rounded-full [&::-webkit-progress-bar]:bg-mid-gray/20 [&::-webkit-progress-value]:rounded-full [&::-webkit-progress-value]:bg-logo-primary"
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue