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}