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 {
135 pub label: String,
136 pub fields: Vec<DataField>,
137 pub error: Option<String>,
138 pub render: Option<Rc<dyn Fn(Vec<DataField>) -> DomElement>>,
139 pub fieldset_style: FieldsetStyle,
140 pub fieldset_css: Option<Css>,
141 pub new_group: bool,
142}
143
144#[derive(Clone)]
146pub struct DataField {
147 pub key: String,
148 pub value: DataFieldValue,
149}
150
151impl DataSection {
152 pub fn new(label: impl Into<String>) -> Self {
154 Self {
155 label: label.into(),
156 ..Default::default()
157 }
158 }
159
160 pub fn with_string_field(
162 label: impl Into<String>,
163 key: impl Into<String>,
164 original_value: impl Into<String>,
165 ) -> Self {
166 let value = original_value.into();
167 Self {
168 label: label.into(),
169 fields: vec![DataField {
170 key: key.into(),
171 value: DataFieldValue::String(StringValue {
172 value: Value::new(value.clone()),
173 original_value: Rc::new(value),
174 }),
175 }],
176 ..Default::default()
177 }
178 }
179
180 pub fn with_opt_string_field(
182 label: impl Into<String>,
183 key: impl Into<String>,
184 original_value: &Option<String>,
185 ) -> Self {
186 Self::with_string_field(label, key, original_value.clone().unwrap_or_default())
187 }
188
189 pub fn add_field(mut self, key: impl Into<String>, value: DataFieldValue) -> Self {
190 self.fields.push(DataField {
191 key: key.into(),
192 value,
193 });
194 self
195 }
196
197 pub fn add_string_field(
199 mut self,
200 key: impl Into<String>,
201 original_value: impl Into<String>,
202 ) -> Self {
203 let value = original_value.into();
204 self.fields.push(DataField {
205 key: key.into(),
206 value: DataFieldValue::String(StringValue {
207 value: Value::new(value.clone()),
208 original_value: Rc::new(value),
209 }),
210 });
211 self
212 }
213
214 pub fn add_opt_string_field(
216 self,
217 key: impl Into<String>,
218 original_value: &Option<String>,
219 ) -> Self {
220 self.add_string_field(key, original_value.clone().unwrap_or_default())
221 }
222
223 pub fn add_list_field(
225 mut self,
226 key: impl Into<String>,
227 original_value: Option<impl Into<String>>,
228 options: Vec<String>,
229 ) -> Self {
230 let value = original_value.map(|s| s.into());
231 let options = Computed::from(move |_ctx| options.clone());
232 self.fields.push(DataField {
233 key: key.into(),
234 value: DataFieldValue::List(ListValue {
235 value: Value::new(value.clone().unwrap_or_default()),
236 original_value: value.map(Rc::new),
237 options,
238 }),
239 });
240 self
241 }
242
243 pub fn add_static_dict_field(
246 self,
247 key: impl Into<String>,
248 original_value: Option<i64>,
249 options: Vec<(i64, String)>,
250 ) -> Self {
251 let options = Computed::from(move |_ctx| options.clone());
252 self.add_dict_field(key, original_value, options)
253 }
254
255 pub fn add_dict_field(
258 mut self,
259 key: impl Into<String>,
260 original_value: Option<i64>,
261 options: Computed<Vec<(i64, String)>>,
262 ) -> Self {
263 self.fields.push(DataField {
264 key: key.into(),
265 value: DataFieldValue::Dict(DictValue {
266 value: Value::new(original_value.unwrap_or_default()),
267 original_value: original_value.map(Rc::new),
268 options,
269 }),
270 });
271 self
272 }
273
274 pub fn add_multiselect_field(
276 mut self,
277 key: impl Into<String>,
278 original_value: Vec<i64>,
279 options: Computed<HashMap<i64, String>>,
280 add_label: impl Into<String>,
281 ) -> Self {
282 self.fields.push(DataField {
283 key: key.into(),
284 value: DataFieldValue::Multi(MultiValue {
285 value: Value::new(original_value.iter().cloned().map(Value::new).collect()),
286 original_value: Rc::new(original_value),
287 options,
288 add_label: Rc::new(add_label.into()),
289 }),
290 });
291 self
292 }
293
294 pub fn add_bool_field(
296 mut self,
297 key: impl Into<String>,
298 original_value: Option<impl Into<bool>>,
299 ) -> Self {
300 let value = original_value.map(|b| b.into());
301 self.fields.push(DataField {
302 key: key.into(),
303 value: DataFieldValue::Bool(BoolValue {
304 value: Value::new(value.unwrap_or_default()),
305 original_value: value.map(Rc::new),
306 }),
307 });
308 self
309 }
310
311 pub fn add_image_field(
313 mut self,
314 key: impl Into<String>,
315 original_value: Option<impl Into<String>>,
316 ) -> Self {
317 let value = original_value.map(|l| l.into());
318 self.fields.push(DataField {
319 key: key.into(),
320 value: DataFieldValue::Image(ImageValue {
321 value: Value::new(None),
322 original_link: value.map(Rc::new),
323 component_params: None,
324 }),
325 });
326 self
327 }
328
329 pub fn set_fieldset_style(mut self, fieldset_style: FieldsetStyle) -> Self {
331 self.fieldset_style = fieldset_style;
332 self
333 }
334
335 pub fn set_fieldset_css(mut self, fieldset_css: Css) -> Self {
337 self.fieldset_css = Some(fieldset_css);
338 self
339 }
340
341 pub fn starts_new_group(mut self) -> Self {
343 self.new_group = true;
344 self
345 }
346}