Skip to main content

hyle_dioxus/
query.rs

1use std::{
2    future::Future,
3    hash::{Hash, Hasher},
4    pin::Pin,
5    rc::Rc,
6    sync::Arc,
7};
8
9use dioxus::prelude::*;
10use dioxus_fullstack_core::{use_server_future, ServerFnError};
11use dioxus_query::prelude::*;
12use dioxus_signals::{Signal, WritableExt};
13
14use crate::{HyleAdapter, HyleMutation, HyleSourceState, UseSource};
15use hyle::{MutateInput, Source};
16
17pub type InvalidationSignal = Signal<u32>;
18
19type MutateFn =
20    Arc<dyn Fn(MutateInput) -> Pin<Box<dyn Future<Output = Result<(), String>>>> + 'static>;
21
22#[derive(Clone)]
23struct HyleMutationQuery {
24    mutate: MutateFn,
25    on_success: Option<Rc<dyn Fn()>>,
26}
27
28impl PartialEq for HyleMutationQuery {
29    fn eq(&self, _other: &Self) -> bool { true }
30}
31impl Eq for HyleMutationQuery {}
32impl Hash for HyleMutationQuery {
33    fn hash<H: Hasher>(&self, _state: &mut H) {}
34}
35
36impl MutationCapability for HyleMutationQuery {
37    type Ok = ();
38    type Err = String;
39    type Keys = String;
40
41    async fn run(&self, input_json: &Self::Keys) -> Result<Self::Ok, Self::Err> {
42        let input: MutateInput =
43            serde_json::from_str(input_json).map_err(|e| e.to_string())?;
44        (self.mutate)(input).await
45    }
46
47    async fn on_settled(&self, _input_json: &Self::Keys, result: &Result<Self::Ok, Self::Err>) {
48        if result.is_ok() {
49            if let Some(cb) = &self.on_success {
50                cb();
51            }
52        }
53    }
54}
55
56#[derive(Default)]
57pub struct DioxusMutationOptions {
58    pub on_success: Option<Rc<dyn Fn()>>,
59}
60
61pub fn use_dioxus_mutation<F, Fut>(
62    mutate_fn: F,
63    options: DioxusMutationOptions,
64) -> HyleMutation
65where
66    F: Fn(MutateInput) -> Fut + 'static + Clone,
67    Fut: Future<Output = Result<(), String>> + 'static,
68{
69    let mutate: MutateFn = Arc::new(move |input| Box::pin(mutate_fn(input)));
70    let on_success = options.on_success;
71
72    let capability = HyleMutationQuery {
73        mutate: mutate.clone(),
74        on_success: on_success.clone(),
75    };
76    let dq_mutation = use_mutation(Mutation::new(capability));
77
78    let mut is_pending = use_signal(|| false);
79    let mut is_success = use_signal(|| false);
80    let mut error: Signal<Option<String>> = use_signal(|| None);
81
82    let mutate_cb = Callback::new(move |input: MutateInput| {
83        is_pending.set(true);
84        is_success.set(false);
85        error.set(None);
86        spawn(async move {
87            let input_json = serde_json::to_string(&input).unwrap_or_default();
88            dq_mutation.mutate_async(input_json).await;
89            let reader = dq_mutation.peek();
90            let state = reader.state();
91            is_pending.set(false);
92            match &*state {
93                MutationStateData::Settled { res: Ok(()), .. } => {
94                    is_success.set(true);
95                    if let Some(mut inv) = try_consume_context::<InvalidationSignal>() {
96                        *inv.write() += 1;
97                    }
98                }
99                MutationStateData::Settled { res: Err(e), .. } => {
100                    error.set(Some(e.clone()));
101                }
102                _ => {}
103            }
104        });
105    });
106
107    let reset_cb = Callback::new(move |_: ()| {
108        is_pending.set(false);
109        is_success.set(false);
110        error.set(None);
111    });
112
113    HyleMutation {
114        mutate: mutate_cb,
115        reset: reset_cb,
116        is_pending,
117        is_success,
118        error,
119    }
120}
121
122pub fn use_fullstack_source<F, Fut>(fetch_fn: F) -> UseSource
123where
124    F: Fn() -> Fut + Clone + 'static,
125    Fut: Future<Output = Result<Source, ServerFnError>> + 'static,
126{
127    let invalidation = try_consume_context::<InvalidationSignal>();
128
129    let future = use_server_future(move || {
130        if let Some(inv) = invalidation {
131            let _ = inv.read();
132        }
133        fetch_fn()
134    });
135
136    use_memo(move || match &future {
137        Ok(f) => match &*f.read() {
138            Some(Ok(src))  => HyleSourceState::Ready(src.clone()),
139            Some(Err(e))   => HyleSourceState::Error(e.to_string()),
140            None           => HyleSourceState::Loading,
141        },
142        Err(_suspended) => HyleSourceState::Loading,
143    }).into()
144}
145
146pub fn make_fullstack_adapter<SF, SFut, CF, CFut, UF, UFut, DF, DFut>(
147    source_fn: SF,
148    create_fn: CF,
149    update_fn: UF,
150    delete_fn: DF,
151) -> HyleAdapter
152where
153    SF: Fn() -> SFut + Clone + 'static,
154    SFut: Future<Output = Result<Source, ServerFnError>> + 'static,
155    CF: Fn(MutateInput) -> CFut + Clone + 'static,
156    CFut: Future<Output = Result<(), String>> + 'static,
157    UF: Fn(MutateInput) -> UFut + Clone + 'static,
158    UFut: Future<Output = Result<(), String>> + 'static,
159    DF: Fn(MutateInput) -> DFut + Clone + 'static,
160    DFut: Future<Output = Result<(), String>> + 'static,
161{
162    let source = use_fullstack_source(source_fn);
163    let create = use_dioxus_mutation(create_fn, DioxusMutationOptions::default());
164    let update = use_dioxus_mutation(update_fn, DioxusMutationOptions::default());
165    let delete = use_dioxus_mutation(delete_fn, DioxusMutationOptions::default());
166    HyleAdapter { source, create, update, delete }
167}