Skip to main content

Mountain/Track/Effect/CreateEffectForRequest/
WindowUI.rs

1#![allow(non_snake_case, unused_variables, dead_code, unused_imports)]
2
3//! Window-namespace UI commands from Cocoon's window shim.
4//! ShowMessage is fire-and-forget (no selection reply needed).
5//! ShowQuickPick / ShowInputBox / ShowOpenDialog / ShowSaveDialog block on
6//! a oneshot channel that is resolved by the frontend via ResolveUIRequest.
7
8use std::{future::Future, pin::Pin, sync::Arc};
9
10use serde_json::{Value, json};
11use tauri::Runtime;
12
13use crate::{
14	ApplicationState::State::ApplicationState::ApplicationState,
15	RunTime::ApplicationRunTime::ApplicationRunTime,
16	Track::Effect::MappedEffectType::MappedEffect,
17	dev_log,
18};
19
20pub fn CreateEffect<R:Runtime>(MethodName:&str, Parameters:Value) -> Option<Result<MappedEffect, String>> {
21	match MethodName {
22		"Window.ShowMessage" => {
23			let effect =
24				move |run_time:Arc<ApplicationRunTime>| -> Pin<Box<dyn Future<Output = Result<Value, String>> + Send>> {
25					Box::pin(async move {
26						use tauri::Emitter;
27						let AppHandle = run_time.Environment.ApplicationHandle.clone();
28						let Payload = if Parameters.is_array() {
29							Parameters.get(0).cloned().unwrap_or_default()
30						} else {
31							Parameters
32						};
33						let Id = format!(
34							"notification-{}",
35							std::time::SystemTime::now()
36								.duration_since(std::time::UNIX_EPOCH)
37								.map(|D| D.as_millis())
38								.unwrap_or(0)
39						);
40						let Message = Payload.get("message").and_then(Value::as_str).unwrap_or("").to_string();
41						let Level = Payload.get("level").and_then(Value::as_str).unwrap_or("info").to_string();
42						let Items = Payload.get("items").cloned().unwrap_or(json!([]));
43						let Options = Payload.get("options").cloned().unwrap_or(json!({}));
44						if let Err(Error) = AppHandle.emit(
45							"sky://notification/show",
46							json!({
47								"id": Id,
48								"message": Message,
49								"severity": Level,
50								"actions": Items,
51								"options": Options,
52							}),
53						) {
54							dev_log!(
55								"notification",
56								"warn: [Window.ShowMessage] sky://notification/show emit failed: {}",
57								Error
58							);
59						}
60						Ok(Value::Null)
61					})
62				};
63
64			Some(Ok(Box::new(effect)))
65		},
66
67		"Window.ShowQuickPick" | "Window.ShowInputBox" | "Window.ShowOpenDialog" | "Window.ShowSaveDialog" => {
68			let MethodNameOwned = MethodName.to_string();
69
70			let effect =
71				move |run_time:Arc<ApplicationRunTime>| -> Pin<Box<dyn Future<Output = Result<Value, String>> + Send>> {
72					Box::pin(async move {
73						use tauri::Emitter;
74
75						let Args = if Parameters.is_array() { Parameters } else { json!([Parameters]) };
76
77						let Channel = match MethodNameOwned.as_str() {
78							"Window.ShowQuickPick" => "sky://quickpick/show",
79							"Window.ShowInputBox" => "sky://input-box/show",
80							"Window.ShowOpenDialog" => "sky://dialog/open",
81							"Window.ShowSaveDialog" => "sky://dialog/save",
82							_ => "sky://quickpick/show",
83						};
84
85						let Nonce = format!(
86							"ui-{}",
87							std::time::SystemTime::now()
88								.duration_since(std::time::UNIX_EPOCH)
89								.map(|D| D.as_nanos())
90								.unwrap_or(0)
91						);
92
93						// Register the reply channel before emitting so the
94						// frontend can never race-resolve before we are waiting.
95						let (tx, rx) = tokio::sync::oneshot::channel();
96						run_time.Environment.ApplicationState.UI.AddPendingRequest(Nonce.clone(), tx);
97
98						let AppHandle = run_time.Environment.ApplicationHandle.clone();
99						if let Err(Error) = AppHandle.emit(Channel, json!({ "nonce": Nonce, "args": Args })) {
100							// Emit failed -- remove the dangling sender so the map
101							// does not grow unboundedly on repeated failures.
102							run_time.Environment.ApplicationState.UI.RemovePendingRequest(&Nonce.clone());
103							dev_log!("ipc", "warn: [{}] {} emit failed: {}", MethodNameOwned, Channel, Error);
104							return Err(format!("[{}] emit failed: {}", MethodNameOwned, Error));
105						}
106
107						// Block until the frontend calls ResolveUIRequest with
108						// the same nonce, or the sender is dropped (dialog
109						// dismissed / window closed).
110						match rx.await {
111							Ok(Ok(Value)) => Ok(Value),
112							Ok(Err(CommonError)) => Err(CommonError.to_string()),
113							Err(_RecvError) => {
114								// Sender was dropped without a reply -- the user
115								// dismissed the dialog.  Return null so the extension
116								// host sees `undefined` (VS Code contract for cancelled
117								// quick-pick / input-box).
118								dev_log!("ipc", "[{}] dialog dismissed (nonce dropped)", MethodNameOwned);
119								Ok(Value::Null)
120							},
121						}
122					})
123				};
124
125			Some(Ok(Box::new(effect)))
126		},
127
128		_ => None,
129	}
130}