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>(
14 cmd: &'static str,
15) -> UseTauriWithReturn<(), T>
16where
17 T: serde::de::DeserializeOwned + Clone + Send + Sync + 'static,
18{
19 let (args, set_args) = signal(None::<()>);
20
21 let UseTauriReturn {
22 data,
23 error,
24 trigger
25 } = use_invoke::<(), (), T>(cmd);
26
27 Effect::new(move || {
28 if let Some(()) = args.get() {
29 trigger.set(Some(((), ())));
30 set_args.update_untracked(|v| *v = None);
31 }
32 });
33
34 UseTauriWithReturn {
35 data: data.into(),
36 error: error.into(),
37 trigger: set_args
38 }
39}
40
41pub fn use_invoke_with_args<Args, T>(
42 cmd: &'static str,
43) -> UseTauriWithReturn<Args, T>
44where
45 Args: serde::Serialize + Clone + Send + Sync + 'static,
46 T: serde::de::DeserializeOwned + Clone + Send + Sync + 'static,
47{
48 let (args, set_args) = signal(None::<Args>);
49
50 let UseTauriReturn {
51 data,
52 error,
53 trigger
54 } = use_invoke::<Args, (), T>(cmd);
55
56 Effect::new(move || {
57 if let Some(args) = args.get() {
58 trigger.set(Some((args, ())));
59 set_args.update_untracked(|v| *v = None);
60 }
61 });
62
63 UseTauriWithReturn {
64 data: data.into(),
65 error: error.into(),
66 trigger: set_args
67 }
68}
69
70pub fn use_invoke_with_options<Opts, T>(
71 cmd: &'static str,
72) -> UseTauriWithReturn<Opts, T>
73where
74 Opts: serde::Serialize + Clone + Send + Sync + 'static,
75 T: serde::de::DeserializeOwned + Clone + Send + Sync + 'static,
76{
77 let (opts, set_opts) = signal(None::<Opts>);
78
79 let UseTauriReturn {
80 data,
81 error,
82 trigger
83 } = use_invoke::<(), Opts, T>(cmd);
84
85 Effect::new(move || {
86 if let Some(opts) = opts.get() {
87 trigger.set(Some(((), opts)));
88 set_opts.update_untracked(|v| *v = None);
89 }
90 });
91
92 UseTauriWithReturn {
93 data: data.into(),
94 error: error.into(),
95 trigger: set_opts
96 }
97}
98
99pub fn use_invoke<Args, Opts, T>(
100 cmd: &'static str
101) -> UseTauriReturn<Args, Opts, T>
102where
103 Args: serde::Serialize + Clone + Send + Sync + 'static,
104 Opts: serde::Serialize + Clone + Send + Sync + 'static,
105 T: serde::de::DeserializeOwned + Clone + Send + Sync + 'static,
106{
107 let (data, set_data) = signal(None::<T>);
108 let (error, set_error) = signal_local(None::<UseTauriError>);
109 let (trigger, set_trigger) = signal(None::<(Args, Opts)>);
110
111 let init = StoredValue::new(move |cmd: &'static str, args: Args, options: Opts| {
112 #[derive(Clone, serde::Serialize)]
113 struct OptionsWrapper<Opts>
114 where
115 Opts: serde::Serialize + Clone + 'static,
116 {
117 options: Opts
118 }
119
120 let args = args.clone();
121 let options = OptionsWrapper{
122 options: options.clone()
123 };
124
125 spawn_local_scoped(async move {
126 let args = match serde_wasm_bindgen::to_value(&args) {
127 Ok(value) => value,
128 Err(err) => {
129 set_error.set(Some(UseTauriError::Serialize(err.to_string())));
130 return;
131 }
132 };
133
134 let options = match serde_wasm_bindgen::to_value(&options) {
135 Ok(value) => value,
136 Err(err) => {
137 set_error.set(Some(UseTauriError::Serialize(err.to_string())));
138 return;
139 }
140 };
141
142 match invoke(cmd, args, options).await {
143 Ok(data) => {
144 match serde_wasm_bindgen::from_value::<T>(data) {
145 Ok(data) => set_data.set(Some(data)),
146 Err(err) => set_error.set(Some(UseTauriError::Deserialize(err.to_string())))
147 }
148 }
149 Err(err) => {
150 let err_str = err.as_string().unwrap_or_else(|| "Unknown error".to_string());
151 set_error.set(Some(UseTauriError::Command(cmd, err_str)))
152 }
153 }
154 });
155 });
156
157 Effect::new(move || {
158 let init = init.get_value();
159 if let Some((args, opts)) = trigger.get() {
160 init(cmd, args, opts);
161 }
162 set_trigger.update_untracked(|v| *v = None);
163 });
164
165 UseTauriReturn {
166 data: data.into(),
167 error: error.into(),
168 trigger: set_trigger
169 }
170}
171
172
173
174pub struct UseTauriReturn<Args, Opts, T>
175where
176 Args: serde::Serialize + Clone + 'static,
177 Opts: serde::Serialize + Clone + 'static,
178 T: serde::de::DeserializeOwned + Clone + Send + Sync + 'static,
179{
180 pub data: Signal<Option<T>>,
181 pub error: Signal<Option<UseTauriError>, LocalStorage>,
182 pub trigger: WriteSignal<Option<(Args, Opts)>>,
183}
184
185pub struct UseTauriWithReturn<O, T>
186where
187 O: serde::Serialize + Clone + 'static,
188 T: serde::de::DeserializeOwned + Clone + Send + Sync + 'static,
189{
190 pub data: Signal<Option<T>>,
191 pub error: Signal<Option<UseTauriError>, LocalStorage>,
192 pub trigger: WriteSignal<Option<O>>,
193}
194
195#[derive(Clone, Debug)]
196pub enum UseTauriError {
197 Command(&'static str, String),
198 Serialize(String),
199 Deserialize(String),
200}
201
202impl fmt::Display for UseTauriError {
203 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
204 match self {
205 UseTauriError::Command(place, err) => write!(f, "Command error in {}: {}", place, err),
206 UseTauriError::Serialize(err) => write!(f, "Error serializing value: {}", err),
207 UseTauriError::Deserialize(err) => write!(f, "Error deserializing value: {}", err),
208 }
209 }
210}
211
212#[wasm_bindgen]
213extern "C" {
214 #[wasm_bindgen(catch, js_namespace = ["window", "__TAURI__", "core"])]
215 async fn invoke(cmd: &str, args: JsValue, options: JsValue) -> Result<JsValue, JsValue>;
216}