//! Window management commands. //! //! Handles creating and managing Tauri windows such as the compose email window, //! and dynamic window title updates. These are desktop-only operations. use tracing::instrument; use super::{ApiError, ResultApiError}; /// Percent-encode a string for use in URL query parameters. fn url_encode(s: &str) -> String { let mut encoded = String::with_capacity(s.len() * 2); for byte in s.bytes() { match byte { b'A'..=b'Z' | b'a'..=b'z' | b'0'..=b'9' | b'-' | b'_' | b'.' | b'~' => { encoded.push(byte as char); } _ => { encoded.push_str(&format!("%{:02X}", byte)); } } } encoded } // ============ Commands ============ /// Optional reply context passed when opening compose for a reply. #[derive(Debug, serde::Deserialize, Default)] #[serde(rename_all = "camelCase")] pub struct ComposeContext { pub to: Option, pub subject: Option, pub body: Option, pub in_reply_to: Option, pub references: Option, pub thread_id: Option, pub account_id: Option, pub draft_id: Option, } #[tauri::command] #[instrument(skip_all)] pub async fn open_compose_window( #[allow(unused_variables)] app: tauri::AppHandle, #[allow(unused_variables)] context: Option, ) -> Result<(), ApiError> { #[cfg(not(any(target_os = "ios", target_os = "android")))] { use tauri::{WebviewWindowBuilder, WebviewUrl}; let label = format!("compose-{}", chrono::Utc::now().timestamp_millis()); // Build URL with reply context as query params let mut url_str = "compose.html".to_string(); if let Some(ctx) = &context { let mut params = Vec::new(); if let Some(to) = &ctx.to { params.push(format!("to={}", url_encode(to))); } if let Some(subject) = &ctx.subject { params.push(format!("subject={}", url_encode(subject))); } if let Some(body) = &ctx.body { params.push(format!("body={}", url_encode(body))); } if let Some(irt) = &ctx.in_reply_to { params.push(format!("inReplyTo={}", url_encode(irt))); } if let Some(refs) = &ctx.references { params.push(format!("references={}", url_encode(refs))); } if let Some(tid) = &ctx.thread_id { params.push(format!("threadId={}", url_encode(tid))); } if let Some(aid) = &ctx.account_id { params.push(format!("accountId={}", url_encode(aid))); } if let Some(did) = &ctx.draft_id { params.push(format!("draftId={}", url_encode(did))); } if !params.is_empty() { url_str = format!("compose.html?{}", params.join("&")); } } let title = if context.as_ref().and_then(|c| c.draft_id.as_ref()).is_some() { "Edit Draft" } else if context.as_ref().and_then(|c| c.in_reply_to.as_ref()).is_some() { "Reply" } else { "Compose Email" }; WebviewWindowBuilder::new(&app, &label, WebviewUrl::App(url_str.into())) .title(title) .inner_size(700.0, 550.0) .min_inner_size(500.0, 400.0) .resizable(true) .center() .build() .map_api_err("Failed to create compose window", ApiError::internal)?; } Ok(()) } #[tauri::command] #[instrument(skip_all)] pub async fn set_window_title( #[allow(unused_variables)] app: tauri::AppHandle, #[allow(unused_variables)] title: String, ) -> Result<(), ApiError> { #[cfg(not(any(target_os = "ios", target_os = "android")))] { use tauri::Manager; if let Some(window) = app.get_webview_window("main") { window .set_title(&title) .map_api_err("Failed to set window title", ApiError::internal)?; } } Ok(()) }