1use core::fmt;
2
3use reactive_graph::{
4 effect::Effect,
5 owner::{LocalStorage, StoredValue},
6 signal::{signal, signal_local, WriteSignal},
7 spawn_local_scoped,
8 traits::{Get as _, GetValue as _, Set as _, UpdateUntracked as _},
9 wrappers::read::Signal
10};
11use wasm_bindgen::prelude::*;
12
13pub fn use_command<T>(
33 cmd: &'static str,
34) -> UseTauriWithReturn<(), T>
35where
36 T: serde::de::DeserializeOwned + Clone + Send + Sync + 'static,
37{
38 let (args, set_args) = signal(None::<()>);
39
40 let UseTauriReturn {
41 data,
42 error,
43 trigger
44 } = use_invoke::<(), (), T>(cmd);
45
46 Effect::new(move || {
47 if let Some(()) = args.get() {
48 trigger.set(Some(((), ())));
49 set_args.update_untracked(|v| *v = None);
50 }
51 });
52
53 UseTauriWithReturn {
54 data: data.into(),
55 error: error.into(),
56 trigger: set_args
57 }
58}
59
60pub fn use_invoke_with_args<Args, T>(
80 cmd: &'static str,
81) -> UseTauriWithReturn<Args, T>
82where
83 Args: serde::Serialize + Clone + Send + Sync + 'static,
84 T: serde::de::DeserializeOwned + Clone + Send + Sync + 'static,
85{
86 let (args, set_args) = signal(None::<Args>);
87
88 let UseTauriReturn {
89 data,
90 error,
91 trigger
92 } = use_invoke::<Args, (), T>(cmd);
93
94 Effect::new(move || {
95 if let Some(args) = args.get() {
96 trigger.set(Some((args, ())));
97 set_args.update_untracked(|v| *v = None);
98 }
99 });
100
101 UseTauriWithReturn {
102 data: data.into(),
103 error: error.into(),
104 trigger: set_args
105 }
106}
107
108pub fn use_invoke_with_options<Opts, T>(
128 cmd: &'static str,
129) -> UseTauriWithReturn<Opts, T>
130where
131 Opts: serde::Serialize + Clone + Send + Sync + 'static,
132 T: serde::de::DeserializeOwned + Clone + Send + Sync + 'static,
133{
134 let (opts, set_opts) = signal(None::<Opts>);
135
136 let UseTauriReturn {
137 data,
138 error,
139 trigger
140 } = use_invoke::<(), Opts, T>(cmd);
141
142 Effect::new(move || {
143 if let Some(opts) = opts.get() {
144 trigger.set(Some(((), opts)));
145 set_opts.update_untracked(|v| *v = None);
146 }
147 });
148
149 UseTauriWithReturn {
150 data: data.into(),
151 error: error.into(),
152 trigger: set_opts
153 }
154}
155
156pub fn use_invoke<Args, Opts, T>(
158 cmd: &'static str
159) -> UseTauriReturn<Args, Opts, T>
160where
161 Args: serde::Serialize + Clone + Send + Sync + 'static,
162 Opts: serde::Serialize + Clone + Send + Sync + 'static,
163 T: serde::de::DeserializeOwned + Clone + Send + Sync + 'static,
164{
165 let (data, set_data) = signal(None::<T>);
166 let (error, set_error) = signal_local(None::<UseTauriError>);
167 let (trigger, set_trigger) = signal(None::<(Args, Opts)>);
168
169 let init = StoredValue::new(move |cmd: &'static str, args: Args, options: Opts| {
170 #[derive(Clone, serde::Serialize)]
171 struct OptionsWrapper<Opts>
172 where
173 Opts: serde::Serialize + Clone + 'static,
174 {
175 options: Opts
176 }
177
178 let args = args.clone();
179 let options = OptionsWrapper{
180 options: options.clone()
181 };
182
183 spawn_local_scoped(async move {
184 let args = match serde_wasm_bindgen::to_value(&args) {
185 Ok(value) => value,
186 Err(err) => {
187 set_error.set(Some(UseTauriError::Serialize(err.to_string())));
188 return;
189 }
190 };
191
192 let options = match serde_wasm_bindgen::to_value(&options) {
193 Ok(value) => value,
194 Err(err) => {
195 set_error.set(Some(UseTauriError::Serialize(err.to_string())));
196 return;
197 }
198 };
199
200 match invoke(cmd, args, options).await {
201 Ok(data) => {
202 match serde_wasm_bindgen::from_value::<T>(data) {
203 Ok(data) => set_data.set(Some(data)),
204 Err(err) => set_error.set(Some(UseTauriError::Deserialize(err.to_string())))
205 }
206 }
207 Err(err) => {
208 let err_str = err.as_string().unwrap_or_else(|| "Unknown error".to_string());
209 set_error.set(Some(UseTauriError::Command(cmd, err_str)))
210 }
211 }
212 });
213 });
214
215 Effect::new(move || {
216 let init = init.get_value();
217 if let Some((args, opts)) = trigger.get() {
218 init(cmd, args, opts);
219 }
220 set_trigger.update_untracked(|v| *v = None);
221 });
222
223 UseTauriReturn {
224 data: data.into(),
225 error: error.into(),
226 trigger: set_trigger
227 }
228}
229
230pub struct UseTauriReturn<Args, Opts, T>
231where
232 Args: serde::Serialize + Clone + 'static,
233 Opts: serde::Serialize + Clone + 'static,
234 T: serde::de::DeserializeOwned + Clone + Send + Sync + 'static,
235{
236 pub data: Signal<Option<T>>,
237 pub error: Signal<Option<UseTauriError>, LocalStorage>,
238 pub trigger: WriteSignal<Option<(Args, Opts)>>,
239}
240
241pub struct UseTauriWithReturn<O, T>
242where
243 O: serde::Serialize + Clone + 'static,
244 T: serde::de::DeserializeOwned + Clone + Send + Sync + 'static,
245{
246 pub data: Signal<Option<T>>,
247 pub error: Signal<Option<UseTauriError>, LocalStorage>,
248 pub trigger: WriteSignal<Option<O>>,
249}
250
251#[derive(Clone, Debug)]
252pub enum UseTauriError {
253 Command(&'static str, String),
254 Serialize(String),
255 Deserialize(String),
256}
257
258impl fmt::Display for UseTauriError {
259 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
260 match self {
261 UseTauriError::Command(place, err) => write!(f, "Command error in {}: {}", place, err),
262 UseTauriError::Serialize(err) => write!(f, "Error serializing value: {}", err),
263 UseTauriError::Deserialize(err) => write!(f, "Error deserializing value: {}", err),
264 }
265 }
266}
267
268#[wasm_bindgen]
269extern "C" {
270 #[wasm_bindgen(catch, js_namespace = ["window", "__TAURI__", "core"])]
271 async fn invoke(cmd: &str, args: JsValue, options: JsValue) -> Result<JsValue, JsValue>;
272}