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
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}