feat(app): handle SQL export and tray updates

This commit is contained in:
t8y2
2026-05-18 18:39:59 +08:00
parent b01c17e709
commit b2be49367c
12 changed files with 153 additions and 11 deletions
+34
View File
@@ -9,6 +9,11 @@ pub fn pending_open_sql_files(state: tauri::State<'_, ExternalSqlOpenState>) ->
dedupe_paths(paths)
}
#[tauri::command]
pub fn read_external_sql_file(path: String) -> Result<String, String> {
read_external_sql_file_content(Path::new(&path))
}
#[derive(Default)]
pub struct ExternalSqlOpenState {
pending: Mutex<Vec<String>>,
@@ -55,6 +60,13 @@ pub fn is_sql_file_path(path: &Path) -> bool {
path.extension().and_then(|ext| ext.to_str()).map(|ext| ext.eq_ignore_ascii_case("sql")).unwrap_or(false)
}
pub fn read_external_sql_file_content(path: &Path) -> Result<String, String> {
if !is_sql_file_path(path) {
return Err("Only .sql files can be opened this way".to_string());
}
std::fs::read_to_string(path).map_err(|e| format!("Failed to read SQL file: {e}"))
}
fn dedupe_paths(paths: Vec<String>) -> Vec<String> {
let mut unique = Vec::new();
for path in paths {
@@ -91,4 +103,26 @@ mod tests {
assert_eq!(state.drain(), vec!["/tmp/a.sql"]);
assert!(state.drain().is_empty());
}
#[test]
fn reads_external_sql_file_content() {
let path = std::env::temp_dir().join(format!("dbx-test-{}.sql", uuid::Uuid::new_v4()));
std::fs::write(&path, "select 1;").unwrap();
let result = read_external_sql_file_content(&path);
let _ = std::fs::remove_file(&path);
assert_eq!(result.unwrap(), "select 1;");
}
#[test]
fn rejects_external_non_sql_file_content() {
let path = std::env::temp_dir().join(format!("dbx-test-{}.txt", uuid::Uuid::new_v4()));
std::fs::write(&path, "select 1;").unwrap();
let result = read_external_sql_file_content(&path);
let _ = std::fs::remove_file(&path);
assert!(result.unwrap_err().contains(".sql"));
}
}
+66 -7
View File
@@ -8,8 +8,66 @@ use commands::connection::AppState;
use dbx_core::storage::Storage;
use std::sync::Arc;
use std::time::Instant;
use tauri::{
menu::MenuBuilder,
tray::{MouseButton, MouseButtonState, TrayIconBuilder, TrayIconEvent},
};
use tauri::{Emitter, Manager, RunEvent};
fn should_hide_window_on_close(target_os: &str) -> bool {
matches!(target_os, "macos" | "windows")
}
fn show_main_window(app: &tauri::AppHandle) {
if let Some(window) = app.get_webview_window("main") {
let _ = window.show();
let _ = window.unminimize();
let _ = window.set_focus();
}
}
#[cfg_attr(not(target_os = "windows"), allow(dead_code))]
fn setup_windows_tray(app: &mut tauri::App) -> tauri::Result<()> {
let menu = MenuBuilder::new(app).text("show", "Show DBX").separator().text("quit", "Quit DBX").build()?;
let mut tray = TrayIconBuilder::with_id("main-tray").tooltip("DBX").menu(&menu).show_menu_on_left_click(false);
if let Some(icon) = app.default_window_icon().cloned() {
tray = tray.icon(icon);
}
tray.on_menu_event(|app, event| {
if event.id() == "show" {
show_main_window(app);
} else if event.id() == "quit" {
app.exit(0);
}
})
.on_tray_icon_event(|tray, event| match event {
TrayIconEvent::Click { button: MouseButton::Left, button_state: MouseButtonState::Up, .. }
| TrayIconEvent::DoubleClick { button: MouseButton::Left, .. } => show_main_window(tray.app_handle()),
_ => {}
})
.build(app)?;
Ok(())
}
#[cfg(test)]
mod tests {
use super::should_hide_window_on_close;
#[test]
fn hides_window_on_close_for_windows_and_macos() {
assert!(should_hide_window_on_close("windows"));
assert!(should_hide_window_on_close("macos"));
}
#[test]
fn does_not_hide_window_on_close_for_other_platforms() {
assert!(!should_hide_window_on_close("linux"));
}
}
#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
rustls::crypto::aws_lc_rs::default_provider().install_default().expect("Failed to install rustls crypto provider");
@@ -75,15 +133,18 @@ pub fn run() {
let _ = window.set_decorations(false);
}
}
#[cfg(target_os = "windows")]
setup_windows_tray(app)?;
window_state_guard::enforce_main_window_bounds(app.handle());
Ok(())
})
.on_window_event(|window, event| {
#[cfg(target_os = "macos")]
if let tauri::WindowEvent::CloseRequested { api, .. } = event {
window.hide().unwrap();
api.prevent_close();
if should_hide_window_on_close(std::env::consts::OS) {
let _ = window.hide();
api.prevent_close();
}
}
})
.invoke_handler(tauri::generate_handler![
@@ -135,6 +196,7 @@ pub fn run() {
commands::sql_file::execute_sql_file,
commands::sql_file::cancel_sql_file_execution,
commands::external_sql::pending_open_sql_files,
commands::external_sql::read_external_sql_file,
commands::table_import::preview_table_import_file,
commands::table_import::import_table_file,
commands::table_import::cancel_table_import,
@@ -215,10 +277,7 @@ pub fn run() {
#[cfg(target_os = "macos")]
if let RunEvent::Reopen { has_visible_windows, .. } = &event {
if !has_visible_windows {
if let Some(window) = app_handle.get_webview_window("main") {
let _ = window.show();
let _ = window.set_focus();
}
show_main_window(app_handle);
}
}
});