Skip to main content

max / goingson

4.1 KB · 127 lines History Blame Raw
1 //! Window management commands.
2 //!
3 //! Handles creating and managing Tauri windows such as the compose email window,
4 //! and dynamic window title updates. These are desktop-only operations.
5
6 use tracing::instrument;
7
8 use super::{ApiError, ResultApiError};
9
10 /// Percent-encode a string for use in URL query parameters.
11 fn url_encode(s: &str) -> String {
12 let mut encoded = String::with_capacity(s.len() * 2);
13 for byte in s.bytes() {
14 match byte {
15 b'A'..=b'Z' | b'a'..=b'z' | b'0'..=b'9' | b'-' | b'_' | b'.' | b'~' => {
16 encoded.push(byte as char);
17 }
18 _ => {
19 encoded.push_str(&format!("%{:02X}", byte));
20 }
21 }
22 }
23 encoded
24 }
25
26 // ============ Commands ============
27
28 /// Optional reply context passed when opening compose for a reply.
29 #[derive(Debug, serde::Deserialize, Default)]
30 #[serde(rename_all = "camelCase")]
31 pub struct ComposeContext {
32 pub to: Option<String>,
33 pub subject: Option<String>,
34 pub body: Option<String>,
35 pub in_reply_to: Option<String>,
36 pub references: Option<String>,
37 pub thread_id: Option<String>,
38 pub account_id: Option<String>,
39 pub draft_id: Option<String>,
40 }
41
42 #[tauri::command]
43 #[instrument(skip_all)]
44 pub async fn open_compose_window(
45 #[allow(unused_variables)] app: tauri::AppHandle,
46 #[allow(unused_variables)] context: Option<ComposeContext>,
47 ) -> Result<(), ApiError> {
48 #[cfg(not(any(target_os = "ios", target_os = "android")))]
49 {
50 use tauri::{WebviewWindowBuilder, WebviewUrl};
51
52 let label = format!("compose-{}", chrono::Utc::now().timestamp_millis());
53
54 // Build URL with reply context as query params
55 let mut url_str = "compose.html".to_string();
56 if let Some(ctx) = &context {
57 let mut params = Vec::new();
58 if let Some(to) = &ctx.to {
59 params.push(format!("to={}", url_encode(to)));
60 }
61 if let Some(subject) = &ctx.subject {
62 params.push(format!("subject={}", url_encode(subject)));
63 }
64 if let Some(body) = &ctx.body {
65 params.push(format!("body={}", url_encode(body)));
66 }
67 if let Some(irt) = &ctx.in_reply_to {
68 params.push(format!("inReplyTo={}", url_encode(irt)));
69 }
70 if let Some(refs) = &ctx.references {
71 params.push(format!("references={}", url_encode(refs)));
72 }
73 if let Some(tid) = &ctx.thread_id {
74 params.push(format!("threadId={}", url_encode(tid)));
75 }
76 if let Some(aid) = &ctx.account_id {
77 params.push(format!("accountId={}", url_encode(aid)));
78 }
79 if let Some(did) = &ctx.draft_id {
80 params.push(format!("draftId={}", url_encode(did)));
81 }
82 if !params.is_empty() {
83 url_str = format!("compose.html?{}", params.join("&"));
84 }
85 }
86
87 let title = if context.as_ref().and_then(|c| c.draft_id.as_ref()).is_some() {
88 "Edit Draft"
89 } else if context.as_ref().and_then(|c| c.in_reply_to.as_ref()).is_some() {
90 "Reply"
91 } else {
92 "Compose Email"
93 };
94
95 WebviewWindowBuilder::new(&app, &label, WebviewUrl::App(url_str.into()))
96 .title(title)
97 .inner_size(700.0, 550.0)
98 .min_inner_size(500.0, 400.0)
99 .resizable(true)
100 .center()
101 .build()
102 .map_api_err("Failed to create compose window", ApiError::internal)?;
103 }
104
105 Ok(())
106 }
107
108 #[tauri::command]
109 #[instrument(skip_all)]
110 pub async fn set_window_title(
111 #[allow(unused_variables)] app: tauri::AppHandle,
112 #[allow(unused_variables)] title: String,
113 ) -> Result<(), ApiError> {
114 #[cfg(not(any(target_os = "ios", target_os = "android")))]
115 {
116 use tauri::Manager;
117
118 if let Some(window) = app.get_webview_window("main") {
119 window
120 .set_title(&title)
121 .map_api_err("Failed to set window title", ApiError::internal)?;
122 }
123 }
124
125 Ok(())
126 }
127