1use std::{collections::HashMap, rc::Rc};
2use vertigo::{Computed, Css, DomElement, Value, transaction};
3
4use super::{
5 DataFieldValue, FormExport,
6 data_field::{BoolValue, DictValue, ImageValue, ListValue, MultiValue, StringValue},
7};
8
9#[derive(Default)]
40pub struct FormData {
41 pub sections: Vec<DataSection>,
42 pub tabs: Vec<(String, Rc<Vec<DataSection>>)>,
43 pub top_controls: ControlsConfig,
44 pub bottom_controls: ControlsConfig,
45}
46
47#[derive(Default)]
48pub struct ControlsConfig {
49 pub css: Option<Css>,
50 pub submit: bool,
51 pub delete: bool,
52}
53
54impl ControlsConfig {
55 pub fn full() -> Self {
56 Self {
57 css: None,
58 submit: true,
59 delete: true,
60 }
61 }
62
63 pub fn with_css(mut self, css: Css) -> Self {
64 self.css = Some(css);
65 self
66 }
67}
68
69impl FormData {
70 pub fn with(mut self, section: DataSection) -> Self {
72 self.sections.push(section);
73 self
74 }
75
76 pub fn add_tab(mut self, tab_label: impl Into<String>, sections: Vec<DataSection>) -> Self {
78 self.tabs.push((tab_label.into(), Rc::new(sections)));
79 self
80 }
81
82 pub fn add_top_controls(mut self) -> Self {
83 self.top_controls = ControlsConfig::full();
84 self
85 }
86
87 pub fn add_top_controls_styled(mut self, css: Css) -> Self {
88 self.top_controls = ControlsConfig::full().with_css(css);
89 self
90 }
91
92 pub fn add_bottom_controls(mut self) -> Self {
93 self.bottom_controls = ControlsConfig::full();
94 self
95 }
96
97 pub fn add_bottom_controls_styled(mut self, css: Css) -> Self {
98 self.bottom_controls = ControlsConfig::full().with_css(css);
99 self
100 }
101
102 pub fn export(&self) -> FormExport {
103 let mut hash_map = HashMap::new();
104 transaction(|ctx| {
105 for (_, sections) in &self.tabs {
106 for section in sections.iter() {
107 for field in §ion.fields {
108 hash_map.insert(field.key.clone(), field.value.export(ctx));
109 }
110 }
111 }
112 for section in &self.sections {
113 for field in §ion.fields {
114 hash_map.insert(field.key.clone(), field.value.export(ctx));
115 }
116 }
117 });
118 FormExport::new(hash_map)
119 }
120}
121
122#[derive(Clone, Copy, Default, PartialEq)]
124pub enum FieldsetStyle {
125 #[default]
127 Plain,
128 Dimensions,
130}
131
132#[derive(Default)]
134pub struct DataSection {
136 pub label: String,
137 pub fields: Vec<DataField>,
138 pub error: Option<String>,
139 pub render: Option<Rc<dyn Fn(Vec<DataField>) -> DomElement>>,
140 pub fieldset_style: FieldsetStyle,
141 pub fieldset_css: Option<Css>,
142 pub new_group: bool,
143}
144
145#[derive(Clone)]
147pub struct DataField {
148 pub key: String,
149 pub value: DataFieldValue,
150}
151
152impl DataSection {
153 pub fn new(label: impl Into<String>) -> Self {
155 Self {
156 label: label.into(),
157 ..Default::default()
158 }
159 }
160
161 pub fn with_string_field(
163 label: impl Into<String>,
164 key: impl Into<String>,
165 original_value: impl Into<String>,
166 ) -> Self {
167 let value = original_value.into();
168 Self {
169 label: label.into(),
170 fields: vec![DataField {
171 key: key.into(),
172 value: DataFieldValue::String(StringValue {
173 value: Value::new(value.clone()),
174 original_value: Rc::new(value),
175 }),
176 }],
177 ..Default::default()
178 }
179 }
180
181 pub fn with_opt_string_field(
183 label: impl Into<String>,
184 key: impl Into<String>,
185 original_value: &Option<String>,
186 ) -> Self {
187 Self::with_string_field(label, key, original_value.clone().unwrap_or_default())
188 }
189
190 pub fn add_field(mut self, key: impl Into<String>, value: DataFieldValue) -> Self {
191 self.fields.push(DataField {
192 key: key.into(),
193 value,
194 });
195 self
196 }
197
198 pub fn add_string_field(
200 mut self,
201 key: impl Into<String>,
202 original_value: impl Into<String>,
203 ) -> Self {
204 let value = original_value.into();
205 self.fields.push(DataField {
206 key: key.into(),
207 value: DataFieldValue::String(StringValue {
208 value: Value::new(value.clone()),
209 original_value: Rc::new(value),
210 }),
211 });
212 self
213 }
214
215 pub fn add_opt_string_field(
217 self,
218 key: impl Into<String>,
219 original_value: &Option<String>,
220 ) -> Self {
221 self.add_string_field(key, original_value.clone().unwrap_or_default())
222 }
223
224 pub fn add_list_field(
226 mut self,
227 key: impl Into<String>,
228 original_value: Option<impl Into<String>>,
229 options: Vec<String>,
230 ) -> Self {
231 let value = original_value.map(|s| s.into());
232 let options = Computed::from(move |_ctx| options.clone());
233 self.fields.push(DataField {
234 key: key.into(),
235 value: DataFieldValue::List(ListValue {
236 value: Value::new(value.clone().unwrap_or_default()),
237 original_value: value.map(Rc::new),
238 options,
239 }),
240 });
241 self
242 }
243
244 pub fn add_static_dict_field(
247 self,
248 key: impl Into<String>,
249 original_value: Option<i64>,
250 options: Vec<(i64, String)>,
251 ) -> Self {
252 let options = Computed::from(move |_ctx| options.clone());
253 self.add_dict_field(key, original_value, options)
254 }
255
256 pub fn add_dict_field(
259 mut self,
260 key: impl Into<String>,
261 original_value: Option<i64>,
262 options: Computed<Vec<(i64, String)>>,
263 ) -> Self {
264 self.fields.push(DataField {
265 key: key.into(),
266 value: DataFieldValue::Dict(DictValue {
267 value: Value::new(original_value.unwrap_or_default()),
268 original_value: original_value.map(Rc::new),
269 options,
270 }),
271 });
272 self
273 }
274
275 pub fn add_multiselect_field(
277 mut self,
278 key: impl Into<String>,
279 original_value: Vec<i64>,
280 options: Computed<HashMap<i64, String>>,
281 add_label: impl Into<String>,
282 ) -> Self {
283 self.fields.push(DataField {
284 key: key.into(),
285 value: DataFieldValue::Multi(MultiValue {
286 value: Value::new(original_value.iter().cloned().map(Value::new).collect()),
287 original_value: Rc::new(original_value),
288 options,
289 add_label: Rc::new(add_label.into()),
290 }),
291 });
292 self
293 }
294
295 pub fn add_bool_field(
297 mut self,
298 key: impl Into<String>,
299 original_value: Option<impl Into<bool>>,
300 ) -> Self {
301 let value = original_value.map(|b| b.into());
302 self.fields.push(DataField {
303 key: key.into(),
304 value: DataFieldValue::Bool(BoolValue {
305 value: Value::new(value.unwrap_or_default()),
306 original_value: value.map(Rc::new),
307 }),
308 });
309 self
310 }
311
312 pub fn add_image_field(
314 mut self,
315 key: impl Into<String>,
316 original_value: Option<impl Into<String>>,
317 ) -> Self {
318 let value = original_value.map(|l| l.into());
319 self.fields.push(DataField {
320 key: key.into(),
321 value: DataFieldValue::Image(ImageValue {
322 value: Value::new(None),
323 original_link: value.map(Rc::new),
324 component_params: None,
325 }),
326 });
327 self
328 }
329
330 pub fn set_fieldset_style(mut self, fieldset_style: FieldsetStyle) -> Self {
332 self.fieldset_style = fieldset_style;
333 self
334 }
335
336 pub fn set_fieldset_css(mut self, fieldset_css: Css) -> Self {
338 self.fieldset_css = Some(fieldset_css);
339 self
340 }
341
342 pub fn starts_new_group(mut self) -> Self {
344 self.new_group = true;
345 self
346 }
347}