tauri_use/
use_invoke.rs

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
13/// An alias function of `use_invoke` without options or parameters 
14/// 
15/// ```rust
16/// #[component]
17/// pub fn Demo() -> impl IntoView {
18///     let UseTauriWithReturn { 
19///         data: demo, 
20///         trigger: list,
21///         ..
22///     } = use_command::<Vec<Demo>>("list_data");
23///     
24///     view!{
25///         ...
26///         <button on:click=move |_| list.set(Some(()))>list data</button>
27///         ...
28///     }
29/// }
30/// 
31/// ```
32pub 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
60/// An alias function of `use_invoke` without options 
61/// 
62/// ```rust
63/// #[component]
64/// pub fn Demo() -> impl IntoView {
65///     let UseTauriWithReturn { 
66///         data: demo, 
67///         trigger: delete,
68///         ..
69///     } = use_invoke_with_args::<IdWrapper, ()>("delete_data");;
70///     
71///     view!{
72///         ...
73///         <button on:click=move |_| delete.set(Some(IdWrapper::new(id)))>delete data</button>
74///         ...
75///     }
76/// }
77/// 
78/// ```
79pub 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
108/// An alias function of `use_invoke` without parameters 
109/// 
110/// ```rust
111/// #[component]
112/// pub fn Demo() -> impl IntoView {
113///     let UseTauriWithReturn { 
114///         data: demo, 
115///         trigger: config,
116///         ..
117///     } = use_invoke_with_options::<Options, ()>("config_data");;
118///     
119///     view!{
120///         ...
121///         <button on:click=move |_| config.set(Some(Options::new(...)))>config data</button>
122///         ...
123///     }
124/// }
125/// 
126/// ```
127pub 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
156/// A `leptos` wrapper for Tauri's native `invoke` function.
157pub 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}