tilo_app/framework/
form.rs1use std::any::Any;
9use std::collections::BTreeMap;
10use std::marker::PhantomData;
11use std::sync::{Arc, Mutex};
12
13use serde::Serialize;
14use tilo_actions::ActionBinding;
15use tilo_resource::{Invoke, Params, Path, Resource, Segment};
16use ui_core::LocalState;
17
18use super::controls::Actions;
19use super::dispatch::Submit;
20use tilo_lock::MutexRecover as _;
21
22trait Slot: Send + Sync {
24 fn json(&self) -> Result<serde_json::Value, String>;
25 fn clear(&self);
26 fn any(&self) -> &dyn Any;
27}
28
29struct Typed<V: Send + Sync + 'static>(LocalState<V>);
30
31impl<V> Slot for Typed<V>
32where
33 V: Serialize + Default + Send + Sync + 'static,
34{
35 fn json(&self) -> Result<serde_json::Value, String> {
36 self.0
37 .with(|value| serde_json::to_value(value))
38 .map_err(|error| error.to_string())
39 }
40
41 fn clear(&self) {
42 self.0.set(V::default());
43 }
44
45 fn any(&self) -> &dyn Any {
46 &self.0
47 }
48}
49
50type Slots = Arc<Mutex<BTreeMap<String, Box<dyn Slot>>>>;
51
52pub struct Form<P: Params> {
55 slots: Slots,
56 marker: PhantomData<fn() -> P>,
57}
58
59impl<P: Params> Clone for Form<P> {
60 fn clone(&self) -> Self {
61 Self {
62 slots: Arc::clone(&self.slots),
63 marker: PhantomData,
64 }
65 }
66}
67
68impl<P: Params> Form<P> {
69 pub(crate) fn new() -> Self {
70 Self {
71 slots: Arc::default(),
72 marker: PhantomData,
73 }
74 }
75
76 pub(crate) fn field<V>(&self, path: &Path<P, V>) -> LocalState<V>
78 where
79 V: Serialize + Default + Send + Sync + 'static,
80 {
81 let key = match path.segments() {
82 [Segment::Field(key)] => key.clone(),
83 other => other
84 .iter()
85 .map(ToString::to_string)
86 .collect::<Vec<_>>()
87 .join("."),
88 };
89 let mut slots = self.slots.lock_recover();
90 if let Some(state) = slots
91 .get(&key)
92 .and_then(|slot| slot.any().downcast_ref::<LocalState<V>>())
93 {
94 return state.clone();
95 }
96 let state = LocalState::new(V::default());
97 slots.insert(key, Box::new(Typed(state.clone())));
98 state
99 }
100
101 #[must_use]
104 pub fn paths(&self) -> P::Paths {
105 P::paths()
106 }
107
108 pub fn values(&self) -> Result<P, String> {
113 read::<P>(&self.slots)
114 }
115
116 pub fn clear(&self) {
118 clear(&self.slots);
119 }
120
121 #[must_use]
125 pub fn submit(&self) -> FormSubmit<P> {
126 FormSubmit {
127 slots: Arc::clone(&self.slots),
128 marker: PhantomData,
129 }
130 }
131}
132
133fn read<P: Params>(slots: &Slots) -> Result<P, String> {
134 let fields = slots
135 .lock_recover()
136 .iter()
137 .map(|(key, slot)| Ok((key.clone(), slot.json()?)))
138 .collect::<Result<serde_json::Map<_, _>, String>>()?;
139 serde_json::from_value(serde_json::Value::Object(fields)).map_err(|error| error.to_string())
140}
141
142fn clear(slots: &Slots) {
143 for slot in slots.lock_recover().values() {
144 slot.clear();
145 }
146}
147
148pub struct FormSubmit<P: Params> {
150 slots: Slots,
151 marker: PhantomData<fn() -> P>,
152}
153
154impl<P> Submit<P::Resource> for FormSubmit<P>
155where
156 P: Params,
157 P::Resource: Resource,
158{
159 fn action_id(&self) -> &'static str {
160 P::ACTION.id
161 }
162
163 fn into_invoke(self) -> Result<Invoke<P::Resource>, String> {
164 let params = read::<P>(&self.slots)?;
165 clear(&self.slots);
166 Ok(P::ACTION.with(params))
167 }
168
169 fn control_binding(self, actions: &Actions<P::Resource>) -> Option<ActionBinding<()>> {
170 let slots = self.slots;
171 actions.deferred_binding(P::ACTION.key(), move || {
172 let params = read::<P>(&slots)?;
173 let arguments = P::ACTION.arguments(¶ms)?;
174 clear(&slots);
175 Ok(arguments)
176 })
177 }
178}
179
180pub enum Attached<V> {
183 Record {
185 id: String,
186 state: LocalState<V>,
187 binding: Option<Box<ActionBinding<V>>>,
188 },
189 Local { id: String, state: LocalState<V> },
191}
192
193#[doc(hidden)]
195pub enum RecordField {}
196
197#[doc(hidden)]
199pub enum FormField {}
200
201#[derive(Clone, Default)]
203pub(crate) struct Forms(
204 std::rc::Rc<std::cell::RefCell<std::collections::HashMap<std::any::TypeId, Box<dyn Any>>>>,
205);
206
207impl Forms {
208 pub(crate) fn get<P: Params>(&self) -> Form<P> {
209 let key = std::any::TypeId::of::<P>();
210 let mut forms = self.0.borrow_mut();
211 if let Some(form) = forms
212 .get(&key)
213 .and_then(|form| form.downcast_ref::<Form<P>>())
214 {
215 return form.clone();
216 }
217 let form = Form::<P>::new();
218 forms.insert(key, Box::new(form.clone()));
219 form
220 }
221}