1use std::{collections::HashMap, future::Future, pin::Pin, sync::Arc};
2
3use quokka::state::FromState;
4pub use quokka_admin_macros::{AdminCreateForm, AdminUpdateForm};
5
6#[derive(Clone)]
7pub struct FormBuilder<S> {
8 state: S,
9}
10
11pub trait AdminCreateForm<S> {
12 fn entity_name() -> &'static str;
13 fn get_form() -> Form<S>;
14 fn create_query(self, state: &S) -> impl Future<Output = quokka::Result<()>> + Send;
15}
16
17pub trait AdminUpdateForm<S>: Sized {
18 type PrimaryKeys;
19
20 fn entity_name() -> &'static str;
21
22 fn get_form() -> Form<S>;
23
24 fn update_query(self, state: &S) -> impl Future<Output = quokka::Result<()>> + Send;
25
26 fn get_query(
27 state: &S,
28 pks: Self::PrimaryKeys,
29 ) -> impl Future<Output = quokka::Result<Self>> + Send;
30}
31
32#[derive(Clone)]
33pub struct Form<S> {
34 pub entity_name: String,
35 pub action: String,
36 pub fields: Vec<Arc<dyn FormField<S> + Send + Sync>>,
37}
38
39pub struct FormFieldPreProcessorContext<'a> {
40 pub field: &'a mut FormFieldData,
41}
42
43pub trait FormFieldPreProcessor {
44 fn process_form_data(
45 &self,
46 context: FormFieldPreProcessorContext<'_>,
47 ) -> Pin<Box<dyn Future<Output = quokka::Result<()>> + Send + Sync>>;
48}
49
50pub trait FormField<S> {
51 fn template(&self) -> String;
52
53 fn name(&self) -> String;
54
55 fn label(&self) -> String;
56
57 fn default(&self) -> Option<String> {
58 None
59 }
60
61 fn required(&self) -> bool {
62 false
63 }
64
65 fn additional_options(&self) -> HashMap<String, serde_json::Value> {
66 HashMap::new()
67 }
68
69 fn to_form_field_data(
70 &self,
71 state: &S,
72 ) -> Pin<Box<dyn Future<Output = quokka::Result<FormFieldData>> + Send + Sync>> {
73 let data = FormFieldData {
74 template: self.template().to_string(),
75 name: self.name().to_string(),
76 label: self.label().to_string(),
77 default: self.default(),
78 required: self.required(),
79 additional_options: self.additional_options(),
80 };
81
82 let processor = self.processor(state);
83
84 Box::pin(async move {
85 let mut data = data.clone();
86
87 if let Some(processor) = processor {
88 let ctx = FormFieldPreProcessorContext { field: &mut data };
89
90 processor.process_form_data(ctx).await?;
91 }
92
93 Ok(data)
94 })
95 }
96
97 fn processor(&self, _state: &S) -> Option<Box<dyn FormFieldPreProcessor + Send + Sync>> {
98 None
99 }
100}
101
102#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
103pub struct FormFieldData {
104 pub template: String,
105 pub name: String,
106 pub label: String,
107 pub default: Option<String>,
108 pub required: bool,
109 pub additional_options: HashMap<String, serde_json::Value>,
110}
111
112#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
113pub struct FormProperties {
114 pub action: String,
115 pub entity: String,
116 pub fields: Vec<FormFieldData>,
117}
118
119pub mod fields {
120 use super::FormField;
121 use std::collections::HashMap;
122
123 macro_rules! input_field {
124 ($(#[$attr:meta])* $field_name:ident, $template:expr) => {
125 $(#[$attr])*
126 #[derive(Clone, Debug, Default)]
127 pub struct $field_name {
128 pub name: String,
129 pub label: String,
130 pub template: String,
131 pub default: Option<String>,
132 pub required: bool,
133 pub additional_options: HashMap<String, serde_json::Value>,
134 }
135
136 impl<S> FormField<S> for $field_name {
137 fn name(&self) -> String {
138 self.name.clone()
139 }
140
141 fn label(&self) -> String {
142 self.label.clone()
143 }
144
145 fn template(&self) -> String {
146 self.template.clone()
147 }
148
149 fn default(&self) -> Option<String> {
150 self.default.clone()
151 }
152
153 fn required(&self) -> bool {
154 self.required
155 }
156
157 fn additional_options(&self) -> HashMap<String, serde_json::Value> {
158 self.additional_options.clone()
159 }
160 }
161
162 impl $field_name {
163 pub fn new(name: impl ToString, label: impl ToString) -> Self {
164 Self {
165 name: name.to_string(),
166 label: label.to_string(),
167 template: $template.to_string(),
168 ..Default::default()
169 }
170 }
171
172 pub fn set_template(mut self, new: String) -> Self {
173 self.template = new;
174
175 self
176 }
177
178 pub fn set_label(mut self, new: String) -> Self {
179 self.label = new;
180
181 self
182 }
183
184 pub fn set_default(mut self, new: Option<String>) -> Self {
185 self.default = new;
186
187 self
188 }
189
190 pub fn set_required(mut self, new: bool) -> Self {
191 self.required = new;
192
193 self
194 }
195 }
196 };
197 }
198
199 input_field!(
200 TextField,
202 "partials/admin/field/text_field"
203 );
204 input_field!(
205 PasswordField,
207 "partials/admin/field/text_field/password"
208 );
209 input_field!(
210 NumberField,
212 "partials/admin/field/number_field"
213 );
214 input_field!(
215 CheckboxField,
217 "partials/admin/field/checkbox_field"
218 );
219 input_field!(
220 SelectField,
223 "partials/admin/field/select_field/combo_box"
224 );
225 input_field!(
226 HiddenField,
229 "partials/admin/field/hidden_field"
230 );
231 input_field!(
232 DisplayField,
235 "partials/admin/field/display_field"
236 );
237 input_field!(
238 HtmlInputField,
242 "partials/admin/field/html_input_field"
243 );
244
245 pub enum SelectFieldStyle {
250 Combobox,
251 Radio,
252 }
253
254 impl SelectField {
255 pub fn add_option(mut self, label: impl ToString, value: impl ToString) -> Self {
256 self.additional_options
257 .entry("options".to_string())
258 .and_modify(|entry| {
259 if let serde_json::Value::Array(values) = entry {
260 values.push(serde_json::json! {{
261 "label": label.to_string(),
262 "value": value.to_string(),
263 }});
264 }
265 })
266 .or_insert(serde_json::json!([{
267 "label": label.to_string(),
268 "value": value.to_string(),
269 }]));
270
271 self
272 }
273
274 pub fn style(mut self, style: SelectFieldStyle) -> Self {
275 match style {
276 SelectFieldStyle::Combobox => {
277 self.template = String::from("partials/admin/field/select_field/combo_box")
278 }
279 SelectFieldStyle::Radio => {
280 self.template = String::from("partials/admin/field/select_field/radio_buttons")
281 }
282 }
283
284 self
285 }
286 }
287
288 impl HtmlInputField {
289 pub fn set_type(mut self, typ: impl ToString) -> Self {
290 self.additional_options
291 .insert("type".to_string(), typ.to_string().into());
292
293 self
294 }
295
296 pub fn set_attribute(mut self, name: impl ToString, value: impl ToString) -> Self {
297 self.additional_options
298 .entry("attributes".to_string())
299 .and_modify(|entry| {
300 if let serde_json::Value::Object(object) = entry {
301 object.insert(name.to_string(), value.to_string().into());
302 }
303 })
304 .or_insert(serde_json::json! {{ name.to_string(): value.to_string() }});
305
306 self
307 }
308 }
309}
310
311impl<S: Clone + Send + Sync + 'static> FromState<S> for FormBuilder<S> {
312 fn from_state(state: &S) -> Self {
313 Self {
314 state: state.clone(),
315 }
316 }
317}
318
319impl<S: Send + Sync + 'static> FormBuilder<S> {
320 pub async fn construct_form_data(&self, form: Form<S>) -> quokka::Result<FormProperties> {
321 let fields = futures::future::try_join_all(
322 form.fields
323 .into_iter()
324 .map(|field| field.to_form_field_data(&self.state)),
325 )
326 .await?;
327
328 let form = FormProperties {
329 action: format!("/admin/entity/{}/{}", form.entity_name, form.action),
330 fields,
331 entity: form.entity_name,
332 };
333
334 Ok(form)
335 }
336}
337
338impl<S> Form<S> {
339 pub fn new(entity_name: impl ToString, action: impl ToString) -> Self {
340 Self {
341 entity_name: entity_name.to_string(),
342 action: action.to_string(),
343 fields: Default::default(),
344 }
345 }
346
347 pub fn add_field(mut self, field: impl FormField<S> + Send + Sync + 'static) -> Self {
348 self.fields.push(Arc::new(field));
349
350 self
351 }
352}