ftd_rt/
ui.rs

1#[derive(serde::Deserialize, Clone)]
2#[cfg_attr(not(feature = "wasm"), derive(Debug, PartialEq, serde::Serialize))]
3#[serde(tag = "type")]
4pub enum Element {
5    Text(Text),
6    TextBlock(TextBlock),
7    Code(Code),
8    Image(Image),
9    Row(Row),
10    Column(Column),
11    IFrame(IFrame),
12    Input(Input),
13    Integer(Text),
14    Boolean(Text),
15    Decimal(Text),
16    Scene(Scene),
17    Null,
18}
19
20impl Element {
21    pub fn set_id(
22        children: &mut [ftd_rt::Element],
23        index_vec: &[usize],
24        external_id: Option<String>,
25    ) {
26        for (idx, child) in children.iter_mut().enumerate() {
27            let index_string: String = {
28                let mut index_vec = index_vec.to_vec();
29                index_vec.push(idx);
30                index_vec
31                    .iter()
32                    .map(|v| v.to_string())
33                    .collect::<Vec<String>>()
34                    .join(",")
35            };
36            let mut id = match child {
37                Self::Text(ftd_rt::Text {
38                    common: ftd_rt::Common { data_id: id, .. },
39                    ..
40                }) => id,
41                Self::TextBlock(ftd_rt::TextBlock {
42                    common: ftd_rt::Common { data_id: id, .. },
43                    ..
44                }) => id,
45                Self::Code(ftd_rt::Code {
46                    common: ftd_rt::Common { data_id: id, .. },
47                    ..
48                }) => id,
49                Self::Image(ftd_rt::Image {
50                    common: ftd_rt::Common { data_id: id, .. },
51                    ..
52                }) => id,
53                Self::Row(ftd_rt::Row {
54                    common: ftd_rt::Common { data_id: id, .. },
55                    container,
56                })
57                | Self::Column(ftd_rt::Column {
58                    common: ftd_rt::Common { data_id: id, .. },
59                    container,
60                })
61                | Self::Scene(ftd_rt::Scene {
62                    common: ftd_rt::Common { data_id: id, .. },
63                    container,
64                    ..
65                }) => {
66                    let mut index_vec = index_vec.to_vec();
67                    index_vec.push(idx);
68                    Self::set_id(&mut container.children, &index_vec, external_id.clone());
69                    if let Some((id, container, external_children)) =
70                        &mut container.external_children
71                    {
72                        if let Some(ftd_rt::Element::Column(col)) = external_children.first_mut() {
73                            let index_string: String = index_vec
74                                .iter()
75                                .map(|v| v.to_string())
76                                .collect::<Vec<String>>()
77                                .join(",");
78
79                            let external_id = Some({
80                                if let Some(ref ext_id) = external_id {
81                                    format!("{}.{}-external:{}", ext_id, id, index_string)
82                                } else {
83                                    format!("{}-external:{}", id, index_string)
84                                }
85                            });
86                            col.common.data_id = external_id.clone();
87                            if let Some(val) = container.first_mut() {
88                                index_vec.append(&mut val.to_vec());
89                                Self::set_id(&mut col.container.children, &index_vec, external_id);
90                            }
91                        }
92                    }
93                    id
94                }
95                Self::IFrame(ftd_rt::IFrame {
96                    common: ftd_rt::Common { data_id: id, .. },
97                    ..
98                }) => id,
99                Self::Input(ftd_rt::Input {
100                    common: ftd_rt::Common { data_id: id, .. },
101                    ..
102                }) => id,
103                Self::Integer(ftd_rt::Text {
104                    common: ftd_rt::Common { data_id: id, .. },
105                    ..
106                }) => id,
107                Self::Boolean(ftd_rt::Text {
108                    common: ftd_rt::Common { data_id: id, .. },
109                    ..
110                }) => id,
111                Self::Decimal(ftd_rt::Text {
112                    common: ftd_rt::Common { data_id: id, .. },
113                    ..
114                }) => id,
115                Self::Null => continue,
116            };
117
118            let external_id = {
119                if let Some(ref external_id) = external_id {
120                    format!(":{}", external_id)
121                } else {
122                    "".to_string()
123                }
124            };
125
126            if let Some(id) = &mut id {
127                *id = format!("{}:{}{}", id, index_string, external_id);
128            } else {
129                *id = Some(format!("{}{}", index_string, external_id));
130            }
131        }
132    }
133
134    pub fn get_external_children_condition(
135        &self,
136        external_open_id: &Option<String>,
137        external_children_container: &[Vec<usize>],
138    ) -> Vec<ftd_rt::ExternalChildrenCondition> {
139        let mut d: Vec<ftd_rt::ExternalChildrenCondition> = vec![];
140        let mut ext_child_condition = None;
141        let (id, open_id, children_container, children) = match self {
142            Self::Row(ftd_rt::Row {
143                common: ftd_rt::Common { data_id: id, .. },
144                container:
145                    ftd_rt::Container {
146                        external_children,
147                        children,
148                        ..
149                    },
150            })
151            | Self::Column(ftd_rt::Column {
152                common: ftd_rt::Common { data_id: id, .. },
153                container:
154                    ftd_rt::Container {
155                        external_children,
156                        children,
157                        ..
158                    },
159            })
160            | Self::Scene(ftd_rt::Scene {
161                common: ftd_rt::Common { data_id: id, .. },
162                container:
163                    ftd_rt::Container {
164                        external_children,
165                        children,
166                        ..
167                    },
168                ..
169            }) => (
170                id,
171                external_children
172                    .as_ref()
173                    .map(|(open_id, _, _)| open_id.to_string()),
174                external_children
175                    .as_ref()
176                    .map(|(_, children_container, _)| children_container.to_vec()),
177                children,
178            ),
179            _ => return d,
180        };
181
182        #[allow(clippy::blocks_in_if_conditions)]
183        if *external_open_id
184            == id.as_ref().map(|v| {
185                if v.contains(':') {
186                    let mut part = v.splitn(2, ':');
187                    part.next().unwrap().trim().to_string()
188                } else {
189                    v.to_string()
190                }
191            })
192            && external_children_container.is_empty()
193        {
194            ext_child_condition = id.clone();
195            if open_id.is_none() {
196                let id = ext_child_condition.expect("");
197                d.push(ftd_rt::ExternalChildrenCondition {
198                    condition: vec![id.to_string()],
199                    set_at: id,
200                });
201                return d;
202            }
203        }
204
205        let (open_id, external_children_container) =
206            if open_id.is_some() && external_children_container.is_empty() {
207                (open_id, {
208                    if let Some(c) = children_container {
209                        c
210                    } else {
211                        vec![]
212                    }
213                })
214            } else {
215                (
216                    external_open_id.clone(),
217                    external_children_container.to_vec(),
218                )
219            };
220
221        let mut index = 0;
222        for (i, v) in children.iter().enumerate() {
223            let external_container = {
224                let mut external_container = vec![];
225                while index < external_children_container.len() {
226                    if let Some(container) = external_children_container[index].get(0) {
227                        if container < &i {
228                            index += 1;
229                            continue;
230                        }
231                        let external_child_container =
232                            external_children_container[index][1..].to_vec();
233                        if container == &i && !external_child_container.is_empty() {
234                            external_container.push(external_child_container)
235                        } else {
236                            break;
237                        }
238                    }
239                    index += 1;
240                }
241                external_container
242            };
243            let conditions =
244                v.get_external_children_condition(&open_id, external_container.as_slice());
245            for mut condition in conditions {
246                if let Some(e) = &ext_child_condition {
247                    condition.condition.push(e.to_string());
248                }
249                d.push(condition);
250            }
251        }
252        d
253    }
254
255    pub fn get_external_children_dependencies(
256        children: &[ftd_rt::Element],
257    ) -> ftd_rt::ExternalChildrenDependenciesMap {
258        let mut d: ftd_rt::ExternalChildrenDependenciesMap = Default::default();
259        for child in children {
260            let container = match child {
261                ftd_rt::Element::Row(ftd_rt::Row { container, .. }) => container,
262                ftd_rt::Element::Column(ftd_rt::Column { container, .. }) => container,
263                ftd_rt::Element::Scene(ftd_rt::Scene { container, .. }) => container,
264                _ => continue,
265            };
266            let all_locals =
267                ftd_rt::Element::get_external_children_dependencies(&container.children);
268            for (k, v) in all_locals {
269                d.insert(k.to_string(), v);
270            }
271            if let Some((external_open_id, external_children_container, external_children)) =
272                &container.external_children
273            {
274                if let Some(ftd_rt::Element::Column(col)) = external_children.first() {
275                    let external_children_condition: Vec<ftd_rt::ExternalChildrenCondition> = child
276                        .get_external_children_condition(
277                            &Some(external_open_id.to_string()),
278                            external_children_container,
279                        );
280                    d.insert(
281                        col.common.data_id.as_ref().expect("").to_string(),
282                        external_children_condition,
283                    );
284                    let all_locals = ftd_rt::Element::get_external_children_dependencies(
285                        &col.container.children,
286                    );
287                    for (k, v) in all_locals {
288                        d.insert(k.to_string(), v);
289                    }
290                }
291            }
292        }
293        d
294    }
295
296    pub fn get_style_event_dependencies(
297        children: &[ftd_rt::Element],
298        data: &mut ftd_rt::DataDependenciesMap,
299    ) {
300        for child in children {
301            let (conditional_attributes, id) = match child {
302                ftd_rt::Element::Column(ftd_rt::Column {
303                    common:
304                        ftd_rt::Common {
305                            conditional_attribute,
306                            data_id: id,
307                            ..
308                        },
309                    container,
310                })
311                | ftd_rt::Element::Row(ftd_rt::Row {
312                    common:
313                        ftd_rt::Common {
314                            conditional_attribute,
315                            data_id: id,
316                            ..
317                        },
318                    container,
319                })
320                | ftd_rt::Element::Scene(ftd_rt::Scene {
321                    common:
322                        ftd_rt::Common {
323                            conditional_attribute,
324                            data_id: id,
325                            ..
326                        },
327                    container,
328                }) => {
329                    ftd_rt::Element::get_style_event_dependencies(&container.children, data);
330                    if let Some((_, _, external_children)) = &container.external_children {
331                        ftd_rt::Element::get_style_event_dependencies(external_children, data);
332                    }
333                    (conditional_attribute, id)
334                }
335                ftd_rt::Element::Image(ftd_rt::Image { common, .. })
336                | ftd_rt::Element::Text(ftd_rt::Text { common, .. })
337                | ftd_rt::Element::TextBlock(ftd_rt::TextBlock { common, .. })
338                | ftd_rt::Element::Code(ftd_rt::Code { common, .. })
339                | ftd_rt::Element::IFrame(ftd_rt::IFrame { common, .. })
340                | ftd_rt::Element::Input(ftd_rt::Input { common, .. })
341                | ftd_rt::Element::Integer(ftd_rt::Text { common, .. })
342                | ftd_rt::Element::Boolean(ftd_rt::Text { common, .. })
343                | ftd_rt::Element::Decimal(ftd_rt::Text { common, .. }) => {
344                    (&common.conditional_attribute, &common.data_id)
345                }
346                ftd_rt::Element::Null => continue,
347            };
348            for (k, v) in conditional_attributes {
349                if let ftd_rt::ConditionalAttribute {
350                    attribute_type: ftd_rt::AttributeType::Style,
351                    conditions_with_value,
352                    default,
353                } = v
354                {
355                    for (condition, value) in conditions_with_value {
356                        let id = id.clone().expect("universal id should be present");
357                        if let Some(ftd_rt::Data { dependencies, .. }) =
358                            data.get_mut(&condition.variable)
359                        {
360                            let json = ftd_rt::Dependencies {
361                                dependency_type: ftd_rt::DependencyType::Style,
362                                condition: Some(condition.value.to_string()),
363                                parameters: std::array::IntoIter::new([(
364                                    k.to_string(),
365                                    ftd_rt::ValueWithDefault {
366                                        value: value.clone(),
367                                        default: default.clone(),
368                                    },
369                                )])
370                                .collect(),
371                            };
372                            if let Some(dependencies) = dependencies.get_mut(&id) {
373                                let mut d =
374                                    serde_json::from_str::<Vec<ftd_rt::Dependencies>>(dependencies)
375                                        .unwrap();
376                                d.push(json);
377                                *dependencies = serde_json::to_string(&d).unwrap();
378                            } else {
379                                dependencies
380                                    .insert(id, serde_json::to_string(&vec![json]).unwrap());
381                            }
382                        } else {
383                            panic!("{} should be declared", condition.variable)
384                        }
385                    }
386                }
387            }
388        }
389    }
390
391    pub fn get_value_event_dependencies(
392        children: &[ftd_rt::Element],
393        data: &mut ftd_rt::DataDependenciesMap,
394    ) {
395        for child in children {
396            let (reference, id) = match child {
397                ftd_rt::Element::Column(ftd_rt::Column {
398                    common:
399                        ftd_rt::Common {
400                            reference,
401                            data_id: id,
402                            ..
403                        },
404                    container,
405                })
406                | ftd_rt::Element::Row(ftd_rt::Row {
407                    common:
408                        ftd_rt::Common {
409                            reference,
410                            data_id: id,
411                            ..
412                        },
413                    container,
414                })
415                | ftd_rt::Element::Scene(ftd_rt::Scene {
416                    common:
417                        ftd_rt::Common {
418                            reference,
419                            data_id: id,
420                            ..
421                        },
422                    container,
423                }) => {
424                    ftd_rt::Element::get_value_event_dependencies(&container.children, data);
425                    if let Some((_, _, external_children)) = &container.external_children {
426                        ftd_rt::Element::get_value_event_dependencies(external_children, data);
427                    }
428                    (reference, id)
429                }
430                ftd_rt::Element::Image(ftd_rt::Image { common, .. })
431                | ftd_rt::Element::Text(ftd_rt::Text { common, .. })
432                | ftd_rt::Element::TextBlock(ftd_rt::TextBlock { common, .. })
433                | ftd_rt::Element::Code(ftd_rt::Code { common, .. })
434                | ftd_rt::Element::IFrame(ftd_rt::IFrame { common, .. })
435                | ftd_rt::Element::Input(ftd_rt::Input { common, .. })
436                | ftd_rt::Element::Integer(ftd_rt::Text { common, .. })
437                | ftd_rt::Element::Boolean(ftd_rt::Text { common, .. })
438                | ftd_rt::Element::Decimal(ftd_rt::Text { common, .. }) => {
439                    (&common.reference, &common.data_id)
440                }
441                ftd_rt::Element::Null => continue,
442            };
443            if let Some(reference) = reference {
444                let id = id.clone().expect("universal id should be present");
445
446                if let Some(ftd_rt::Data { dependencies, .. }) = data.get_mut(reference) {
447                    let json = ftd_rt::Dependencies {
448                        dependency_type: ftd_rt::DependencyType::Value,
449                        condition: None,
450                        parameters: Default::default(),
451                    };
452                    if let Some(dependencies) = dependencies.get_mut(&id) {
453                        let mut d = serde_json::from_str::<Vec<ftd_rt::Dependencies>>(dependencies)
454                            .unwrap();
455                        d.push(json);
456                        *dependencies = serde_json::to_string(&d).unwrap();
457                    } else {
458                        dependencies.insert(id, serde_json::to_string(&vec![json]).unwrap());
459                    }
460                }
461            }
462        }
463    }
464
465    pub fn get_visible_event_dependencies(
466        children: &[ftd_rt::Element],
467        data: &mut ftd_rt::DataDependenciesMap,
468    ) {
469        for child in children {
470            let (condition, id) = match child {
471                ftd_rt::Element::Column(ftd_rt::Column {
472                    common:
473                        ftd_rt::Common {
474                            condition,
475                            data_id: id,
476                            ..
477                        },
478                    container,
479                })
480                | ftd_rt::Element::Row(ftd_rt::Row {
481                    common:
482                        ftd_rt::Common {
483                            condition,
484                            data_id: id,
485                            ..
486                        },
487                    container,
488                })
489                | ftd_rt::Element::Scene(ftd_rt::Scene {
490                    common:
491                        ftd_rt::Common {
492                            condition,
493                            data_id: id,
494                            ..
495                        },
496                    container,
497                }) => {
498                    ftd_rt::Element::get_visible_event_dependencies(&container.children, data);
499                    if let Some((_, _, external_children)) = &container.external_children {
500                        ftd_rt::Element::get_visible_event_dependencies(external_children, data);
501                    }
502                    (condition, id)
503                }
504                ftd_rt::Element::Image(ftd_rt::Image { common, .. })
505                | ftd_rt::Element::Text(ftd_rt::Text { common, .. })
506                | ftd_rt::Element::TextBlock(ftd_rt::TextBlock { common, .. })
507                | ftd_rt::Element::Code(ftd_rt::Code { common, .. })
508                | ftd_rt::Element::IFrame(ftd_rt::IFrame { common, .. })
509                | ftd_rt::Element::Input(ftd_rt::Input { common, .. })
510                | ftd_rt::Element::Integer(ftd_rt::Text { common, .. })
511                | ftd_rt::Element::Boolean(ftd_rt::Text { common, .. })
512                | ftd_rt::Element::Decimal(ftd_rt::Text { common, .. }) => {
513                    (&common.condition, &common.data_id)
514                }
515                ftd_rt::Element::Null => continue,
516            };
517            if let Some(condition) = condition {
518                let id = id.clone().expect("universal id should be present");
519
520                if let Some(ftd_rt::Data { dependencies, .. }) = data.get_mut(&condition.variable) {
521                    let json = ftd_rt::Dependencies {
522                        dependency_type: ftd_rt::DependencyType::Visible,
523                        condition: Some(condition.value.to_string()),
524                        parameters: Default::default(),
525                    };
526                    if let Some(dependencies) = dependencies.get_mut(&id) {
527                        let mut d = serde_json::from_str::<Vec<ftd_rt::Dependencies>>(dependencies)
528                            .unwrap();
529                        d.push(json);
530                        *dependencies = serde_json::to_string(&d).unwrap();
531                    } else {
532                        dependencies.insert(id, serde_json::to_string(&vec![json]).unwrap());
533                    }
534                } else {
535                    panic!("{} should be declared", condition.variable)
536                }
537            }
538        }
539    }
540
541    pub fn get_locals(children: &[ftd_rt::Element]) -> ftd_rt::Map {
542        let mut d: ftd_rt::Map = Default::default();
543        for child in children {
544            let locals = match child {
545                ftd_rt::Element::Row(ftd_rt::Row {
546                    common: ftd_rt::Common { locals, .. },
547                    container,
548                })
549                | ftd_rt::Element::Column(ftd_rt::Column {
550                    common: ftd_rt::Common { locals, .. },
551                    container,
552                })
553                | ftd_rt::Element::Scene(ftd_rt::Scene {
554                    common: ftd_rt::Common { locals, .. },
555                    container,
556                }) => {
557                    let mut all_locals = ftd_rt::Element::get_locals(&container.children);
558                    for (k, v) in locals {
559                        all_locals.insert(k.to_string(), v.to_string());
560                    }
561                    if let Some((_, _, ref c)) = container.external_children {
562                        for (k, v) in ftd_rt::Element::get_locals(c) {
563                            all_locals.insert(k.to_string(), v.to_string());
564                        }
565                    }
566                    all_locals
567                }
568                ftd_rt::Element::Decimal(ftd_rt::Text { common, .. })
569                | ftd_rt::Element::Text(ftd_rt::Text { common, .. })
570                | ftd_rt::Element::TextBlock(ftd_rt::TextBlock { common, .. })
571                | ftd_rt::Element::Code(ftd_rt::Code { common, .. })
572                | ftd_rt::Element::Image(ftd_rt::Image { common, .. })
573                | ftd_rt::Element::IFrame(ftd_rt::IFrame { common, .. })
574                | ftd_rt::Element::Input(ftd_rt::Input { common, .. })
575                | ftd_rt::Element::Integer(ftd_rt::Text { common, .. })
576                | ftd_rt::Element::Boolean(ftd_rt::Text { common, .. }) => common.locals.clone(),
577                ftd_rt::Element::Null => Default::default(),
578            };
579
580            for (k, v) in locals {
581                d.insert(k.to_string(), v.to_string());
582            }
583        }
584        d
585    }
586
587    pub fn is_open_container(&self, is_container_children_empty: bool) -> (bool, Option<String>) {
588        match self {
589            ftd_rt::Element::Column(e) => e.container.is_open(is_container_children_empty),
590            ftd_rt::Element::Row(e) => e.container.is_open(is_container_children_empty),
591            ftd_rt::Element::Scene(e) => e.container.is_open(is_container_children_empty),
592            _ => (false, None),
593        }
594    }
595
596    pub fn container_id(&self) -> Option<String> {
597        match self {
598            ftd_rt::Element::Column(e) => e.common.data_id.clone(),
599            ftd_rt::Element::Row(e) => e.common.data_id.clone(),
600            ftd_rt::Element::Scene(e) => e.common.data_id.clone(),
601            _ => None,
602        }
603    }
604
605    pub fn set_container_id(&mut self, name: Option<String>) {
606        match self {
607            ftd_rt::Element::Column(e) => e.common.data_id = name,
608            ftd_rt::Element::Row(e) => e.common.data_id = name,
609            ftd_rt::Element::Scene(e) => e.common.data_id = name,
610            _ => {}
611        }
612    }
613
614    pub fn set_element_id(&mut self, name: Option<String>) {
615        match self {
616            ftd_rt::Element::Column(ftd_rt::Column { common, .. })
617            | ftd_rt::Element::Row(ftd_rt::Row { common, .. })
618            | ftd_rt::Element::Text(ftd_rt::Text { common, .. })
619            | ftd_rt::Element::TextBlock(ftd_rt::TextBlock { common, .. })
620            | ftd_rt::Element::Code(ftd_rt::Code { common, .. })
621            | ftd_rt::Element::Image(ftd_rt::Image { common, .. })
622            | ftd_rt::Element::IFrame(ftd_rt::IFrame { common, .. })
623            | ftd_rt::Element::Input(ftd_rt::Input { common, .. })
624            | ftd_rt::Element::Integer(ftd_rt::Text { common, .. })
625            | ftd_rt::Element::Boolean(ftd_rt::Text { common, .. })
626            | ftd_rt::Element::Decimal(ftd_rt::Text { common, .. })
627            | ftd_rt::Element::Scene(ftd_rt::Scene { common, .. }) => common.id = name,
628            ftd_rt::Element::Null => {}
629        }
630    }
631
632    pub fn set_condition(&mut self, condition: Option<ftd_rt::Condition>) {
633        match self {
634            ftd_rt::Element::Column(ftd_rt::Column { common, .. })
635            | ftd_rt::Element::Row(ftd_rt::Row { common, .. })
636            | ftd_rt::Element::Text(ftd_rt::Text { common, .. })
637            | ftd_rt::Element::TextBlock(ftd_rt::TextBlock { common, .. })
638            | ftd_rt::Element::Code(ftd_rt::Code { common, .. })
639            | ftd_rt::Element::Image(ftd_rt::Image { common, .. })
640            | ftd_rt::Element::IFrame(ftd_rt::IFrame { common, .. })
641            | ftd_rt::Element::Input(ftd_rt::Input { common, .. })
642            | ftd_rt::Element::Integer(ftd_rt::Text { common, .. })
643            | ftd_rt::Element::Boolean(ftd_rt::Text { common, .. })
644            | ftd_rt::Element::Decimal(ftd_rt::Text { common, .. })
645            | ftd_rt::Element::Scene(ftd_rt::Scene { common, .. }) => common,
646            ftd_rt::Element::Null => return,
647        }
648        .condition = condition;
649    }
650
651    pub fn set_non_visibility(&mut self, is_not_visible: bool) {
652        match self {
653            ftd_rt::Element::Column(ftd_rt::Column { common, .. })
654            | ftd_rt::Element::Row(ftd_rt::Row { common, .. })
655            | ftd_rt::Element::Text(ftd_rt::Text { common, .. })
656            | ftd_rt::Element::TextBlock(ftd_rt::TextBlock { common, .. })
657            | ftd_rt::Element::Code(ftd_rt::Code { common, .. })
658            | ftd_rt::Element::Image(ftd_rt::Image { common, .. })
659            | ftd_rt::Element::IFrame(ftd_rt::IFrame { common, .. })
660            | ftd_rt::Element::Input(ftd_rt::Input { common, .. })
661            | ftd_rt::Element::Integer(ftd_rt::Text { common, .. })
662            | ftd_rt::Element::Boolean(ftd_rt::Text { common, .. })
663            | ftd_rt::Element::Decimal(ftd_rt::Text { common, .. })
664            | ftd_rt::Element::Scene(ftd_rt::Scene { common, .. }) => common,
665            ftd_rt::Element::Null => return,
666        }
667        .is_not_visible = is_not_visible;
668    }
669
670    pub fn set_locals(&mut self, locals: ftd_rt::Map) {
671        match self {
672            ftd_rt::Element::Column(ftd_rt::Column { common, .. })
673            | ftd_rt::Element::Row(ftd_rt::Row { common, .. })
674            | ftd_rt::Element::Text(ftd_rt::Text { common, .. })
675            | ftd_rt::Element::TextBlock(ftd_rt::TextBlock { common, .. })
676            | ftd_rt::Element::Code(ftd_rt::Code { common, .. })
677            | ftd_rt::Element::Image(ftd_rt::Image { common, .. })
678            | ftd_rt::Element::IFrame(ftd_rt::IFrame { common, .. })
679            | ftd_rt::Element::Input(ftd_rt::Input { common, .. })
680            | ftd_rt::Element::Integer(ftd_rt::Text { common, .. })
681            | ftd_rt::Element::Boolean(ftd_rt::Text { common, .. })
682            | ftd_rt::Element::Decimal(ftd_rt::Text { common, .. })
683            | ftd_rt::Element::Scene(ftd_rt::Scene { common, .. }) => common,
684            ftd_rt::Element::Null => return,
685        }
686        .locals = locals;
687    }
688
689    pub fn set_events(&mut self, events: &mut Vec<ftd_rt::Event>) {
690        match self {
691            ftd_rt::Element::Column(ftd_rt::Column { common, .. })
692            | ftd_rt::Element::Row(ftd_rt::Row { common, .. })
693            | ftd_rt::Element::Text(ftd_rt::Text { common, .. })
694            | ftd_rt::Element::TextBlock(ftd_rt::TextBlock { common, .. })
695            | ftd_rt::Element::Code(ftd_rt::Code { common, .. })
696            | ftd_rt::Element::Image(ftd_rt::Image { common, .. })
697            | ftd_rt::Element::IFrame(ftd_rt::IFrame { common, .. })
698            | ftd_rt::Element::Input(ftd_rt::Input { common, .. })
699            | ftd_rt::Element::Integer(ftd_rt::Text { common, .. })
700            | ftd_rt::Element::Boolean(ftd_rt::Text { common, .. })
701            | ftd_rt::Element::Decimal(ftd_rt::Text { common, .. })
702            | ftd_rt::Element::Scene(ftd_rt::Scene { common, .. }) => common,
703            ftd_rt::Element::Null => return,
704        }
705        .events
706        .append(events)
707    }
708
709    pub fn get_heading_region(&self) -> Option<&ftd_rt::Region> {
710        match self {
711            ftd_rt::Element::Column(e) => e.common.region.as_ref().filter(|v| v.is_heading()),
712            ftd_rt::Element::Row(e) => e.common.region.as_ref().filter(|v| v.is_heading()),
713            _ => None,
714        }
715    }
716
717    pub fn get_mut_common(&mut self) -> Option<&mut ftd_rt::Common> {
718        match self {
719            ftd_rt::Element::Column(e) => Some(&mut e.common),
720            ftd_rt::Element::Row(e) => Some(&mut e.common),
721            ftd_rt::Element::Text(e) => Some(&mut e.common),
722            ftd_rt::Element::TextBlock(e) => Some(&mut e.common),
723            ftd_rt::Element::Code(e) => Some(&mut e.common),
724            ftd_rt::Element::Image(e) => Some(&mut e.common),
725            ftd_rt::Element::IFrame(e) => Some(&mut e.common),
726            ftd_rt::Element::Input(e) => Some(&mut e.common),
727            ftd_rt::Element::Integer(e) => Some(&mut e.common),
728            ftd_rt::Element::Boolean(e) => Some(&mut e.common),
729            ftd_rt::Element::Decimal(e) => Some(&mut e.common),
730            ftd_rt::Element::Scene(e) => Some(&mut e.common),
731            _ => None,
732        }
733    }
734
735    pub fn get_common(&self) -> Option<&ftd_rt::Common> {
736        match self {
737            ftd_rt::Element::Column(e) => Some(&e.common),
738            ftd_rt::Element::Row(e) => Some(&e.common),
739            ftd_rt::Element::Text(e) => Some(&e.common),
740            ftd_rt::Element::TextBlock(e) => Some(&e.common),
741            ftd_rt::Element::Code(e) => Some(&e.common),
742            ftd_rt::Element::Image(e) => Some(&e.common),
743            ftd_rt::Element::IFrame(e) => Some(&e.common),
744            ftd_rt::Element::Input(e) => Some(&e.common),
745            ftd_rt::Element::Integer(e) => Some(&e.common),
746            ftd_rt::Element::Boolean(e) => Some(&e.common),
747            ftd_rt::Element::Decimal(e) => Some(&e.common),
748            ftd_rt::Element::Scene(e) => Some(&e.common),
749            _ => None,
750        }
751    }
752
753    pub fn renesting_on_region(elements: &mut Vec<ftd_rt::Element>) {
754        let mut region: Option<(usize, &Region)> = None;
755        let mut insert: Vec<(usize, usize)> = Default::default();
756        for (idx, element) in elements.iter().enumerate() {
757            match element {
758                ftd_rt::Element::Column(ftd_rt::Column { common, .. })
759                | ftd_rt::Element::Row(ftd_rt::Row { common, .. }) => {
760                    let r = common.region.as_ref().filter(|v| v.is_heading());
761                    if let Some(r) = r {
762                        if let Some((place_at, r1)) = region {
763                            if r.get_lower_priority_heading().contains(r1) || r == r1 {
764                                insert.push((place_at, idx));
765                                region = Some((idx, r));
766                            }
767                        } else {
768                            region = Some((idx, r));
769                        }
770                    }
771                }
772                _ => continue,
773            }
774        }
775        if let Some((place_at, _)) = region {
776            insert.push((place_at, elements.len()));
777        }
778
779        for (place_at, end) in insert.iter().rev() {
780            let mut children = elements[place_at + 1..*end].to_vec();
781            match elements[*place_at] {
782                ftd_rt::Element::Column(ftd_rt::Column {
783                    ref mut container, ..
784                })
785                | ftd_rt::Element::Row(ftd_rt::Row {
786                    ref mut container, ..
787                }) => {
788                    container.children.append(&mut children);
789                }
790                _ => continue,
791            }
792            for idx in (place_at + 1..*end).rev() {
793                elements.remove(idx);
794            }
795        }
796
797        for element in &mut *elements {
798            match element {
799                ftd_rt::Element::Column(ftd_rt::Column {
800                    ref mut container, ..
801                })
802                | ftd_rt::Element::Row(ftd_rt::Row {
803                    ref mut container, ..
804                }) => {
805                    if let Some((_, _, ref mut e)) = container.external_children {
806                        ftd_rt::Element::renesting_on_region(e);
807                    }
808                    ftd_rt::Element::renesting_on_region(&mut container.children);
809                }
810                _ => continue,
811            }
812        }
813    }
814}
815
816#[derive(serde::Deserialize, PartialEq)]
817#[cfg_attr(not(feature = "wasm"), derive(Debug, Clone, serde::Serialize))]
818#[serde(tag = "type")]
819pub enum Length {
820    Fill,
821    Shrink,
822    Auto,
823    FitContent,
824    Px { value: i64 },
825    Portion { value: i64 },
826    Percent { value: i64 },
827    Calc { value: String },
828}
829
830impl Length {
831    pub fn from(l: Option<String>, doc_id: &str) -> ftd_rt::Result<Option<ftd_rt::Length>> {
832        let l = match l {
833            Some(l) => l,
834            None => return Ok(None),
835        };
836
837        if l == "fill" {
838            return Ok(Some(Length::Fill));
839        }
840
841        if l == "shrink" {
842            return Ok(Some(Length::Shrink));
843        }
844        if l == "auto" {
845            return Ok(Some(Length::Auto));
846        }
847
848        if l.starts_with("calc ") {
849            let v = crate::get_name("calc", l.as_str(), doc_id)?;
850            return match v.parse() {
851                Ok(v) => Ok(Some(Length::Calc { value: v })),
852                Err(_) => crate::e(format!("{} is not a valid integer", v), doc_id),
853            };
854        }
855
856        if l == "fit-content" {
857            return Ok(Some(Length::FitContent));
858        }
859
860        if l.starts_with("portion ") {
861            let v = crate::get_name("portion", l.as_str(), doc_id)?;
862            return match v.parse() {
863                Ok(v) => Ok(Some(Length::Portion { value: v })),
864                Err(_) => crate::e(format!("{} is not a valid integer", v), doc_id),
865            };
866        }
867        if l.starts_with("percent ") {
868            let v = crate::get_name("percent", l.as_str(), doc_id)?;
869            return match v.parse() {
870                Ok(v) => Ok(Some(Length::Percent { value: v })),
871                Err(_) => crate::e(format!("{} is not a valid integer", v), doc_id),
872            };
873        }
874
875        match l.parse() {
876            Ok(v) => Ok(Some(Length::Px { value: v })),
877            Err(_) => crate::e(format!("{} is not a valid integer", l), doc_id),
878        }
879    }
880}
881
882#[derive(serde::Deserialize)]
883#[cfg_attr(
884    not(feature = "wasm"),
885    derive(Debug, PartialEq, Clone, serde::Serialize)
886)]
887#[serde(tag = "type")]
888pub enum Position {
889    Center,
890    Top,
891    Bottom,
892    Left,
893    Right,
894    TopLeft,
895    TopRight,
896    BottomLeft,
897    BottomRight,
898}
899
900impl Default for Position {
901    fn default() -> ftd_rt::Position {
902        Self::TopLeft
903    }
904}
905
906impl Position {
907    pub fn from(l: Option<String>, doc_id: &str) -> ftd_rt::Result<ftd_rt::Position> {
908        Ok(match l.as_deref() {
909            Some("center") => Self::Center,
910            Some("top") => Self::Top,
911            Some("bottom") => Self::Bottom,
912            Some("left") => Self::Left,
913            Some("right") => Self::Right,
914            Some("top-left") => Self::TopLeft,
915            Some("top-right") => Self::TopRight,
916            Some("bottom-left") => Self::BottomLeft,
917            Some("bottom-right") => Self::BottomRight,
918            Some(t) => return crate::e(format!("{} is not a valid alignment", t), doc_id),
919            None => return Ok(Self::TopLeft),
920        })
921    }
922}
923
924// https://package.elm-lang.org/packages/mdgriffith/elm-ui/latest/Element-Region
925#[derive(serde::Deserialize)]
926#[cfg_attr(
927    not(feature = "wasm"),
928    derive(Debug, PartialEq, Clone, serde::Serialize)
929)]
930pub enum Region {
931    H0,
932    H1,
933    H2,
934    H3,
935    H4,
936    H5,
937    H6,
938    H7,
939    Title,
940    MainContent,
941    Navigation,
942    Aside,
943    Footer,
944    Description,
945    Announce,
946    AnnounceUrgently,
947}
948
949impl ToString for Region {
950    fn to_string(&self) -> String {
951        match self {
952            Self::H0 => "h0",
953            Self::H1 => "h1",
954            Self::H2 => "h2",
955            Self::H3 => "h3",
956            Self::H4 => "h4",
957            Self::H5 => "h5",
958            Self::H6 => "h6",
959            Self::H7 => "h7",
960            Self::Title => "title",
961            Self::MainContent => "main",
962            Self::Navigation => "navigation",
963            Self::Aside => "aside",
964            Self::Footer => "footer",
965            Self::Description => "description",
966            Self::Announce => "announce",
967            Self::AnnounceUrgently => "announce-urgently",
968        }
969        .to_string()
970    }
971}
972
973impl Region {
974    pub fn from(l: Option<String>, doc_id: &str) -> ftd_rt::Result<Option<ftd_rt::Region>> {
975        Ok(Some(match l.as_deref() {
976            Some("h0") => Self::H0,
977            Some("h1") => Self::H1,
978            Some("h2") => Self::H2,
979            Some("h3") => Self::H3,
980            Some("h4") => Self::H4,
981            Some("h5") => Self::H5,
982            Some("h6") => Self::H6,
983            Some("h7") => Self::H7,
984            Some("title") => Self::Title,
985            Some("main") => Self::MainContent,
986            Some("navigation") => Self::Navigation,
987            Some("aside") => Self::Aside,
988            Some("footer") => Self::Footer,
989            Some("description") => Self::Description,
990            Some("announce") => Self::Announce,
991            Some("announce-urgently") => Self::AnnounceUrgently,
992            Some(t) => return crate::e(format!("{} is not a valid alignment", t), doc_id),
993            None => return Ok(None),
994        }))
995    }
996
997    pub fn is_heading(&self) -> bool {
998        matches!(
999            self,
1000            ftd_rt::Region::H0
1001                | ftd_rt::Region::H1
1002                | ftd_rt::Region::H2
1003                | ftd_rt::Region::H3
1004                | ftd_rt::Region::H4
1005                | ftd_rt::Region::H5
1006                | ftd_rt::Region::H6
1007                | ftd_rt::Region::H7
1008        )
1009    }
1010
1011    pub fn is_primary_heading(&self) -> bool {
1012        matches!(self, ftd_rt::Region::H0 | ftd_rt::Region::H1)
1013    }
1014
1015    pub fn is_title(&self) -> bool {
1016        matches!(self, ftd_rt::Region::Title)
1017    }
1018
1019    pub fn get_lower_priority_heading(&self) -> Vec<ftd_rt::Region> {
1020        let mut list = vec![];
1021        if matches!(
1022            self,
1023            ftd_rt::Region::Title
1024                | ftd_rt::Region::MainContent
1025                | ftd_rt::Region::Navigation
1026                | ftd_rt::Region::Aside
1027                | ftd_rt::Region::Footer
1028                | ftd_rt::Region::Description
1029                | ftd_rt::Region::Announce
1030                | ftd_rt::Region::AnnounceUrgently
1031        ) {
1032            return list;
1033        }
1034        for region in [
1035            ftd_rt::Region::H7,
1036            ftd_rt::Region::H6,
1037            ftd_rt::Region::H5,
1038            ftd_rt::Region::H4,
1039            ftd_rt::Region::H3,
1040            ftd_rt::Region::H2,
1041            ftd_rt::Region::H1,
1042        ] {
1043            if self.to_string() == region.to_string() {
1044                return list;
1045            }
1046            list.push(region);
1047        }
1048        list
1049    }
1050}
1051
1052#[derive(serde::Deserialize)]
1053#[cfg_attr(
1054    not(feature = "wasm"),
1055    derive(Debug, PartialEq, Clone, serde::Serialize)
1056)]
1057#[serde(tag = "type")]
1058pub enum Overflow {
1059    Hidden,
1060    Visible,
1061    Auto,
1062    Scroll,
1063}
1064
1065impl Overflow {
1066    pub fn from(l: Option<String>, doc_id: &str) -> ftd_rt::Result<Option<ftd_rt::Overflow>> {
1067        Ok(Option::from(match l.as_deref() {
1068            Some("hidden") => Self::Hidden,
1069            Some("visible") => Self::Visible,
1070            Some("auto") => Self::Auto,
1071            Some("scroll") => Self::Scroll,
1072            Some(t) => return crate::e(format!("{} is not a valid property", t), doc_id),
1073            None => return Ok(None),
1074        }))
1075    }
1076}
1077
1078#[derive(serde::Deserialize)]
1079#[cfg_attr(
1080    not(feature = "wasm"),
1081    derive(Debug, PartialEq, Clone, serde::Serialize)
1082)]
1083#[serde(tag = "type")]
1084pub enum Anchor {
1085    Window,
1086    Parent,
1087}
1088
1089impl Anchor {
1090    pub fn from(l: Option<String>, doc_id: &str) -> ftd_rt::Result<Option<ftd_rt::Anchor>> {
1091        let l = match l {
1092            Some(l) => l,
1093            None => return Ok(None),
1094        };
1095
1096        Ok(Some(match l.as_str() {
1097            "window" => ftd_rt::Anchor::Window,
1098            "parent" => ftd_rt::Anchor::Parent,
1099            t => {
1100                return ftd_rt::e(
1101                    format!(
1102                        "invalid value for `absolute` expected `window` or `parent` found: {}",
1103                        t
1104                    ),
1105                    doc_id,
1106                )
1107            }
1108        }))
1109    }
1110
1111    pub fn to_postion(&self) -> String {
1112        match self {
1113            ftd_rt::Anchor::Window => "fixed",
1114            ftd_rt::Anchor::Parent => "absolute",
1115        }
1116        .to_string()
1117    }
1118}
1119
1120#[derive(serde::Deserialize)]
1121#[cfg_attr(
1122    not(feature = "wasm"),
1123    derive(Debug, PartialEq, Clone, serde::Serialize)
1124)]
1125#[serde(tag = "type")]
1126pub enum GradientDirection {
1127    BottomToTop,
1128    TopToBottom,
1129    RightToLeft,
1130    LeftToRight,
1131    BottomRightToTopLeft,
1132    BottomLeftToTopRight,
1133    TopRightToBottomLeft,
1134    TopLeftBottomRight,
1135    Center,
1136    Angle { value: i64 },
1137}
1138
1139impl GradientDirection {
1140    pub fn from(
1141        l: Option<String>,
1142        doc_id: &str,
1143    ) -> ftd_rt::Result<Option<ftd_rt::GradientDirection>> {
1144        let l = match l {
1145            Some(l) => l,
1146            None => return Ok(None),
1147        };
1148
1149        if l == "bottom to top" {
1150            return Ok(Some(GradientDirection::BottomToTop));
1151        }
1152        if l == "top to bottom" {
1153            return Ok(Some(GradientDirection::TopToBottom));
1154        }
1155        if l == "right to left" {
1156            return Ok(Some(GradientDirection::RightToLeft));
1157        }
1158        if l == "left to right" {
1159            return Ok(Some(GradientDirection::LeftToRight));
1160        }
1161        if l == "bottom-left to top-right" {
1162            return Ok(Some(GradientDirection::BottomLeftToTopRight));
1163        }
1164        if l == "bottom-right to top-left" {
1165            return Ok(Some(GradientDirection::BottomRightToTopLeft));
1166        }
1167        if l == "top-right to bottom-left" {
1168            return Ok(Some(GradientDirection::TopRightToBottomLeft));
1169        }
1170        if l == "top-left to bottom-right" {
1171            return Ok(Some(GradientDirection::TopLeftBottomRight));
1172        }
1173        if l == "center" {
1174            return Ok(Some(GradientDirection::Center));
1175        }
1176        if l.starts_with("angle ") {
1177            let v = crate::get_name("angle", l.as_str(), doc_id)?;
1178            return match v.parse() {
1179                Ok(v) => Ok(Some(GradientDirection::Angle { value: v })),
1180                Err(_) => crate::e(format!("{} is not a valid integer", v), doc_id),
1181            };
1182        }
1183        Ok(None)
1184    }
1185}
1186
1187#[derive(serde::Deserialize)]
1188#[cfg_attr(
1189    not(feature = "wasm"),
1190    derive(Debug, PartialEq, Clone, serde::Serialize)
1191)]
1192pub enum AttributeType {
1193    Style,
1194    Attribute,
1195}
1196
1197#[derive(serde::Deserialize)]
1198#[cfg_attr(
1199    not(feature = "wasm"),
1200    derive(Debug, PartialEq, Clone, serde::Serialize)
1201)]
1202pub struct ConditionalAttribute {
1203    pub attribute_type: AttributeType,
1204    pub conditions_with_value: Vec<(ftd_rt::Condition, ConditionalValue)>,
1205    pub default: Option<ConditionalValue>,
1206}
1207
1208#[derive(serde::Deserialize, Clone)]
1209#[cfg_attr(not(feature = "wasm"), derive(Debug, PartialEq, serde::Serialize))]
1210pub struct ConditionalValue {
1211    pub value: String,
1212    pub important: bool,
1213}
1214
1215#[derive(serde::Deserialize)]
1216#[cfg_attr(
1217    not(feature = "wasm"),
1218    derive(Debug, PartialEq, Clone, serde::Serialize, Default)
1219)]
1220pub struct Common {
1221    pub conditional_attribute: std::collections::BTreeMap<String, ConditionalAttribute>,
1222    pub locals: ftd_rt::Map,
1223    pub condition: Option<ftd_rt::Condition>,
1224    pub is_not_visible: bool,
1225    pub events: Vec<ftd_rt::Event>,
1226    pub reference: Option<String>,
1227    pub region: Option<Region>,
1228    pub padding: Option<i64>,
1229    pub padding_vertical: Option<i64>,
1230    pub padding_horizontal: Option<i64>,
1231    pub padding_left: Option<i64>,
1232    pub padding_right: Option<i64>,
1233    pub padding_top: Option<i64>,
1234    pub padding_bottom: Option<i64>,
1235    pub border_top_radius: Option<i64>,
1236    pub border_bottom_radius: Option<i64>,
1237    pub border_left_radius: Option<i64>,
1238    pub border_right_radius: Option<i64>,
1239    pub width: Option<Length>,
1240    pub max_width: Option<Length>,
1241    pub min_width: Option<Length>,
1242    pub height: Option<Length>,
1243    pub min_height: Option<Length>,
1244    pub max_height: Option<Length>,
1245    pub color: Option<Color>,
1246    pub background_color: Option<Color>,
1247    pub border_color: Option<Color>,
1248    pub border_width: i64,
1249    pub border_radius: i64,
1250    pub id: Option<String>,
1251    pub data_id: Option<String>,
1252    pub overflow_x: Option<Overflow>,
1253    pub overflow_y: Option<Overflow>,
1254    pub border_top: Option<i64>,
1255    pub border_left: Option<i64>,
1256    pub border_right: Option<i64>,
1257    pub border_bottom: Option<i64>,
1258    pub margin_top: Option<i64>,
1259    pub margin_left: Option<i64>,
1260    pub margin_right: Option<i64>,
1261    pub margin_bottom: Option<i64>,
1262    pub link: Option<String>,
1263    pub open_in_new_tab: bool,
1264    pub sticky: bool,
1265    pub top: Option<i64>,
1266    pub bottom: Option<i64>,
1267    pub left: Option<i64>,
1268    pub right: Option<i64>,
1269    pub submit: Option<String>,
1270    pub cursor: Option<String>,
1271    pub shadow_offset_x: Option<i64>,
1272    pub shadow_offset_y: Option<i64>,
1273    pub shadow_size: Option<i64>,
1274    pub shadow_blur: Option<i64>,
1275    pub shadow_color: Option<Color>,
1276    pub anchor: Option<ftd_rt::Anchor>,
1277    pub gradient_direction: Option<GradientDirection>,
1278    pub gradient_colors: Vec<Color>,
1279    pub background_image: Option<String>,
1280    pub background_repeat: bool,
1281    pub background_parallax: bool,
1282    pub scale: Option<f64>,
1283    pub scale_x: Option<f64>,
1284    pub scale_y: Option<f64>,
1285    pub rotate: Option<i64>,
1286    pub move_up: Option<i64>,
1287    pub move_down: Option<i64>,
1288    pub move_left: Option<i64>,
1289    pub move_right: Option<i64>,
1290    pub position: Position,
1291    pub inner: bool,
1292    // TODO: background-image, un-cropped, tiled, tiled{X, Y}
1293    // TODO: border-style: solid, dashed, dotted
1294    // TODO: border-{shadow, glow}
1295}
1296
1297#[derive(serde::Deserialize)]
1298#[cfg_attr(
1299    not(feature = "wasm"),
1300    derive(Debug, PartialEq, Clone, serde::Serialize, Default)
1301)]
1302pub struct Container {
1303    pub children: Vec<ftd_rt::Element>,
1304    pub external_children: Option<(String, Vec<Vec<usize>>, Vec<ftd_rt::Element>)>,
1305    pub open: (Option<bool>, Option<String>),
1306    pub spacing: Option<i64>,
1307    pub wrap: bool,
1308}
1309
1310impl Container {
1311    pub fn is_open(&self, is_container_children_empty: bool) -> (bool, Option<String>) {
1312        (
1313            self.open
1314                .0
1315                .unwrap_or_else(|| (self.children.is_empty() && is_container_children_empty)),
1316            self.open.1.clone(),
1317        )
1318    }
1319}
1320
1321#[derive(serde::Deserialize)]
1322#[cfg_attr(
1323    not(feature = "wasm"),
1324    derive(Debug, PartialEq, Clone, serde::Serialize, Default)
1325)]
1326pub struct Image {
1327    pub src: String,
1328    pub description: String,
1329    pub common: Common,
1330    pub crop: bool,
1331}
1332
1333#[derive(serde::Deserialize)]
1334#[cfg_attr(
1335    not(feature = "wasm"),
1336    derive(Debug, PartialEq, Clone, serde::Serialize, Default)
1337)]
1338pub struct Row {
1339    pub container: Container,
1340    pub common: Common,
1341}
1342
1343#[derive(serde::Deserialize)]
1344#[cfg_attr(
1345    not(feature = "wasm"),
1346    derive(Debug, PartialEq, Clone, serde::Serialize, Default)
1347)]
1348pub struct Scene {
1349    pub container: Container,
1350    pub common: Common,
1351}
1352
1353#[derive(serde::Deserialize)]
1354#[cfg_attr(
1355    not(feature = "wasm"),
1356    derive(Debug, PartialEq, Clone, serde::Serialize, Default)
1357)]
1358pub struct Column {
1359    pub container: Container,
1360    pub common: Common,
1361}
1362
1363#[derive(serde::Deserialize)]
1364#[cfg_attr(
1365    not(feature = "wasm"),
1366    derive(Debug, PartialEq, Clone, serde::Serialize)
1367)]
1368#[serde(tag = "type")]
1369pub enum TextAlign {
1370    Left,
1371    Right,
1372    Center,
1373    Justify,
1374}
1375
1376impl Default for TextAlign {
1377    fn default() -> Self {
1378        ftd_rt::TextAlign::Left
1379    }
1380}
1381
1382impl TextAlign {
1383    pub fn from(l: Option<String>, doc_id: &str) -> ftd_rt::Result<ftd_rt::TextAlign> {
1384        Ok(match l.as_deref() {
1385            Some("center") => ftd_rt::TextAlign::Center,
1386            Some("left") => ftd_rt::TextAlign::Left,
1387            Some("right") => ftd_rt::TextAlign::Right,
1388            Some("justify") => ftd_rt::TextAlign::Justify,
1389            Some(t) => return crate::e(format!("{} is not a valid alignment", t), doc_id),
1390            None => return Ok(ftd_rt::TextAlign::Left),
1391        })
1392    }
1393}
1394
1395#[derive(serde::Deserialize)]
1396#[cfg_attr(
1397    not(feature = "wasm"),
1398    derive(Debug, PartialEq, Clone, serde::Serialize)
1399)]
1400#[serde(tag = "type")]
1401pub enum FontDisplay {
1402    Swap,
1403    Block,
1404}
1405impl Default for ftd_rt::FontDisplay {
1406    fn default() -> Self {
1407        ftd_rt::FontDisplay::Block
1408    }
1409}
1410
1411impl FontDisplay {
1412    pub fn from(l: Option<String>, doc_id: &str) -> ftd_rt::Result<ftd_rt::FontDisplay> {
1413        Ok(match l.as_deref() {
1414            Some("swap") => ftd_rt::FontDisplay::Swap,
1415            Some("block") => ftd_rt::FontDisplay::Block,
1416            Some(t) => return crate::e(format!("{} is not a valid alignment", t), doc_id),
1417            None => return Ok(ftd_rt::FontDisplay::Block),
1418        })
1419    }
1420}
1421
1422#[derive(serde::Deserialize)]
1423#[cfg_attr(
1424    not(feature = "wasm"),
1425    derive(Debug, PartialEq, Clone, serde::Serialize)
1426)]
1427#[serde(tag = "type")]
1428pub enum NamedFont {
1429    Monospace,
1430    Serif,
1431    SansSerif,
1432    Named { value: String },
1433}
1434
1435impl NamedFont {
1436    pub fn from(l: Option<String>) -> ftd_rt::Result<ftd_rt::NamedFont> {
1437        Ok(match l.as_deref() {
1438            Some("monospace") => ftd_rt::NamedFont::Monospace,
1439            Some("serif") => ftd_rt::NamedFont::Serif,
1440            Some("sansSerif") => ftd_rt::NamedFont::SansSerif,
1441            Some(t) => ftd_rt::NamedFont::Named {
1442                value: t.to_string(),
1443            },
1444            None => return Ok(ftd_rt::NamedFont::Serif),
1445        })
1446    }
1447}
1448
1449#[derive(serde::Deserialize)]
1450#[cfg_attr(
1451    not(feature = "wasm"),
1452    derive(Debug, PartialEq, Clone, serde::Serialize, Default)
1453)]
1454pub struct ExternalFont {
1455    pub url: String,
1456    pub name: String,
1457    pub display: FontDisplay,
1458}
1459
1460#[derive(serde::Deserialize)]
1461#[cfg_attr(
1462    not(feature = "wasm"),
1463    derive(Debug, PartialEq, Clone, serde::Serialize)
1464)]
1465#[serde(tag = "type")]
1466pub enum Weight {
1467    Heavy,
1468    ExtraBold,
1469    Bold,
1470    SemiBold,
1471    Medium,
1472    Regular,
1473    Light,
1474    ExtraLight,
1475    HairLine,
1476}
1477
1478impl Default for Weight {
1479    fn default() -> Self {
1480        ftd_rt::Weight::Regular
1481    }
1482}
1483
1484#[derive(serde::Deserialize)]
1485#[cfg_attr(
1486    not(feature = "wasm"),
1487    derive(Debug, PartialEq, Clone, serde::Serialize, Default)
1488)]
1489pub struct Style {
1490    pub italic: bool,
1491    pub underline: bool,
1492    pub strike: bool,
1493    pub weight: ftd_rt::Weight,
1494}
1495
1496impl Style {
1497    pub fn from(l: Option<String>, doc_id: &str) -> ftd_rt::Result<ftd_rt::Style> {
1498        let mut s = Style {
1499            italic: false,
1500            underline: false,
1501            strike: false,
1502            weight: Default::default(),
1503        };
1504        let l = match l {
1505            Some(v) => v,
1506            None => return Ok(s),
1507        };
1508        // TODO: assert no word is repeated?
1509        for part in l.split_ascii_whitespace() {
1510            match part {
1511                "italic" => s.italic = true,
1512                "underline" => s.underline = true,
1513                "strike" => s.strike = true,
1514                "heavy" => s.weight = ftd_rt::Weight::Heavy,
1515                "extra-bold" => s.weight = ftd_rt::Weight::ExtraBold,
1516                "bold" => s.weight = ftd_rt::Weight::Bold,
1517                "semi-bold" => s.weight = ftd_rt::Weight::SemiBold,
1518                "medium" => s.weight = ftd_rt::Weight::Medium,
1519                "regular" => s.weight = ftd_rt::Weight::Regular,
1520                "light" => s.weight = ftd_rt::Weight::Light,
1521                "extra-light" => s.weight = ftd_rt::Weight::ExtraLight,
1522                "hairline" => s.weight = ftd_rt::Weight::HairLine,
1523                t => return crate::e(format!("{} is not a valid style", t), doc_id),
1524            }
1525        }
1526        Ok(s)
1527    }
1528}
1529
1530#[derive(serde::Deserialize)]
1531#[cfg_attr(
1532    not(feature = "wasm"),
1533    derive(Debug, PartialEq, Clone, serde::Serialize)
1534)]
1535#[serde(tag = "type")]
1536pub enum TextFormat {
1537    // FTD, // TODO
1538    Markdown,
1539    Latex,
1540    Code { lang: String },
1541    Text,
1542}
1543
1544impl Default for ftd_rt::TextFormat {
1545    fn default() -> ftd_rt::TextFormat {
1546        ftd_rt::TextFormat::Markdown
1547    }
1548}
1549
1550impl TextFormat {
1551    pub fn from(
1552        l: Option<String>,
1553        lang: Option<String>,
1554        doc_id: &str,
1555    ) -> ftd_rt::Result<ftd_rt::TextFormat> {
1556        Ok(match l.as_deref() {
1557            Some("markdown") => ftd_rt::TextFormat::Markdown,
1558            Some("latex") => ftd_rt::TextFormat::Latex,
1559            Some("code") => ftd_rt::TextFormat::Code {
1560                lang: lang.unwrap_or_else(|| "txt".to_string()),
1561            },
1562            Some("text") => ftd_rt::TextFormat::Text,
1563            Some(t) => return ftd_rt::e(format!("{} is not a valid format", t), doc_id),
1564            None => return Ok(ftd_rt::TextFormat::Markdown),
1565        })
1566    }
1567}
1568
1569#[derive(serde::Deserialize)]
1570#[cfg_attr(
1571    not(feature = "wasm"),
1572    derive(Debug, PartialEq, Clone, serde::Serialize, Default)
1573)]
1574pub struct IFrame {
1575    pub src: String,
1576    pub common: Common,
1577}
1578
1579#[derive(serde::Deserialize)]
1580#[cfg_attr(
1581    not(feature = "wasm"),
1582    derive(Debug, PartialEq, Clone, serde::Serialize, Default)
1583)]
1584pub struct Text {
1585    pub text: ftd_rt::Rendered,
1586    pub line: bool,
1587    pub common: Common,
1588    pub text_align: TextAlign,
1589    pub style: Style,
1590    pub size: Option<i64>,
1591    pub font: Vec<NamedFont>,
1592    pub external_font: Option<ExternalFont>,
1593    pub line_height: Option<i64>,
1594    pub line_clamp: Option<i64>,
1595    // TODO: line-height
1596    // TODO: region (https://package.elm-lang.org/packages/mdgriffith/elm-ui/latest/Element-Region)
1597    // TODO: family (maybe we need a type to represent font-family?)
1598    // TODO: letter-spacing
1599    // TODO: word-spacing
1600    // TODO: font-variants [small-caps, slashed-zero, feature/index etc]
1601    // TODO: shadow, glow
1602}
1603
1604#[derive(serde::Deserialize)]
1605#[cfg_attr(
1606    not(feature = "wasm"),
1607    derive(Debug, PartialEq, Clone, serde::Serialize, Default)
1608)]
1609pub struct TextBlock {
1610    pub text: ftd_rt::Rendered,
1611    pub line: bool,
1612    pub common: Common,
1613    pub text_align: TextAlign,
1614    pub style: Style,
1615    pub size: Option<i64>,
1616    pub font: Vec<NamedFont>,
1617    pub external_font: Option<ExternalFont>,
1618    pub line_height: Option<i64>,
1619    pub line_clamp: Option<i64>,
1620}
1621
1622#[derive(serde::Deserialize)]
1623#[cfg_attr(
1624    not(feature = "wasm"),
1625    derive(Debug, PartialEq, Clone, serde::Serialize, Default)
1626)]
1627pub struct Code {
1628    pub text: ftd_rt::Rendered,
1629    pub common: Common,
1630    pub text_align: TextAlign,
1631    pub style: Style,
1632    pub size: Option<i64>,
1633    pub font: Vec<NamedFont>,
1634    pub external_font: Option<ExternalFont>,
1635    pub line_height: Option<i64>,
1636    pub line_clamp: Option<i64>,
1637}
1638
1639#[derive(serde::Deserialize)]
1640#[cfg_attr(
1641    not(feature = "wasm"),
1642    derive(Debug, PartialEq, Clone, serde::Serialize, Default)
1643)]
1644pub struct Color {
1645    pub r: u8,
1646    pub g: u8,
1647    pub b: u8,
1648    pub alpha: f32,
1649}
1650
1651#[derive(serde::Deserialize)]
1652#[cfg_attr(
1653    not(feature = "wasm"),
1654    derive(Debug, PartialEq, Clone, serde::Serialize, Default)
1655)]
1656pub struct Input {
1657    pub common: Common,
1658    pub placeholder: Option<String>,
1659}