mirror of
https://github.com/zhom/donutbrowser.git
synced 2026-06-28 17:39:56 +02:00
Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a816fbb140 | |||
| c954668ed1 | |||
| 2db27b5ffd | |||
| 845e9f28ad | |||
| ee8c6dcc85 |
+3
-1
@@ -2,7 +2,7 @@
|
||||
"name": "donutbrowser",
|
||||
"private": true,
|
||||
"license": "AGPL-3.0",
|
||||
"version": "0.5.1",
|
||||
"version": "0.5.2",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "next dev --turbopack",
|
||||
@@ -20,6 +20,7 @@
|
||||
"format:js": "biome check src/ --fix",
|
||||
"format": "pnpm format:js && pnpm format:rust",
|
||||
"cargo": "cd src-tauri && cargo",
|
||||
"unused-exports:js": "ts-unused-exports tsconfig.json",
|
||||
"check-unused-commands": "cd src-tauri && cargo run --bin check_unused_commands"
|
||||
},
|
||||
"dependencies": {
|
||||
@@ -71,6 +72,7 @@
|
||||
"husky": "^9.1.7",
|
||||
"lint-staged": "^16.1.1",
|
||||
"tailwindcss": "^4.1.10",
|
||||
"ts-unused-exports": "^11.0.1",
|
||||
"tw-animate-css": "^1.3.4",
|
||||
"typescript": "~5.8.3",
|
||||
"typescript-eslint": "^8.34.0"
|
||||
|
||||
Generated
+15
@@ -147,6 +147,9 @@ importers:
|
||||
tailwindcss:
|
||||
specifier: ^4.1.10
|
||||
version: 4.1.10
|
||||
ts-unused-exports:
|
||||
specifier: ^11.0.1
|
||||
version: 11.0.1(typescript@5.8.3)
|
||||
tw-animate-css:
|
||||
specifier: ^1.3.4
|
||||
version: 1.3.4
|
||||
@@ -3437,6 +3440,12 @@ packages:
|
||||
'@swc/wasm':
|
||||
optional: true
|
||||
|
||||
ts-unused-exports@11.0.1:
|
||||
resolution: {integrity: sha512-b1uIe0B8YfNZjeb+bx62LrB6qaO4CHT8SqMVBkwbwLj7Nh0xQ4J8uV0dS9E6AABId0U4LQ+3yB/HXZBMslGn2A==}
|
||||
hasBin: true
|
||||
peerDependencies:
|
||||
typescript: '>=3.8.3'
|
||||
|
||||
tsconfig-paths@3.15.0:
|
||||
resolution: {integrity: sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==}
|
||||
|
||||
@@ -7022,6 +7031,12 @@ snapshots:
|
||||
v8-compile-cache-lib: 3.0.1
|
||||
yn: 3.1.1
|
||||
|
||||
ts-unused-exports@11.0.1(typescript@5.8.3):
|
||||
dependencies:
|
||||
chalk: 4.1.2
|
||||
tsconfig-paths: 3.15.0
|
||||
typescript: 5.8.3
|
||||
|
||||
tsconfig-paths@3.15.0:
|
||||
dependencies:
|
||||
'@types/json5': 0.0.29
|
||||
|
||||
Generated
+1
-1
@@ -993,7 +993,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "donutbrowser"
|
||||
version = "0.5.1"
|
||||
version = "0.5.2"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"base64 0.22.1",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "donutbrowser"
|
||||
version = "0.5.1"
|
||||
version = "0.5.2"
|
||||
description = "Simple Yet Powerful Browser Orchestrator"
|
||||
authors = ["zhom@github"]
|
||||
edition = "2021"
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use crate::api_client::is_browser_version_nightly;
|
||||
use crate::browser_runner::{BrowserProfile, BrowserRunner};
|
||||
use crate::browser_version_service::{BrowserVersionInfo, BrowserVersionService};
|
||||
use crate::settings_manager::SettingsManager;
|
||||
@@ -101,16 +102,19 @@ impl AutoUpdater {
|
||||
// Apply chromium threshold logic
|
||||
if browser == "chromium" {
|
||||
// For chromium, only show notifications if there are 50+ new versions
|
||||
// Count how many versions are newer than the current profile version
|
||||
let newer_versions_count = versions
|
||||
.iter()
|
||||
.filter(|v| self.is_version_newer(&v.version, &profile.version))
|
||||
.count();
|
||||
let current_version = &profile.version.parse::<u32>().unwrap();
|
||||
let new_version = &update.new_version.parse::<u32>().unwrap();
|
||||
|
||||
if newer_versions_count >= 50 {
|
||||
let result = new_version - current_version;
|
||||
println!(
|
||||
"Current version: {current_version}, New version: {new_version}, Result: {result}"
|
||||
);
|
||||
if result > 50 {
|
||||
notifications.push(update);
|
||||
} else {
|
||||
println!("Skipping chromium update notification: only {newer_versions_count} new versions (need 50+)");
|
||||
println!(
|
||||
"Skipping chromium update notification: only {result} new versions (need 50+)"
|
||||
);
|
||||
}
|
||||
} else {
|
||||
notifications.push(update);
|
||||
@@ -123,6 +127,8 @@ impl AutoUpdater {
|
||||
}
|
||||
|
||||
pub async fn check_for_updates_with_progress(&self, app_handle: &tauri::AppHandle) {
|
||||
println!("Starting auto-update check with progress...");
|
||||
|
||||
// Check for browser updates and trigger auto-downloads
|
||||
match self.check_for_updates().await {
|
||||
Ok(update_notifications) => {
|
||||
@@ -139,22 +145,63 @@ impl AutoUpdater {
|
||||
notification.browser, notification.new_version
|
||||
);
|
||||
|
||||
// Emit a custom event to trigger auto-download
|
||||
let auto_update_event = serde_json::json!({
|
||||
"browser": notification.browser,
|
||||
"new_version": notification.new_version,
|
||||
"notification_id": notification.id,
|
||||
"affected_profiles": notification.affected_profiles
|
||||
});
|
||||
// Clone app_handle for the async task
|
||||
let app_handle_clone = app_handle.clone();
|
||||
let browser = notification.browser.clone();
|
||||
let new_version = notification.new_version.clone();
|
||||
let notification_id = notification.id.clone();
|
||||
let affected_profiles = notification.affected_profiles.clone();
|
||||
|
||||
if let Err(e) = app_handle.emit("browser-auto-update-available", &auto_update_event) {
|
||||
eprintln!(
|
||||
"Failed to emit auto-update event for {}: {e}",
|
||||
notification.browser
|
||||
);
|
||||
} else {
|
||||
println!("Emitted auto-update event for {}", notification.browser);
|
||||
}
|
||||
// Spawn async task to handle the download and auto-update
|
||||
tokio::spawn(async move {
|
||||
// First, check if browser already exists
|
||||
match crate::browser_runner::is_browser_downloaded(
|
||||
browser.clone(),
|
||||
new_version.clone(),
|
||||
) {
|
||||
true => {
|
||||
println!("Browser {browser} {new_version} already downloaded, proceeding to auto-update profiles");
|
||||
|
||||
// Browser already exists, go straight to profile update
|
||||
match crate::auto_updater::complete_browser_update_with_auto_update(
|
||||
browser.clone(),
|
||||
new_version.clone(),
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(updated_profiles) => {
|
||||
println!(
|
||||
"Auto-update completed for {} profiles: {:?}",
|
||||
updated_profiles.len(),
|
||||
updated_profiles
|
||||
);
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("Failed to complete auto-update for {browser}: {e}");
|
||||
}
|
||||
}
|
||||
}
|
||||
false => {
|
||||
println!("Downloading browser {browser} version {new_version}...");
|
||||
|
||||
// Emit the auto-update event to trigger frontend handling
|
||||
let auto_update_event = serde_json::json!({
|
||||
"browser": browser,
|
||||
"new_version": new_version,
|
||||
"notification_id": notification_id,
|
||||
"affected_profiles": affected_profiles
|
||||
});
|
||||
|
||||
if let Err(e) =
|
||||
app_handle_clone.emit("browser-auto-update-available", &auto_update_event)
|
||||
{
|
||||
eprintln!("Failed to emit auto-update event for {browser}: {e}");
|
||||
} else {
|
||||
println!("Emitted auto-update event for {browser}");
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
} else {
|
||||
println!("No browser updates needed");
|
||||
@@ -173,16 +220,15 @@ impl AutoUpdater {
|
||||
available_versions: &[BrowserVersionInfo],
|
||||
) -> Result<Option<UpdateNotification>, Box<dyn std::error::Error + Send + Sync>> {
|
||||
let current_version = &profile.version;
|
||||
let is_current_stable = !self.is_nightly_version(current_version);
|
||||
let is_current_nightly = is_browser_version_nightly(&profile.browser, current_version, None);
|
||||
|
||||
// Find the best available update
|
||||
let best_update = available_versions
|
||||
.iter()
|
||||
.filter(|v| {
|
||||
// Only consider versions newer than current
|
||||
self.is_version_newer(&v.version, current_version) &&
|
||||
// Respect version type preference
|
||||
is_current_stable != v.is_prerelease
|
||||
self.is_version_newer(&v.version, current_version)
|
||||
&& is_browser_version_nightly(&profile.browser, &v.version, None) == is_current_nightly
|
||||
})
|
||||
.max_by(|a, b| self.compare_versions(&a.version, &b.version));
|
||||
|
||||
@@ -367,14 +413,6 @@ impl AutoUpdater {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Helper methods
|
||||
|
||||
fn is_nightly_version(&self, version: &str) -> bool {
|
||||
// Use the centralized nightly detection function
|
||||
// Since we don't have browser context here, use the general fallback
|
||||
crate::api_client::is_nightly_version(version)
|
||||
}
|
||||
|
||||
fn is_version_newer(&self, version1: &str, version2: &str) -> bool {
|
||||
self.compare_versions(version1, version2) == std::cmp::Ordering::Greater
|
||||
}
|
||||
@@ -502,21 +540,6 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_is_nightly_version() {
|
||||
let updater = AutoUpdater::new();
|
||||
|
||||
assert!(updater.is_nightly_version("1.0.0-alpha"));
|
||||
assert!(updater.is_nightly_version("1.0.0-beta"));
|
||||
assert!(updater.is_nightly_version("1.0.0-rc"));
|
||||
assert!(updater.is_nightly_version("1.0.0a1"));
|
||||
assert!(updater.is_nightly_version("1.0.0b1"));
|
||||
assert!(updater.is_nightly_version("1.0.0-dev"));
|
||||
|
||||
assert!(!updater.is_nightly_version("1.0.0"));
|
||||
assert!(!updater.is_nightly_version("1.2.3"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_compare_versions() {
|
||||
let updater = AutoUpdater::new();
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
use base64::{engine::general_purpose, Engine as _};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fs;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
@@ -56,7 +54,7 @@ pub trait Browser: Send + Sync {
|
||||
fn create_launch_args(
|
||||
&self,
|
||||
profile_path: &str,
|
||||
_proxy_settings: Option<&ProxySettings>,
|
||||
proxy_settings: Option<&ProxySettings>,
|
||||
url: Option<String>,
|
||||
) -> Result<Vec<String>, Box<dyn std::error::Error>>;
|
||||
fn is_version_downloaded(&self, version: &str, binaries_dir: &Path) -> bool;
|
||||
@@ -639,14 +637,10 @@ impl Browser for ChromiumBrowser {
|
||||
// Add proxy configuration if provided
|
||||
if let Some(proxy) = proxy_settings {
|
||||
if proxy.enabled {
|
||||
let pac_path = Path::new(profile_path).join("proxy.pac");
|
||||
if pac_path.exists() {
|
||||
let pac_content = fs::read(&pac_path)?;
|
||||
let pac_base64 = general_purpose::STANDARD.encode(&pac_content);
|
||||
args.push(format!(
|
||||
"--proxy-pac-url=data:application/x-javascript-config;base64,{pac_base64}"
|
||||
));
|
||||
}
|
||||
args.push(format!(
|
||||
"--proxy-server=http://{}:{}",
|
||||
proxy.host, proxy.port
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1454,6 +1454,7 @@ impl BrowserRunner {
|
||||
&self,
|
||||
profile: &BrowserProfile,
|
||||
url: Option<String>,
|
||||
local_proxy_settings: Option<&ProxySettings>,
|
||||
) -> Result<BrowserProfile, Box<dyn std::error::Error + Send + Sync>> {
|
||||
// Create browser instance
|
||||
let browser_type = BrowserType::from_str(&profile.browser)
|
||||
@@ -1465,6 +1466,7 @@ impl BrowserRunner {
|
||||
browser_dir.push(&profile.browser);
|
||||
browser_dir.push(&profile.version);
|
||||
|
||||
println!("Browser directory: {browser_dir:?}");
|
||||
let executable_path = browser
|
||||
.get_executable_path(&browser_dir)
|
||||
.expect("Failed to get executable path");
|
||||
@@ -1475,9 +1477,16 @@ impl BrowserRunner {
|
||||
// Continue anyway, the error might not be critical
|
||||
}
|
||||
|
||||
// Get launch arguments (proxy settings will be handled later if needed)
|
||||
// For Chromium browsers, use local proxy settings if available
|
||||
// For Firefox browsers, continue using original proxy settings (handled via PAC files)
|
||||
let proxy_for_launch_args = match browser_type {
|
||||
BrowserType::Chromium | BrowserType::Brave => local_proxy_settings.or(profile.proxy.as_ref()),
|
||||
_ => profile.proxy.as_ref(),
|
||||
};
|
||||
|
||||
// Get launch arguments
|
||||
let browser_args = browser
|
||||
.create_launch_args(&profile.profile_path, None, url)
|
||||
.create_launch_args(&profile.profile_path, proxy_for_launch_args, url)
|
||||
.expect("Failed to create launch arguments");
|
||||
|
||||
// Launch browser using platform-specific method
|
||||
@@ -1605,6 +1614,7 @@ impl BrowserRunner {
|
||||
app_handle: tauri::AppHandle,
|
||||
profile: &BrowserProfile,
|
||||
url: &str,
|
||||
_internal_proxy_settings: Option<&ProxySettings>,
|
||||
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||
// Use the comprehensive browser status check
|
||||
let is_running = self.check_browser_status(app_handle, profile).await?;
|
||||
@@ -1735,6 +1745,7 @@ impl BrowserRunner {
|
||||
app_handle: tauri::AppHandle,
|
||||
profile: &BrowserProfile,
|
||||
url: Option<String>,
|
||||
internal_proxy_settings: Option<&ProxySettings>,
|
||||
) -> Result<BrowserProfile, Box<dyn std::error::Error + Send + Sync>> {
|
||||
// Get the most up-to-date profile data
|
||||
let profiles = self.list_profiles().expect("Failed to list profiles");
|
||||
@@ -1779,7 +1790,12 @@ impl BrowserRunner {
|
||||
}
|
||||
}
|
||||
match self
|
||||
.open_url_in_existing_browser(app_handle, &final_profile, url_ref)
|
||||
.open_url_in_existing_browser(
|
||||
app_handle,
|
||||
&final_profile,
|
||||
url_ref,
|
||||
internal_proxy_settings,
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(()) => {
|
||||
@@ -1804,7 +1820,7 @@ impl BrowserRunner {
|
||||
final_profile.browser
|
||||
);
|
||||
// Fallback to launching a new instance for other browsers
|
||||
self.launch_browser(&final_profile, url).await
|
||||
self.launch_browser(&final_profile, url, internal_proxy_settings).await
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1812,7 +1828,9 @@ impl BrowserRunner {
|
||||
} else {
|
||||
// This case shouldn't happen since we checked is_some() above, but handle it gracefully
|
||||
println!("URL was unexpectedly None, launching new browser instance");
|
||||
self.launch_browser(&final_profile, url).await
|
||||
self
|
||||
.launch_browser(&final_profile, url, internal_proxy_settings)
|
||||
.await
|
||||
}
|
||||
} else {
|
||||
// Browser is not running or no URL provided, launch new instance
|
||||
@@ -1821,7 +1839,9 @@ impl BrowserRunner {
|
||||
} else {
|
||||
println!("Launching new browser instance - no URL provided");
|
||||
}
|
||||
self.launch_browser(&final_profile, url).await
|
||||
self
|
||||
.launch_browser(&final_profile, url, internal_proxy_settings)
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2241,6 +2261,9 @@ pub async fn launch_browser_profile(
|
||||
) -> Result<BrowserProfile, String> {
|
||||
let browser_runner = BrowserRunner::new();
|
||||
|
||||
// Store the internal proxy settings for passing to launch_browser
|
||||
let mut internal_proxy_settings: Option<ProxySettings> = None;
|
||||
|
||||
// If the profile has proxy settings, we need to start the proxy first
|
||||
// and update the profile with proxy settings before launching
|
||||
let profile_for_launch = profile.clone();
|
||||
@@ -2254,14 +2277,17 @@ pub async fn launch_browser_profile(
|
||||
.start_proxy(app_handle.clone(), proxy, temp_pid, Some(&profile.name))
|
||||
.await
|
||||
{
|
||||
Ok(internal_proxy_settings) => {
|
||||
Ok(internal_proxy) => {
|
||||
let browser_runner = BrowserRunner::new();
|
||||
let profiles_dir = browser_runner.get_profiles_dir();
|
||||
let profile_path = profiles_dir.join(profile.name.to_lowercase().replace(" ", "_"));
|
||||
|
||||
// Store the internal proxy settings for later use
|
||||
internal_proxy_settings = Some(internal_proxy.clone());
|
||||
|
||||
// Apply the proxy settings with the internal proxy to the profile directory
|
||||
browser_runner
|
||||
.apply_proxy_settings_to_profile(&profile_path, proxy, Some(&internal_proxy_settings))
|
||||
.apply_proxy_settings_to_profile(&profile_path, proxy, Some(&internal_proxy))
|
||||
.map_err(|e| format!("Failed to update profile proxy: {e}"))?;
|
||||
|
||||
println!("Successfully started proxy for profile: {}", profile.name);
|
||||
@@ -2287,7 +2313,7 @@ pub async fn launch_browser_profile(
|
||||
|
||||
// Launch browser or open URL in existing instance
|
||||
let updated_profile = browser_runner
|
||||
.launch_or_open_url(app_handle.clone(), &profile_for_launch, url)
|
||||
.launch_or_open_url(app_handle.clone(), &profile_for_launch, url, internal_proxy_settings.as_ref())
|
||||
.await
|
||||
.map_err(|e| {
|
||||
// Check if this is an architecture compatibility issue
|
||||
|
||||
@@ -535,7 +535,7 @@ pub async fn open_url_with_profile(
|
||||
|
||||
// Use launch_or_open_url which handles both launching new instances and opening in existing ones
|
||||
runner
|
||||
.launch_or_open_url(app_handle, &profile, Some(url.clone()))
|
||||
.launch_or_open_url(app_handle, &profile, Some(url.clone()), None)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
println!("Failed to open URL with profile '{profile_name}': {e}");
|
||||
@@ -612,7 +612,7 @@ pub async fn smart_open_url(
|
||||
|
||||
// Try to open the URL with this running profile
|
||||
match runner
|
||||
.launch_or_open_url(app_handle.clone(), profile, Some(url.clone()))
|
||||
.launch_or_open_url(app_handle.clone(), profile, Some(url.clone()), None)
|
||||
.await
|
||||
{
|
||||
Ok(_) => {
|
||||
|
||||
@@ -263,149 +263,108 @@ impl VersionUpdater {
|
||||
&self,
|
||||
app_handle: &tauri::AppHandle,
|
||||
) -> Result<Vec<BackgroundUpdateResult>, Box<dyn std::error::Error + Send + Sync>> {
|
||||
println!("Starting background version update for all browsers");
|
||||
|
||||
let all_browsers = [
|
||||
"firefox",
|
||||
"firefox-developer",
|
||||
"mullvad-browser",
|
||||
"zen",
|
||||
"brave",
|
||||
"chromium",
|
||||
"tor-browser",
|
||||
];
|
||||
|
||||
// Filter browsers to only include those supported on the current platform
|
||||
let browsers: Vec<&str> = all_browsers
|
||||
.iter()
|
||||
.filter(|browser| {
|
||||
self
|
||||
.version_service
|
||||
.is_browser_supported(browser)
|
||||
.unwrap_or(false)
|
||||
})
|
||||
.copied()
|
||||
.collect();
|
||||
|
||||
let total_browsers = browsers.len();
|
||||
let supported_browsers = self.version_service.get_supported_browsers();
|
||||
let total_browsers = supported_browsers.len();
|
||||
let mut results = Vec::new();
|
||||
let mut total_new_versions = 0;
|
||||
|
||||
println!(
|
||||
"Updating {} supported browsers (filtered from {} total)",
|
||||
browsers.len(),
|
||||
all_browsers.len()
|
||||
);
|
||||
|
||||
// Emit start event
|
||||
let progress = VersionUpdateProgress {
|
||||
current_browser: "".to_string(),
|
||||
// Emit initial progress
|
||||
let initial_progress = VersionUpdateProgress {
|
||||
current_browser: String::new(),
|
||||
total_browsers,
|
||||
completed_browsers: 0,
|
||||
new_versions_found: 0,
|
||||
browser_new_versions: 0,
|
||||
status: "updating".to_string(),
|
||||
};
|
||||
if let Err(e) = app_handle.emit("version-update-progress", &progress) {
|
||||
eprintln!("Failed to emit start progress: {e}");
|
||||
} else {
|
||||
println!("Emitted start progress event");
|
||||
|
||||
if let Err(e) = app_handle.emit("version-update-progress", &initial_progress) {
|
||||
eprintln!("Failed to emit initial progress: {e}");
|
||||
}
|
||||
|
||||
for (index, browser) in browsers.iter().enumerate() {
|
||||
println!(
|
||||
"Processing browser {} ({}/{}): {}",
|
||||
browser,
|
||||
index + 1,
|
||||
total_browsers,
|
||||
browser
|
||||
);
|
||||
for (index, browser) in supported_browsers.iter().enumerate() {
|
||||
println!("Updating browser versions for: {browser}");
|
||||
|
||||
// Emit progress for current browser
|
||||
// Emit progress update for current browser
|
||||
let progress = VersionUpdateProgress {
|
||||
current_browser: browser.to_string(),
|
||||
current_browser: browser.clone(),
|
||||
total_browsers,
|
||||
completed_browsers: index,
|
||||
new_versions_found: total_new_versions,
|
||||
browser_new_versions: 0,
|
||||
status: "updating".to_string(),
|
||||
};
|
||||
|
||||
if let Err(e) = app_handle.emit("version-update-progress", &progress) {
|
||||
eprintln!("Failed to emit progress for {browser}: {e}");
|
||||
} else {
|
||||
println!("Emitted progress event for browser: {browser}");
|
||||
}
|
||||
|
||||
if !self.version_service.should_update_cache(browser) {
|
||||
println!("Skipping {browser} - cache is still fresh");
|
||||
|
||||
let browser_result = BackgroundUpdateResult {
|
||||
browser: browser.to_string(),
|
||||
new_versions_count: 0,
|
||||
total_versions_count: 0,
|
||||
updated_successfully: true,
|
||||
error: None,
|
||||
};
|
||||
results.push(browser_result);
|
||||
continue;
|
||||
}
|
||||
|
||||
println!("Fetching new versions for browser: {browser}");
|
||||
|
||||
let result = self.update_browser_versions(browser).await;
|
||||
|
||||
match result {
|
||||
Ok(new_count) => {
|
||||
total_new_versions += new_count;
|
||||
let browser_result = BackgroundUpdateResult {
|
||||
browser: browser.to_string(),
|
||||
new_versions_count: new_count,
|
||||
total_versions_count: 0, // We'll update this if needed
|
||||
match self.update_browser_versions(browser).await {
|
||||
Ok(new_versions_count) => {
|
||||
results.push(BackgroundUpdateResult {
|
||||
browser: browser.clone(),
|
||||
new_versions_count,
|
||||
total_versions_count: 0, // We don't track total for background updates
|
||||
updated_successfully: true,
|
||||
error: None,
|
||||
};
|
||||
results.push(browser_result);
|
||||
});
|
||||
|
||||
println!("Found {new_count} new versions for {browser}");
|
||||
total_new_versions += new_versions_count;
|
||||
|
||||
// Emit progress update with new versions found
|
||||
let progress = VersionUpdateProgress {
|
||||
current_browser: browser.clone(),
|
||||
total_browsers,
|
||||
completed_browsers: index,
|
||||
new_versions_found: total_new_versions,
|
||||
browser_new_versions: new_versions_count,
|
||||
status: "updating".to_string(),
|
||||
};
|
||||
|
||||
if let Err(e) = app_handle.emit("version-update-progress", &progress) {
|
||||
eprintln!("Failed to emit progress with versions for {browser}: {e}");
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("Failed to update versions for {browser}: {e}");
|
||||
let browser_result = BackgroundUpdateResult {
|
||||
browser: browser.to_string(),
|
||||
results.push(BackgroundUpdateResult {
|
||||
browser: browser.clone(),
|
||||
new_versions_count: 0,
|
||||
total_versions_count: 0,
|
||||
updated_successfully: false,
|
||||
error: Some(e.to_string()),
|
||||
};
|
||||
results.push(browser_result);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Small delay between browsers to avoid overwhelming APIs
|
||||
tokio::time::sleep(Duration::from_millis(200)).await;
|
||||
}
|
||||
|
||||
self
|
||||
.auto_updater
|
||||
.check_for_updates_with_progress(app_handle)
|
||||
.await;
|
||||
|
||||
// Emit completion event
|
||||
let progress = VersionUpdateProgress {
|
||||
current_browser: "".to_string(),
|
||||
// Emit completion
|
||||
let final_progress = VersionUpdateProgress {
|
||||
current_browser: String::new(),
|
||||
total_browsers,
|
||||
completed_browsers: total_browsers,
|
||||
new_versions_found: total_new_versions,
|
||||
browser_new_versions: 0,
|
||||
status: "completed".to_string(),
|
||||
};
|
||||
if let Err(e) = app_handle.emit("version-update-progress", &progress) {
|
||||
|
||||
if let Err(e) = app_handle.emit("version-update-progress", &final_progress) {
|
||||
eprintln!("Failed to emit completion progress: {e}");
|
||||
} else {
|
||||
println!("Emitted completion progress event");
|
||||
}
|
||||
|
||||
println!("Version update completed. Found {total_new_versions} new versions total");
|
||||
// After all version updates are complete, trigger auto-update check
|
||||
if total_new_versions > 0 {
|
||||
println!(
|
||||
"Found {total_new_versions} new versions across all browsers. Checking for auto-updates..."
|
||||
);
|
||||
|
||||
// Trigger auto-update check which will automatically download browsers
|
||||
self
|
||||
.auto_updater
|
||||
.check_for_updates_with_progress(app_handle)
|
||||
.await;
|
||||
} else {
|
||||
println!("No new versions found, skipping auto-update check");
|
||||
}
|
||||
|
||||
Ok(results)
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://schema.tauri.app/config/2",
|
||||
"productName": "Donut Browser",
|
||||
"version": "0.5.1",
|
||||
"version": "0.5.2",
|
||||
"identifier": "com.donutbrowser",
|
||||
"build": {
|
||||
"beforeDevCommand": "pnpm dev",
|
||||
|
||||
@@ -164,13 +164,60 @@ export function ChangeVersionDialog({
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{!releaseTypes.stable || !releaseTypes.nightly ? (
|
||||
{!releaseTypes.stable && !releaseTypes.nightly ? (
|
||||
<Alert>
|
||||
<AlertDescription>
|
||||
Only {profile.release_type} releases are available for{" "}
|
||||
No releases are available for{" "}
|
||||
{getBrowserDisplayName(profile.browser)}.
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
) : !releaseTypes.stable || !releaseTypes.nightly ? (
|
||||
<div className="space-y-4">
|
||||
<Alert>
|
||||
<AlertDescription>
|
||||
Only {profile.release_type} releases are available for{" "}
|
||||
{getBrowserDisplayName(profile.browser)}.
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
<div className="grid gap-2">
|
||||
<Label>New Release Type</Label>
|
||||
{isLoadingReleaseTypes ? (
|
||||
<div className="text-sm text-muted-foreground">
|
||||
Loading release types...
|
||||
</div>
|
||||
) : (
|
||||
<div className="space-y-4">
|
||||
{selectedReleaseType &&
|
||||
selectedReleaseType !== profile.release_type &&
|
||||
selectedVersion &&
|
||||
!isVersionDownloaded(selectedVersion) && (
|
||||
<Alert>
|
||||
<AlertDescription>
|
||||
You must download{" "}
|
||||
{getBrowserDisplayName(profile.browser)}{" "}
|
||||
{selectedVersion} before switching to this release
|
||||
type. Use the download button above to get the
|
||||
latest version.
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
<ReleaseTypeSelector
|
||||
selectedReleaseType={selectedReleaseType}
|
||||
onReleaseTypeSelect={setSelectedReleaseType}
|
||||
availableReleaseTypes={releaseTypes}
|
||||
browser={profile.browser}
|
||||
isDownloading={isDownloading}
|
||||
onDownload={() => {
|
||||
void handleDownload();
|
||||
}}
|
||||
placeholder="Select release type..."
|
||||
downloadedVersions={downloadedVersions}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="grid gap-2">
|
||||
<Label>New Release Type</Label>
|
||||
@@ -179,18 +226,35 @@ export function ChangeVersionDialog({
|
||||
Loading release types...
|
||||
</div>
|
||||
) : (
|
||||
<ReleaseTypeSelector
|
||||
selectedReleaseType={selectedReleaseType}
|
||||
onReleaseTypeSelect={setSelectedReleaseType}
|
||||
availableReleaseTypes={releaseTypes}
|
||||
browser={profile.browser}
|
||||
isDownloading={isDownloading}
|
||||
onDownload={() => {
|
||||
void handleDownload();
|
||||
}}
|
||||
placeholder="Select release type..."
|
||||
downloadedVersions={downloadedVersions}
|
||||
/>
|
||||
<div className="space-y-4">
|
||||
{selectedReleaseType &&
|
||||
selectedReleaseType !== profile.release_type &&
|
||||
selectedVersion &&
|
||||
!isVersionDownloaded(selectedVersion) && (
|
||||
<Alert>
|
||||
<AlertDescription>
|
||||
You must download{" "}
|
||||
{getBrowserDisplayName(profile.browser)}{" "}
|
||||
{selectedVersion} before switching to this release
|
||||
type. Use the download button above to get the latest
|
||||
version.
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
<ReleaseTypeSelector
|
||||
selectedReleaseType={selectedReleaseType}
|
||||
onReleaseTypeSelect={setSelectedReleaseType}
|
||||
availableReleaseTypes={releaseTypes}
|
||||
browser={profile.browser}
|
||||
isDownloading={isDownloading}
|
||||
onDownload={() => {
|
||||
void handleDownload();
|
||||
}}
|
||||
placeholder="Select release type..."
|
||||
downloadedVersions={downloadedVersions}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -93,6 +93,7 @@ export function CreateProfileDialog({
|
||||
isDownloading,
|
||||
downloadedVersions,
|
||||
loadDownloadedVersions,
|
||||
isVersionDownloaded,
|
||||
} = useBrowserDownload();
|
||||
|
||||
const {
|
||||
@@ -280,6 +281,7 @@ export function CreateProfileDialog({
|
||||
selectedBrowser &&
|
||||
selectedReleaseType &&
|
||||
selectedVersion &&
|
||||
isVersionDownloaded(selectedVersion) &&
|
||||
(!proxyEnabled || isProxyDisabled || (proxyHost && proxyPort)) &&
|
||||
!nameError;
|
||||
|
||||
@@ -370,37 +372,48 @@ export function CreateProfileDialog({
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
{selectedBrowser &&
|
||||
(!releaseTypes.stable || !releaseTypes.nightly) ? (
|
||||
<Alert>
|
||||
<AlertDescription>
|
||||
Only {(releaseTypes.stable && "Stable") ?? "Nightly"} releases
|
||||
are available for {getBrowserDisplayName(selectedBrowser)}.
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
) : (
|
||||
{selectedBrowser ? (
|
||||
<div className="grid gap-2">
|
||||
<Label>Release Type</Label>
|
||||
{isLoadingReleaseTypes ? (
|
||||
<div className="text-sm text-muted-foreground">
|
||||
Loading release types...
|
||||
</div>
|
||||
) : Object.keys(releaseTypes).length === 0 ? (
|
||||
<Alert>
|
||||
<AlertDescription>
|
||||
No releases are available for{" "}
|
||||
{getBrowserDisplayName(selectedBrowser)}.
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
) : (
|
||||
<ReleaseTypeSelector
|
||||
selectedReleaseType={selectedReleaseType}
|
||||
onReleaseTypeSelect={setSelectedReleaseType}
|
||||
availableReleaseTypes={releaseTypes}
|
||||
browser={selectedBrowser ?? ""}
|
||||
isDownloading={isDownloading}
|
||||
onDownload={() => {
|
||||
void handleDownload();
|
||||
}}
|
||||
placeholder="Select release type..."
|
||||
downloadedVersions={downloadedVersions}
|
||||
/>
|
||||
<div className="space-y-4">
|
||||
{(!releaseTypes.stable || !releaseTypes.nightly) && (
|
||||
<Alert>
|
||||
<AlertDescription>
|
||||
Only {(releaseTypes.stable && "Stable") ?? "Nightly"}{" "}
|
||||
releases are available for{" "}
|
||||
{getBrowserDisplayName(selectedBrowser)}.
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
<ReleaseTypeSelector
|
||||
selectedReleaseType={selectedReleaseType}
|
||||
onReleaseTypeSelect={setSelectedReleaseType}
|
||||
availableReleaseTypes={releaseTypes}
|
||||
browser={selectedBrowser}
|
||||
isDownloading={isDownloading}
|
||||
onDownload={() => {
|
||||
void handleDownload();
|
||||
}}
|
||||
placeholder="Select release type..."
|
||||
downloadedVersions={downloadedVersions}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
) : null}
|
||||
|
||||
{/* Proxy Settings */}
|
||||
<div className="grid gap-4 pt-4 border-t">
|
||||
|
||||
@@ -1,107 +0,0 @@
|
||||
"use client";
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-misused-promises */
|
||||
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { getBrowserDisplayName } from "@/lib/browser-utils";
|
||||
import React from "react";
|
||||
import { FaDownload, FaTimes } from "react-icons/fa";
|
||||
|
||||
interface UpdateNotification {
|
||||
id: string;
|
||||
browser: string;
|
||||
current_version: string;
|
||||
new_version: string;
|
||||
affected_profiles: string[];
|
||||
is_stable_update: boolean;
|
||||
timestamp: number;
|
||||
}
|
||||
|
||||
interface UpdateNotificationProps {
|
||||
notification: UpdateNotification;
|
||||
onUpdate: (browser: string, newVersion: string) => Promise<void>;
|
||||
onDismiss: (notificationId: string) => Promise<void>;
|
||||
isUpdating?: boolean;
|
||||
}
|
||||
|
||||
export function UpdateNotificationComponent({
|
||||
notification,
|
||||
onUpdate,
|
||||
onDismiss,
|
||||
isUpdating = false,
|
||||
}: UpdateNotificationProps) {
|
||||
const browserDisplayName = getBrowserDisplayName(notification.browser);
|
||||
|
||||
const profileText =
|
||||
notification.affected_profiles.length === 1
|
||||
? `profile "${notification.affected_profiles[0]}"`
|
||||
: `${notification.affected_profiles.length} profiles`;
|
||||
|
||||
const handleUpdateClick = async () => {
|
||||
// Dismiss the notification immediately to close the modal
|
||||
await onDismiss(notification.id);
|
||||
// Then start the update process
|
||||
await onUpdate(notification.browser, notification.new_version);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-3 p-4 max-w-md rounded-lg border shadow-lg bg-background border-border">
|
||||
<div className="flex gap-2 justify-between items-start">
|
||||
<div className="flex flex-col gap-1">
|
||||
<div className="flex gap-2 items-center">
|
||||
<span className="font-semibold text-foreground">
|
||||
{browserDisplayName} Update Available
|
||||
</span>
|
||||
<Badge
|
||||
variant={notification.is_stable_update ? "default" : "secondary"}
|
||||
>
|
||||
{notification.is_stable_update ? "Stable" : "Nightly"}
|
||||
</Badge>
|
||||
</div>
|
||||
<div className="text-sm text-muted-foreground">
|
||||
Update {profileText} from {notification.current_version} to{" "}
|
||||
<span className="font-medium">{notification.new_version}</span>
|
||||
</div>
|
||||
</div>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={async () => {
|
||||
await onDismiss(notification.id);
|
||||
}}
|
||||
className="p-0 w-6 h-6 shrink-0"
|
||||
>
|
||||
<FaTimes className="w-3 h-3" />
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className="flex gap-2 items-center">
|
||||
<Button
|
||||
onClick={handleUpdateClick}
|
||||
disabled={isUpdating}
|
||||
size="sm"
|
||||
className="flex gap-2 items-center"
|
||||
>
|
||||
<FaDownload className="w-3 h-3" />
|
||||
Update
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={async () => {
|
||||
await onDismiss(notification.id);
|
||||
}}
|
||||
size="sm"
|
||||
>
|
||||
Later
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{notification.affected_profiles.length > 1 && (
|
||||
<div className="text-xs text-muted-foreground">
|
||||
Affected profiles: {notification.affected_profiles.join(", ")}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,156 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import { LoadingButton } from "@/components/loading-button";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
Command,
|
||||
CommandEmpty,
|
||||
CommandGroup,
|
||||
CommandInput,
|
||||
CommandItem,
|
||||
CommandList,
|
||||
} from "@/components/ui/command";
|
||||
import {
|
||||
Popover,
|
||||
PopoverContent,
|
||||
PopoverTrigger,
|
||||
} from "@/components/ui/popover";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { useState } from "react";
|
||||
import { LuDownload } from "react-icons/lu";
|
||||
import { LuCheck, LuChevronsUpDown } from "react-icons/lu";
|
||||
import { ScrollArea } from "./ui/scroll-area";
|
||||
|
||||
interface GithubRelease {
|
||||
tag_name: string;
|
||||
assets: {
|
||||
name: string;
|
||||
browser_download_url: string;
|
||||
hash?: string;
|
||||
}[];
|
||||
published_at: string;
|
||||
is_nightly: boolean;
|
||||
}
|
||||
|
||||
interface VersionSelectorProps {
|
||||
selectedVersion: string | null;
|
||||
onVersionSelect: (version: string | null) => void;
|
||||
availableVersions: GithubRelease[];
|
||||
downloadedVersions: string[];
|
||||
isDownloading: boolean;
|
||||
onDownload: () => void;
|
||||
placeholder?: string;
|
||||
showDownloadButton?: boolean;
|
||||
}
|
||||
|
||||
export function VersionSelector({
|
||||
selectedVersion,
|
||||
onVersionSelect,
|
||||
availableVersions,
|
||||
downloadedVersions,
|
||||
isDownloading,
|
||||
onDownload,
|
||||
placeholder = "Select version...",
|
||||
showDownloadButton = true,
|
||||
}: VersionSelectorProps) {
|
||||
const [versionPopoverOpen, setVersionPopoverOpen] = useState(false);
|
||||
|
||||
const isVersionDownloaded = selectedVersion
|
||||
? downloadedVersions.includes(selectedVersion)
|
||||
: false;
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<Popover
|
||||
open={versionPopoverOpen}
|
||||
onOpenChange={setVersionPopoverOpen}
|
||||
modal={true}
|
||||
>
|
||||
<PopoverTrigger asChild>
|
||||
<Button
|
||||
variant="outline"
|
||||
role="combobox"
|
||||
aria-expanded={versionPopoverOpen}
|
||||
className="justify-between w-full"
|
||||
>
|
||||
{selectedVersion ?? placeholder}
|
||||
<LuChevronsUpDown className="ml-2 w-4 h-4 opacity-50 shrink-0" />
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-[300px] p-0">
|
||||
<Command>
|
||||
<CommandInput placeholder="Search versions..." />
|
||||
<CommandEmpty>No versions found.</CommandEmpty>
|
||||
<CommandList>
|
||||
<ScrollArea
|
||||
className={
|
||||
"[&>[data-radix-scroll-area-viewport]]:max-h-[200px]"
|
||||
}
|
||||
>
|
||||
<CommandGroup>
|
||||
{availableVersions.map((version) => {
|
||||
const isDownloaded = downloadedVersions.includes(
|
||||
version.tag_name,
|
||||
);
|
||||
return (
|
||||
<CommandItem
|
||||
key={version.tag_name}
|
||||
value={version.tag_name}
|
||||
onSelect={(currentValue) => {
|
||||
onVersionSelect(
|
||||
currentValue === selectedVersion
|
||||
? null
|
||||
: currentValue,
|
||||
);
|
||||
setVersionPopoverOpen(false);
|
||||
}}
|
||||
>
|
||||
<LuCheck
|
||||
className={cn(
|
||||
"mr-2 h-4 w-4",
|
||||
selectedVersion === version.tag_name
|
||||
? "opacity-100"
|
||||
: "opacity-0",
|
||||
)}
|
||||
/>
|
||||
<div className="flex gap-2 items-center">
|
||||
<span>{version.tag_name}</span>
|
||||
{version.is_nightly && (
|
||||
<Badge variant="secondary" className="text-xs">
|
||||
Nightly
|
||||
</Badge>
|
||||
)}
|
||||
{isDownloaded && (
|
||||
<Badge variant="default" className="text-xs">
|
||||
Downloaded
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
</CommandItem>
|
||||
);
|
||||
})}
|
||||
</CommandGroup>
|
||||
</ScrollArea>
|
||||
</CommandList>
|
||||
</Command>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
|
||||
{/* Download Button */}
|
||||
{showDownloadButton && selectedVersion && !isVersionDownloaded && (
|
||||
<LoadingButton
|
||||
isLoading={isDownloading}
|
||||
onClick={() => {
|
||||
onDownload();
|
||||
}}
|
||||
variant="outline"
|
||||
className="w-full"
|
||||
>
|
||||
<LuDownload className="mr-2 w-4 h-4" />
|
||||
{isDownloading ? "Downloading..." : "Download Browser"}
|
||||
</LoadingButton>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,12 +1,6 @@
|
||||
import { invoke } from "@tauri-apps/api/core";
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
export interface BrowserSupportInfo {
|
||||
supportedBrowsers: string[];
|
||||
isLoading: boolean;
|
||||
error: string | null;
|
||||
}
|
||||
|
||||
export function useBrowserSupport() {
|
||||
const [supportedBrowsers, setSupportedBrowsers] = useState<string[]>([]);
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
|
||||
@@ -20,7 +20,7 @@ const loadMacOSPermissions = async () => {
|
||||
|
||||
export type PermissionType = "microphone" | "camera";
|
||||
|
||||
export interface UsePermissionsReturn {
|
||||
interface UsePermissionsReturn {
|
||||
requestPermission: (type: PermissionType) => Promise<void>;
|
||||
isMicrophoneAccessGranted: boolean;
|
||||
isCameraAccessGranted: boolean;
|
||||
|
||||
@@ -46,14 +46,3 @@ export function getBrowserIcon(browserType: string) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Format browser name by capitalizing words and joining with spaces
|
||||
* (fallback method for simple transformations)
|
||||
*/
|
||||
export function formatBrowserName(browserType: string): string {
|
||||
return browserType
|
||||
.split("-")
|
||||
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
|
||||
.join(" ");
|
||||
}
|
||||
|
||||
+13
-114
@@ -2,27 +2,26 @@ import { UnifiedToast } from "@/components/custom-toast";
|
||||
import React from "react";
|
||||
import { toast as sonnerToast } from "sonner";
|
||||
|
||||
// Define toast types locally
|
||||
export interface BaseToastProps {
|
||||
interface BaseToastProps {
|
||||
id?: string;
|
||||
title: string;
|
||||
description?: string;
|
||||
duration?: number;
|
||||
}
|
||||
|
||||
export interface LoadingToastProps extends BaseToastProps {
|
||||
interface LoadingToastProps extends BaseToastProps {
|
||||
type: "loading";
|
||||
}
|
||||
|
||||
export interface SuccessToastProps extends BaseToastProps {
|
||||
interface SuccessToastProps extends BaseToastProps {
|
||||
type: "success";
|
||||
}
|
||||
|
||||
export interface ErrorToastProps extends BaseToastProps {
|
||||
interface ErrorToastProps extends BaseToastProps {
|
||||
type: "error";
|
||||
}
|
||||
|
||||
export interface DownloadToastProps extends BaseToastProps {
|
||||
interface DownloadToastProps extends BaseToastProps {
|
||||
type: "download";
|
||||
stage?:
|
||||
| "downloading"
|
||||
@@ -37,7 +36,7 @@ export interface DownloadToastProps extends BaseToastProps {
|
||||
};
|
||||
}
|
||||
|
||||
export interface VersionUpdateToastProps extends BaseToastProps {
|
||||
interface VersionUpdateToastProps extends BaseToastProps {
|
||||
type: "version-update";
|
||||
progress?: {
|
||||
current: number;
|
||||
@@ -47,39 +46,23 @@ export interface VersionUpdateToastProps extends BaseToastProps {
|
||||
};
|
||||
}
|
||||
|
||||
export interface FetchingToastProps extends BaseToastProps {
|
||||
type: "fetching";
|
||||
browserName?: string;
|
||||
}
|
||||
|
||||
export interface TwilightUpdateToastProps extends BaseToastProps {
|
||||
type: "twilight-update";
|
||||
browserName?: string;
|
||||
hasUpdate?: boolean;
|
||||
}
|
||||
|
||||
export type ToastProps =
|
||||
| LoadingToastProps
|
||||
type ToastProps =
|
||||
| SuccessToastProps
|
||||
| ErrorToastProps
|
||||
| DownloadToastProps
|
||||
| VersionUpdateToastProps
|
||||
| FetchingToastProps
|
||||
| TwilightUpdateToastProps;
|
||||
| LoadingToastProps
|
||||
| VersionUpdateToastProps;
|
||||
|
||||
// Unified toast function
|
||||
export function showToast(props: ToastProps & { id?: string }) {
|
||||
const toastId = props.id ?? `toast-${props.type}-${Date.now()}`;
|
||||
|
||||
// Improved duration logic - make toasts disappear more quickly
|
||||
let duration: number;
|
||||
if (props.duration !== undefined) {
|
||||
duration = props.duration;
|
||||
} else {
|
||||
switch (props.type) {
|
||||
case "loading":
|
||||
case "fetching":
|
||||
duration = 10000; // 10 seconds instead of infinite
|
||||
duration = 10000;
|
||||
break;
|
||||
case "download":
|
||||
// Only keep infinite for active downloading, others get shorter durations
|
||||
@@ -91,18 +74,15 @@ export function showToast(props: ToastProps & { id?: string }) {
|
||||
duration = 20000;
|
||||
}
|
||||
break;
|
||||
case "version-update":
|
||||
duration = 15000;
|
||||
break;
|
||||
case "twilight-update":
|
||||
duration = 10000;
|
||||
break;
|
||||
case "success":
|
||||
duration = 3000;
|
||||
break;
|
||||
case "error":
|
||||
duration = 10000;
|
||||
break;
|
||||
case "version-update":
|
||||
duration = 15000;
|
||||
break;
|
||||
default:
|
||||
duration = 5000;
|
||||
}
|
||||
@@ -152,22 +132,6 @@ export function showToast(props: ToastProps & { id?: string }) {
|
||||
return toastId;
|
||||
}
|
||||
|
||||
// Convenience functions for common use cases
|
||||
export function showLoadingToast(
|
||||
title: string,
|
||||
options?: {
|
||||
id?: string;
|
||||
description?: string;
|
||||
duration?: number;
|
||||
},
|
||||
) {
|
||||
return showToast({
|
||||
type: "loading",
|
||||
title,
|
||||
...options,
|
||||
});
|
||||
}
|
||||
|
||||
export function showDownloadToast(
|
||||
browserName: string,
|
||||
version: string,
|
||||
@@ -206,45 +170,6 @@ export function showDownloadToast(
|
||||
});
|
||||
}
|
||||
|
||||
export function showVersionUpdateToast(
|
||||
title: string,
|
||||
options?: {
|
||||
id?: string;
|
||||
description?: string;
|
||||
progress?: {
|
||||
current: number;
|
||||
total: number;
|
||||
found: number;
|
||||
current_browser?: string;
|
||||
};
|
||||
duration?: number;
|
||||
},
|
||||
) {
|
||||
return showToast({
|
||||
type: "version-update",
|
||||
title,
|
||||
...options,
|
||||
});
|
||||
}
|
||||
|
||||
export function showFetchingToast(
|
||||
browserName: string,
|
||||
options?: {
|
||||
id?: string;
|
||||
description?: string;
|
||||
duration?: number;
|
||||
},
|
||||
) {
|
||||
return showToast({
|
||||
type: "fetching",
|
||||
title: `Checking for new ${browserName} versions...`,
|
||||
description:
|
||||
options?.description ?? "Fetching latest release information...",
|
||||
browserName,
|
||||
...options,
|
||||
});
|
||||
}
|
||||
|
||||
export function showSuccessToast(
|
||||
title: string,
|
||||
options?: {
|
||||
@@ -275,25 +200,6 @@ export function showErrorToast(
|
||||
});
|
||||
}
|
||||
|
||||
export function showTwilightUpdateToast(
|
||||
browserName: string,
|
||||
options?: {
|
||||
id?: string;
|
||||
description?: string;
|
||||
hasUpdate?: boolean;
|
||||
duration?: number;
|
||||
},
|
||||
) {
|
||||
return showToast({
|
||||
type: "twilight-update",
|
||||
title: options?.hasUpdate
|
||||
? `${browserName} twilight update available`
|
||||
: `Checking for ${browserName} twilight updates...`,
|
||||
browserName,
|
||||
...options,
|
||||
});
|
||||
}
|
||||
|
||||
export function showAutoUpdateToast(
|
||||
browserName: string,
|
||||
version: string,
|
||||
@@ -314,17 +220,10 @@ export function showAutoUpdateToast(
|
||||
});
|
||||
}
|
||||
|
||||
// Generic helper for dismissing toasts
|
||||
export function dismissToast(id: string) {
|
||||
sonnerToast.dismiss(id);
|
||||
}
|
||||
|
||||
// Dismiss all toasts
|
||||
export function dismissAllToasts() {
|
||||
sonnerToast.dismiss();
|
||||
}
|
||||
|
||||
// Add a specific function for unified version update progress
|
||||
export function showUnifiedVersionUpdateToast(
|
||||
title: string,
|
||||
options?: {
|
||||
|
||||
@@ -43,22 +43,3 @@ export interface AppUpdateInfo {
|
||||
is_nightly: boolean;
|
||||
published_at: string;
|
||||
}
|
||||
|
||||
export interface AppVersionInfo {
|
||||
version: string;
|
||||
is_nightly: boolean;
|
||||
}
|
||||
|
||||
export type PermissionType = "microphone" | "camera" | "location";
|
||||
|
||||
export type PermissionStatus =
|
||||
| "granted"
|
||||
| "denied"
|
||||
| "not_determined"
|
||||
| "restricted";
|
||||
|
||||
export interface PermissionInfo {
|
||||
permission_type: PermissionType;
|
||||
status: PermissionStatus;
|
||||
description: string;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user