feat(06-02): add native desktop features — tray, window-state, autostart, version check
- Add GET /health/version public endpoint to API - Extend Tauri with 4 plugins: notification, autostart, window-state, store - Implement close-to-tray with prevent_close + prevent_exit (Pitfall 2) - Tray menu with Oeffnen/Beenden (German labels) - Async startup version check against server /health/version - Generate Tessera-branded icons (yellow T on dark bg, OKLCH palette) - Update capabilities for notification, autostart, window-state permissions Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,23 +1,34 @@
|
||||
use tauri::Manager;
|
||||
use tauri::{
|
||||
Manager,
|
||||
menu::{MenuBuilder, MenuItemBuilder},
|
||||
tray::{MouseButton, MouseButtonState, TrayIconBuilder, TrayIconEvent},
|
||||
RunEvent, WindowEvent,
|
||||
};
|
||||
use tauri_plugin_autostart::MacosLauncher;
|
||||
use tauri_plugin_notification::NotificationExt;
|
||||
use tauri_plugin_store::StoreExt;
|
||||
|
||||
/// Main entry point for the Tessera desktop application.
|
||||
///
|
||||
/// Registers plugins, checks for a stored server URL on startup,
|
||||
/// and either navigates to the configured server or shows the
|
||||
/// bundled setup page for first-run configuration.
|
||||
#[derive(serde::Deserialize)]
|
||||
struct VersionResponse {
|
||||
version: String,
|
||||
}
|
||||
|
||||
pub fn run() {
|
||||
tauri::Builder::default()
|
||||
let app = tauri::Builder::default()
|
||||
.plugin(tauri_plugin_store::Builder::new().build())
|
||||
.plugin(tauri_plugin_notification::init())
|
||||
.plugin(tauri_plugin_window_state::Builder::default().build())
|
||||
.plugin(tauri_plugin_autostart::init(MacosLauncher::LaunchAgent, None))
|
||||
.setup(|app| {
|
||||
// Attempt to read a previously stored server URL
|
||||
let store = app.store("config.json")?;
|
||||
let server_url = store.get("server_url");
|
||||
|
||||
let mut url_for_check: Option<String> = None;
|
||||
|
||||
if let Some(url_value) = server_url {
|
||||
if let Some(url_str) = url_value.as_str() {
|
||||
if !url_str.is_empty() {
|
||||
// Navigate the main window to the configured server URL
|
||||
url_for_check = Some(url_str.to_string());
|
||||
if let Some(window) = app.get_webview_window("main") {
|
||||
if let Ok(parsed) = url_str.parse::<tauri::Url>() {
|
||||
let _ = window.navigate(parsed);
|
||||
@@ -26,11 +37,83 @@ pub fn run() {
|
||||
}
|
||||
}
|
||||
}
|
||||
// If no server_url is stored, the WebView remains on the
|
||||
// bundled setup page (frontendDist = ../src/setup.html)
|
||||
|
||||
// Tray menu
|
||||
let open = MenuItemBuilder::with_id("open", "Oeffnen").build(app)?;
|
||||
let quit = MenuItemBuilder::with_id("quit", "Beenden").build(app)?;
|
||||
let menu = MenuBuilder::new(app)
|
||||
.item(&open)
|
||||
.separator()
|
||||
.item(&quit)
|
||||
.build()?;
|
||||
|
||||
let _tray = TrayIconBuilder::new()
|
||||
.icon(app.default_window_icon().unwrap().clone())
|
||||
.menu(&menu)
|
||||
.tooltip("Tessera")
|
||||
.show_menu_on_left_click(false)
|
||||
.on_menu_event(|app, event| match event.id().as_ref() {
|
||||
"open" => {
|
||||
if let Some(w) = app.get_webview_window("main") {
|
||||
let _ = w.show();
|
||||
let _ = w.set_focus();
|
||||
}
|
||||
}
|
||||
"quit" => {
|
||||
app.exit(0);
|
||||
}
|
||||
_ => {}
|
||||
})
|
||||
.on_tray_icon_event(|tray, event| {
|
||||
if let TrayIconEvent::Click {
|
||||
button: MouseButton::Left,
|
||||
button_state: MouseButtonState::Up,
|
||||
..
|
||||
} = event
|
||||
{
|
||||
if let Some(w) = tray.app_handle().get_webview_window("main") {
|
||||
let _ = w.show();
|
||||
let _ = w.set_focus();
|
||||
}
|
||||
}
|
||||
})
|
||||
.build(app)?;
|
||||
|
||||
// Version check (async, non-blocking)
|
||||
if let Some(server_url) = url_for_check {
|
||||
let app_handle = app.handle().clone();
|
||||
let app_version = env!("CARGO_PKG_VERSION").to_string();
|
||||
tauri::async_runtime::spawn(async move {
|
||||
let url = format!("{}/health/version", server_url.trim_end_matches('/'));
|
||||
if let Ok(resp) = reqwest::get(&url).await {
|
||||
if let Ok(info) = resp.json::<VersionResponse>().await {
|
||||
if info.version != app_version {
|
||||
let _ = app_handle
|
||||
.notification()
|
||||
.builder()
|
||||
.title("Tessera Update")
|
||||
.body("Eine neue Version ist verfuegbar.")
|
||||
.show();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Ok(())
|
||||
})
|
||||
.run(tauri::generate_context!())
|
||||
.expect("error while running Tessera desktop application");
|
||||
.on_window_event(|window, event| {
|
||||
if let WindowEvent::CloseRequested { api, .. } = event {
|
||||
let _ = window.hide();
|
||||
api.prevent_close();
|
||||
}
|
||||
})
|
||||
.build(tauri::generate_context!())
|
||||
.expect("error while building Tessera desktop application");
|
||||
|
||||
app.run(|_app, event| {
|
||||
if let RunEvent::ExitRequested { api, .. } = event {
|
||||
api.prevent_exit();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user