ftd_rt/
html.rs

1#[derive(serde::Deserialize)]
2#[cfg_attr(
3    not(feature = "wasm"),
4    derive(serde::Serialize, PartialEq, Debug, Default, Clone)
5)]
6pub struct Node {
7    pub condition: Option<ftd_rt::Condition>,
8    pub events: Vec<ftd_rt::Event>,
9    pub classes: Vec<String>,
10    pub node: String,
11    pub attrs: ftd_rt::Map,
12    pub style: ftd_rt::Map,
13    pub children: Vec<Node>,
14    pub external_children: Vec<Node>,
15    pub open_id: Option<String>,
16    pub external_children_container: Vec<Vec<usize>>,
17    pub children_style: ftd_rt::Map,
18    pub text: Option<String>,
19    pub null: bool,
20    pub locals: ftd_rt::Map,
21}
22
23impl Node {
24    pub fn fixed_children_style(&self, index: usize) -> ftd_rt::Map {
25        if index == 1 {
26            let mut list: ftd_rt::Map = Default::default();
27            for (key, value) in self.children_style.iter() {
28                if key == "margin-left" || key == "margin-top" {
29                    continue;
30                }
31                list.insert(key.clone(), value.clone());
32            }
33            list
34        } else {
35            self.children_style.clone()
36        }
37    }
38
39    pub fn is_visible(&self, data: &ftd_rt::DataDependenciesMap) -> bool {
40        if self.null {
41            return false;
42        }
43
44        match self.condition {
45            Some(ref v) => v.is_true(data),
46            None => true,
47        }
48    }
49
50    #[allow(clippy::too_many_arguments)]
51    pub fn to_dnode(
52        &self,
53        style: &ftd_rt::Map,
54        data: &ftd_rt::DataDependenciesMap,
55        external_children: &mut Option<Vec<Self>>,
56        external_open_id: &Option<String>,
57        external_children_container: &[Vec<usize>],
58        is_parent_visible: bool,
59        parent_id: &str,
60        is_last: bool,
61    ) -> ftd_rt::dnode::DNode {
62        let style = {
63            let mut s = self.style.clone();
64            s.extend(style.clone());
65            s
66        };
67
68        let all_children = {
69            let mut children: Vec<ftd_rt::Node> = self.children.to_vec();
70            #[allow(clippy::blocks_in_if_conditions)]
71            if let Some(ext_children) = external_children {
72                if *external_open_id
73                    == self.attrs.get("data-id").map(|v| {
74                        if v.contains(':') {
75                            let mut part = v.splitn(2, ':');
76                            part.next().unwrap().trim().to_string()
77                        } else {
78                            v.to_string()
79                        }
80                    })
81                    && self.open_id.is_none()
82                    && external_children_container.is_empty()
83                    && ((self.is_visible(data) && is_parent_visible) || is_last)
84                {
85                    for child in ext_children.iter() {
86                        if let Some(data_id) = child.attrs.get("data-id") {
87                            for child in child.children.iter() {
88                                let mut child = child.clone();
89                                child.attrs.insert(
90                                    "data-ext-id".to_string(),
91                                    format!("{}:{}", data_id, parent_id),
92                                );
93                                children.push(child);
94                            }
95                        }
96                    }
97                    *external_children = None;
98                }
99            }
100            children
101        };
102
103        let (open_id, external_children_container) =
104            if self.open_id.is_some() && external_children_container.is_empty() {
105                (&self.open_id, self.external_children_container.as_slice())
106            } else {
107                (external_open_id, external_children_container)
108            };
109
110        let mut ext_child = None;
111        let mut is_borrowed_ext_child = false;
112
113        let ext_child: &mut Option<Vec<Self>> = {
114            if external_children_container.is_empty() {
115                &mut ext_child
116            } else if self.open_id.is_some() && !self.external_children.is_empty() {
117                ext_child = Some(self.external_children.clone());
118                &mut ext_child
119            } else {
120                is_borrowed_ext_child = true;
121                external_children
122            }
123        };
124
125        let mut index = 0;
126        let mut index_of_visible_children = 0;
127
128        let children = {
129            let mut children: Vec<ftd_rt::dnode::DNode> = vec![];
130            for (i, v) in all_children.iter().enumerate() {
131                if v.node.is_empty() {
132                    continue;
133                }
134
135                let (external_container, is_last) = {
136                    let mut external_container = vec![];
137                    while index < external_children_container.len() {
138                        if let Some(container) = external_children_container[index].get(0) {
139                            if container < &i {
140                                index += 1;
141                                continue;
142                            }
143                            let external_child_container =
144                                external_children_container[index][1..].to_vec();
145                            if container == &i {
146                                if !external_child_container.is_empty() {
147                                    external_container.push(external_child_container)
148                                }
149                            } else {
150                                break;
151                            }
152                        }
153                        index += 1;
154                    }
155                    let is_last = {
156                        let mut last = external_container.is_empty()
157                            && (index >= external_children_container.len()
158                                || !is_other_sibling_visible(
159                                    i,
160                                    &all_children,
161                                    index,
162                                    external_children_container,
163                                ));
164                        if is_borrowed_ext_child {
165                            last = is_last && last;
166                        }
167                        last
168                    };
169
170                    (external_container, is_last)
171                };
172
173                if v.is_visible(data) {
174                    index_of_visible_children += 1;
175                }
176
177                children.push(v.to_dnode(
178                    &self.fixed_children_style(index_of_visible_children),
179                    data,
180                    ext_child,
181                    open_id,
182                    external_container.as_slice(),
183                    is_parent_visible && self.is_visible(data),
184                    parent_id,
185                    is_last,
186                ));
187            }
188            children
189        };
190
191        let attrs = {
192            let mut attrs = self.attrs.to_owned();
193            let oid = if let Some(oid) = attrs.get("data-id") {
194                format!("{}:{}", oid, parent_id)
195            } else {
196                format!("{}:root", parent_id)
197            };
198            attrs.insert("data-id".to_string(), oid);
199            attrs
200        };
201
202        return ftd_rt::dnode::DNode {
203            classes: self.classes.to_owned(),
204            node: self.node.to_owned(),
205            attrs,
206            style,
207            children,
208            text: self.text.to_owned(),
209            null: self.null.to_owned(),
210            events: self.events.to_owned(),
211            visible: self.is_visible(data),
212        };
213
214        fn is_other_sibling_visible(
215            index: usize,
216            all_children: &[Node],
217            ext_child_container_index: usize,
218            external_children_container: &[Vec<usize>],
219        ) -> bool {
220            for external_child_container in external_children_container
221                .iter()
222                .skip(ext_child_container_index)
223            {
224                if let Some(container) = external_child_container.get(0) {
225                    if container < &index {
226                        continue;
227                    }
228                    if let Some(child) = all_children.get(*container) {
229                        if !child.node.is_empty() {
230                            return true;
231                        }
232                    }
233                }
234            }
235            false
236        }
237    }
238
239    pub fn to_html(
240        &self,
241        style: &ftd_rt::Map,
242        data: &ftd_rt::DataDependenciesMap,
243        id: &str,
244    ) -> String {
245        self.to_dnode(style, data, &mut None, &None, &[], true, id, false)
246            .to_html(id)
247    }
248
249    pub fn get_target_node(&mut self, container: Vec<usize>) -> &mut Self {
250        let mut current = self;
251        for i in container.iter() {
252            current = &mut current.children[*i];
253        }
254        current
255    }
256}
257
258impl ftd_rt::Element {
259    pub fn to_node(&self, doc_id: &str) -> Node {
260        match self {
261            Self::Row(i) => (i.to_node(doc_id)),
262            Self::Scene(i) => (i.to_node(doc_id)),
263            Self::Text(i) => (i.to_node(doc_id)),
264            Self::TextBlock(i) => (i.to_node(doc_id)),
265            Self::Code(i) => (i.to_node(doc_id)),
266            Self::Image(i) => (i.to_node(doc_id)),
267            Self::Column(i) => (i.to_node(doc_id)),
268            Self::IFrame(i) => (i.to_node(doc_id)),
269            Self::Input(i) => (i.to_node(doc_id)),
270            Self::Integer(i) => (i.to_node(doc_id)),
271            Self::Boolean(i) => (i.to_node(doc_id)),
272            Self::Decimal(i) => (i.to_node(doc_id)),
273            Self::Null => Node {
274                condition: None,
275                events: vec![],
276                classes: vec![],
277                node: "".to_string(),
278                attrs: Default::default(),
279                style: Default::default(),
280                children: vec![],
281                external_children: Default::default(),
282                open_id: None,
283                external_children_container: vec![],
284                children_style: Default::default(),
285                text: None,
286                null: true,
287                locals: Default::default(),
288            },
289        }
290    }
291
292    // TODO: only when wasm feature is enabled
293    pub fn to_dom(&self, _id: &str) {
294        todo!()
295    }
296}
297
298impl Node {
299    fn from_common(node: &str, common: &ftd_rt::Common, doc_id: &str) -> Self {
300        Node {
301            condition: common.condition.clone(),
302            node: s(node),
303            attrs: common.attrs(),
304            style: common.style(doc_id),
305            children: vec![],
306            external_children: Default::default(),
307            open_id: None,
308            external_children_container: vec![],
309            children_style: common.children_style(),
310            text: None,
311            classes: common.add_class(),
312            null: false,
313            events: common.events.clone(),
314            locals: common.locals.clone(),
315        }
316    }
317
318    fn from_container(
319        common: &ftd_rt::Common,
320        container: &ftd_rt::Container,
321        doc_id: &str,
322    ) -> Self {
323        let mut attrs = common.attrs();
324        attrs.extend(container.attrs());
325        let mut style = common.style(doc_id);
326        style.extend(container.style());
327        let mut classes = common.add_class();
328        classes.extend(container.add_class());
329
330        let mut children_style = common.children_style();
331        children_style.extend(container.children_style());
332        let node = match common.link {
333            Some(_) => "a",
334            None => match common.submit {
335                Some(_) => "form",
336                None => "div",
337            },
338        };
339
340        let (id, external_children_container, external_children) = {
341            if let Some((id, external_children_container, child)) = &container.external_children {
342                (
343                    Some(id.to_string()),
344                    external_children_container.clone(),
345                    child.iter().map(|v| v.to_node(doc_id)).collect(),
346                )
347            } else {
348                (None, vec![], vec![])
349            }
350        };
351
352        Node {
353            condition: common.condition.clone(),
354            node: s(node), // TODO: use better tags based on common.region
355            attrs,
356            style,
357            classes,
358            children_style,
359            text: None,
360            children: container
361                .children
362                .iter()
363                .map(|v| v.to_node(doc_id))
364                .collect(),
365            external_children,
366            open_id: id,
367            external_children_container,
368            null: false,
369            events: common.events.clone(),
370            locals: common.locals.clone(),
371        }
372    }
373}
374
375impl ftd_rt::Scene {
376    pub fn to_node(&self, doc_id: &str) -> Node {
377        let node = {
378            let mut node = Node {
379                node: s("div"),
380                ..Default::default()
381            };
382            if let Some(ref data_id) = self.common.data_id {
383                node.attrs
384                    .insert(s("data-id"), format!("{}:scene", data_id));
385            } else {
386                node.attrs.insert(s("data-id"), s("scene:root"));
387            }
388            node.style.insert(s("position"), s("relative"));
389            let children = {
390                let parent = {
391                    let mut node = if let Some(ref img) = self.common.background_image {
392                        let mut n = Node {
393                            node: s("img"),
394                            ..Default::default()
395                        };
396                        n.attrs.insert(s("src"), s(img));
397                        n.attrs.insert(s("alt"), escape("Scene"));
398                        n
399                    } else {
400                        Node {
401                            node: s("div"),
402                            ..Default::default()
403                        }
404                    };
405                    node.style.insert(s("width"), s("100%"));
406                    if !self.common.is_not_visible {
407                        node.style.insert(s("display"), s("block"));
408                    }
409                    node.style.insert(s("height"), s("auto"));
410                    if let Some(ref data_id) = self.common.data_id {
411                        node.attrs
412                            .insert(s("data-id"), format!("{}:scene-bg", data_id));
413                    }
414                    node
415                };
416                let mut children: Vec<Node> = self
417                    .container
418                    .children
419                    .iter()
420                    .map(|v| {
421                        let mut n = v.to_node(doc_id);
422                        n.style.insert(s("position"), s("absolute"));
423                        n
424                    })
425                    .collect();
426                children.insert(0, parent);
427                children
428            };
429
430            let (id, external_children_container, external_children) = {
431                if let Some((id, external_children_container, child)) =
432                    &self.container.external_children
433                {
434                    (
435                        Some(id.to_string()),
436                        external_children_container.clone(),
437                        child
438                            .iter()
439                            .map(|v| {
440                                let mut n = v.to_node(doc_id);
441                                n.style.insert(s("position"), s("absolute"));
442                                n
443                            })
444                            .collect(),
445                    )
446                } else {
447                    (None, vec![], vec![])
448                }
449            };
450
451            node.children = children;
452            node.open_id = id;
453            node.external_children = external_children;
454            node.external_children_container = external_children_container;
455            node
456        };
457
458        let mut main_node = Node::from_common("div", &self.common, doc_id);
459        if self.common.width.is_none() {
460            main_node.style.insert(s("width"), s("1000px"));
461        }
462        if let Some(p) = self.container.spacing {
463            main_node
464                .children_style
465                .insert(s("margin-left"), format!("{}px", p));
466        }
467        main_node.children = vec![node];
468        main_node
469    }
470}
471
472impl ftd_rt::Row {
473    pub fn to_node(&self, doc_id: &str) -> Node {
474        let mut n = Node::from_container(&self.common, &self.container, doc_id);
475        if !self.common.is_not_visible {
476            n.style.insert(s("display"), s("flex"));
477        }
478        n.style.insert(s("flex-direction"), s("row"));
479        if self.container.wrap {
480            n.style.insert(s("flex-wrap"), s("wrap"));
481        } else {
482            n.style.insert(s("flex-wrap"), s("nowrap"));
483        }
484
485        n.style.insert(s("align-items"), s("flex-start"));
486
487        n.style.insert(s("justify-content"), s("flex-start"));
488
489        if let Some(p) = self.container.spacing {
490            n.children_style
491                .insert(s("margin-left"), format!("{}px", p));
492            n.attrs
493                .insert(s("data-spacing"), format!("margin-left:{}px", p));
494        }
495
496        n
497    }
498}
499
500impl ftd_rt::Column {
501    pub fn to_node(&self, doc_id: &str) -> Node {
502        let mut n = Node::from_container(&self.common, &self.container, doc_id);
503        if !self.common.is_not_visible {
504            n.style.insert(s("display"), s("flex"));
505        }
506        n.style.insert(s("flex-direction"), s("column"));
507        if self.container.wrap {
508            n.style.insert(s("flex-wrap"), s("wrap"));
509        } else {
510            n.style.insert(s("flex-wrap"), s("nowrap"));
511        }
512        n.style.insert(s("align-items"), s("flex-start"));
513
514        n.style.insert(s("justify-content"), s("flex-start"));
515
516        if let Some(p) = self.container.spacing {
517            n.children_style.insert(s("margin-top"), format!("{}px", p));
518            n.attrs
519                .insert(s("data-spacing"), format!("margin-top:{}px", p));
520        }
521
522        n
523    }
524}
525
526impl ftd_rt::Text {
527    pub fn to_node(&self, doc_id: &str) -> Node {
528        // TODO: proper tag based on self.common.region
529        // TODO: if format is not markdown use pre
530        let node = match &self.common.link {
531            Some(_) => "a",
532            None => match &self.common.submit {
533                Some(_) => "form",
534                _ => "div",
535            },
536        };
537        let mut n = Node::from_common(node, &self.common, doc_id);
538        n.text = Some(self.text.rendered.clone());
539        let (key, value) = text_align(&self.text_align);
540        n.style.insert(s(key.as_str()), value);
541        if let Some(p) = self.size {
542            n.style.insert(s("font-size"), format!("{}px", p));
543        }
544        if let Some(p) = self.line_height {
545            n.style.insert(s("line-height"), format!("{}px", p));
546        } else if !&self.line {
547            n.style.insert(s("line-height"), s("26px"));
548        }
549
550        if self.style.italic {
551            n.style.insert(s("font-style"), s("italic"));
552        }
553        if self.style.underline {
554            n.style.insert(s("text-decoration"), s("underline"));
555        }
556        if self.style.strike {
557            n.style.insert(s("text-decoration"), s("line-through"));
558        }
559
560        if let Some(p) = &self.line_clamp {
561            n.style.insert(s("display"), "-webkit-box".to_string());
562            n.style.insert(s("overflow"), "hidden".to_string());
563            n.style.insert(s("-webkit-line-clamp"), format!("{}", p));
564            n.style
565                .insert(s("-webkit-box-orient"), "vertical".to_string());
566        }
567
568        let (key, value) = style(&self.style.weight);
569        n.style.insert(s(key.as_str()), value);
570
571        // TODO: text styles
572        n
573    }
574}
575
576impl ftd_rt::TextBlock {
577    pub fn to_node(&self, doc_id: &str) -> Node {
578        // TODO: proper tag based on self.common.region
579        // TODO: if format is not markdown use pre
580        let node = match &self.common.link {
581            Some(_) => "a",
582            None => match &self.common.submit {
583                Some(_) => "form",
584                _ => "div",
585            },
586        };
587        let mut n = Node::from_common(node, &self.common, doc_id);
588        n.text = Some(self.text.rendered.clone());
589        let (key, value) = text_align(&self.text_align);
590        n.style.insert(s(key.as_str()), value);
591        if let Some(p) = self.size {
592            n.style.insert(s("font-size"), format!("{}px", p));
593        }
594        if let Some(p) = self.line_height {
595            n.style.insert(s("line-height"), format!("{}px", p));
596        } else if !&self.line {
597            n.style.insert(s("line-height"), s("26px"));
598        }
599
600        if self.style.italic {
601            n.style.insert(s("font-style"), s("italic"));
602        }
603        if self.style.underline {
604            n.style.insert(s("text-decoration"), s("underline"));
605        }
606        if self.style.strike {
607            n.style.insert(s("text-decoration"), s("line-through"));
608        }
609
610        if let Some(p) = &self.line_clamp {
611            n.style.insert(s("display"), "-webkit-box".to_string());
612            n.style.insert(s("overflow"), "hidden".to_string());
613            n.style.insert(s("-webkit-line-clamp"), format!("{}", p));
614            n.style
615                .insert(s("-webkit-box-orient"), "vertical".to_string());
616        }
617
618        let (key, value) = style(&self.style.weight);
619        n.style.insert(s(key.as_str()), value);
620
621        // TODO: text styles
622        n
623    }
624}
625
626impl ftd_rt::Code {
627    pub fn to_node(&self, doc_id: &str) -> Node {
628        let node = match &self.common.link {
629            Some(_) => "a",
630            None => match &self.common.submit {
631                Some(_) => "form",
632                _ => "div",
633            },
634        };
635        let mut n = Node::from_common(node, &self.common, doc_id);
636        n.text = Some(self.text.rendered.clone());
637        let (key, value) = text_align(&self.text_align);
638        n.style.insert(s(key.as_str()), value);
639        if let Some(p) = self.size {
640            n.style.insert(s("font-size"), format!("{}px", p));
641        }
642        if let Some(p) = self.line_height {
643            n.style.insert(s("line-height"), format!("{}px", p));
644        } else {
645            n.style.insert(s("line-height"), s("26px"));
646        }
647
648        if self.style.italic {
649            n.style.insert(s("font-style"), s("italic"));
650        }
651        if self.style.underline {
652            n.style.insert(s("text-decoration"), s("underline"));
653        }
654        if self.style.strike {
655            n.style.insert(s("text-decoration"), s("line-through"));
656        }
657
658        if let Some(p) = &self.line_clamp {
659            n.style.insert(s("display"), "-webkit-box".to_string());
660            n.style.insert(s("overflow"), "hidden".to_string());
661            n.style.insert(s("-webkit-line-clamp"), format!("{}", p));
662            n.style
663                .insert(s("-webkit-box-orient"), "vertical".to_string());
664        }
665
666        let (key, value) = style(&self.style.weight);
667        n.style.insert(s(key.as_str()), value);
668        n
669    }
670}
671
672impl ftd_rt::Image {
673    pub fn to_node(&self, doc_id: &str) -> Node {
674        let mut n = Node::from_common("img", &self.common, doc_id);
675        if self.common.link.is_some() {
676            n.node = s("a");
677            let mut img = Node {
678                condition: None,
679                events: vec![],
680                classes: vec![],
681                node: s("img"),
682                attrs: Default::default(),
683                style: Default::default(),
684                children: vec![],
685                external_children: vec![],
686                open_id: None,
687                external_children_container: vec![],
688                children_style: Default::default(),
689                text: None,
690                null: false,
691                locals: Default::default(),
692            };
693            img.style.insert(s("width"), s("100%"));
694            img.attrs.insert(s("src"), escape(self.src.as_str()));
695            img.attrs
696                .insert(s("alt"), escape(self.description.as_str()));
697            if self.crop {
698                img.style.insert(s("object-fit"), s("cover"));
699                img.style.insert(s("object-position"), s("0 0"));
700            }
701            n.children.push(img);
702        } else {
703            n.attrs.insert(s("src"), escape(self.src.as_str()));
704            n.attrs.insert(s("alt"), escape(self.description.as_str()));
705            if self.crop {
706                n.style.insert(s("object-fit"), s("cover"));
707                n.style.insert(s("object-position"), s("0 0"));
708            }
709        }
710        if self.common.width == None {
711            n.style.insert(s("width"), s("100%"));
712        }
713
714        n
715    }
716}
717
718impl ftd_rt::IFrame {
719    pub fn to_node(&self, doc_id: &str) -> Node {
720        let mut n = Node::from_common("iframe", &self.common, doc_id);
721        n.attrs.insert(s("src"), escape(self.src.as_str()));
722        n.attrs.insert(s("allow"), s("fullscreen"));
723        n.attrs.insert(s("allowfullscreen"), s("allowfullscreen"));
724        n
725    }
726}
727
728impl ftd_rt::Input {
729    pub fn to_node(&self, doc_id: &str) -> Node {
730        let mut n = Node::from_common("input", &self.common, doc_id);
731        if let Some(ref p) = self.placeholder {
732            n.attrs.insert(s("placeholder"), escape(p));
733        }
734        n
735    }
736}
737
738impl ftd_rt::Common {
739    fn add_class(&self) -> Vec<String> {
740        let d: Vec<String> = vec![s("ft_md")];
741        d
742    }
743    fn children_style(&self) -> ftd_rt::Map {
744        let d: ftd_rt::Map = Default::default();
745        d
746    }
747
748    fn style(&self, doc_id: &str) -> ftd_rt::Map {
749        let mut d: ftd_rt::Map = Default::default();
750
751        if !self.events.is_empty() && self.cursor.is_none() {
752            d.insert(s("cursor"), s("pointer"));
753        }
754        if self.is_not_visible {
755            d.insert(s("display"), s("none"));
756        }
757
758        if let Some(p) = self.padding {
759            d.insert(s("padding"), format!("{}px", p));
760        }
761        if let Some(p) = self.padding_left {
762            d.insert(s("padding-left"), format!("{}px", p));
763        }
764        if let Some(ref cursor) = self.cursor {
765            d.insert(s("cursor"), s(cursor));
766        }
767        if let Some(p) = self.padding_vertical {
768            d.insert(s("padding-top"), format!("{}px", p));
769            d.insert(s("padding-bottom"), format!("{}px", p));
770        }
771        if let Some(p) = self.padding_horizontal {
772            d.insert(s("padding-left"), format!("{}px", p));
773            d.insert(s("padding-right"), format!("{}px", p));
774        }
775        if let Some(p) = self.padding_right {
776            d.insert(s("padding-right"), format!("{}px", p));
777        }
778        if let Some(p) = self.padding_top {
779            d.insert(s("padding-top"), format!("{}px", p));
780        }
781        if let Some(p) = self.padding_bottom {
782            d.insert(s("padding-bottom"), format!("{}px", p));
783        }
784
785        if let Some(p) = self.border_top_radius {
786            d.insert(s("border-top-left-radius"), format!("{}px !important", p));
787            d.insert(s("border-top-right-radius"), format!("{}px !important", p));
788        }
789
790        if let Some(p) = self.border_left_radius {
791            d.insert(s("border-top-left-radius"), format!("{}px !important", p));
792            d.insert(
793                s("border-bottom-left-radius"),
794                format!("{}px !important", p),
795            );
796        }
797
798        if let Some(p) = self.border_right_radius {
799            d.insert(s("border-top-right-radius"), format!("{}px !important", p));
800            d.insert(
801                s("border-bottom-right-radius"),
802                format!("{}px !important", p),
803            );
804        }
805
806        if let Some(p) = self.border_bottom_radius {
807            d.insert(
808                s("border-bottom-right-radius"),
809                format!("{}px !important", p),
810            );
811            d.insert(
812                s("border-bottom-left-radius"),
813                format!("{}px !important", p),
814            );
815        }
816
817        if let Some(p) = &self.width {
818            let (key, value) = length(p, "width");
819            d.insert(s(key.as_str()), value);
820        }
821        if let Some(p) = &self.min_width {
822            let (key, value) = length(p, "min-width");
823            d.insert(s(key.as_str()), value);
824        }
825        if let Some(p) = &self.max_width {
826            let (key, value) = length(p, "max-width");
827            d.insert(s(key.as_str()), value);
828        }
829        if let Some(p) = &self.height {
830            let (key, value) = length(p, "height");
831            d.insert(s(key.as_str()), value);
832        }
833        if let Some(p) = &self.min_height {
834            let (key, value) = length(p, "min-height");
835            d.insert(s(key.as_str()), value);
836        }
837        if let Some(p) = &self.max_height {
838            let (key, value) = length(p, "max-height");
839            d.insert(s(key.as_str()), value);
840        }
841        if let Some(p) = self.border_left {
842            d.insert(s("border-left-width"), format!("{}px !important", p));
843        }
844        if let Some(p) = self.border_right {
845            d.insert(s("border-right-width"), format!("{}px !important", p));
846        }
847        if let Some(p) = self.border_top {
848            d.insert(s("border-top-width"), format!("{}px !important", p));
849        }
850        if let Some(p) = self.border_bottom {
851            d.insert(s("border-bottom-width"), format!("{}px !important", p));
852        }
853        if let Some(p) = self.margin_left {
854            d.insert(s("margin-left"), format!("{}px", p));
855        }
856        if let Some(p) = self.margin_right {
857            d.insert(s("margin-right"), format!("{}px", p));
858        }
859        if let Some(p) = self.margin_top {
860            d.insert(s("margin-top"), format!("{}px", p));
861        }
862        if let Some(p) = self.margin_bottom {
863            d.insert(s("margin-bottom"), format!("{}px", p));
864        }
865        if let Some(p) = &self.background_color {
866            d.insert(s("background-color"), color(p));
867        }
868        if let Some(p) = &self.color {
869            d.insert(s("color"), color(p));
870        }
871        if let Some(p) = &self.border_color {
872            d.insert(s("border-color"), color(p));
873        }
874        if let Some(p) = &self.overflow_x {
875            let (key, value) = overflow(p, "overflow-x");
876            d.insert(s(key.as_str()), value);
877        }
878        if let Some(p) = &self.overflow_y {
879            let (key, value) = overflow(p, "overflow-y");
880            d.insert(s(key.as_str()), value);
881        }
882        if self.sticky {
883            d.insert(s("position"), s("sticky"));
884        }
885        if let Some(p) = &self.top {
886            d.insert(s("top"), format!("{}px", p));
887        }
888        if let Some(p) = &self.bottom {
889            d.insert(s("bottom"), format!("{}px", p));
890        }
891        if let Some(p) = &self.left {
892            d.insert(s("left"), format!("{}px", p));
893        }
894        if let Some(p) = &self.right {
895            d.insert(s("right"), format!("{}px", p));
896        }
897        if self.submit.is_some() {
898            d.insert(s("cursor"), s("pointer"));
899        }
900        if self.link.is_some() {
901            d.insert(s("cursor"), s("pointer"));
902        }
903        if self.shadow_size.is_some()
904            || self.shadow_blur.is_some()
905            || self.shadow_offset_x.is_some()
906            || self.shadow_offset_y.is_some()
907        {
908            let shadow_color = match &self.shadow_color {
909                Some(p) => p,
910                None => &ftd_rt::Color {
911                    r: 0,
912                    g: 0,
913                    b: 0,
914                    alpha: 0.25,
915                },
916            };
917
918            d.insert(
919                s("box-shadow"),
920                format!(
921                    "{}px {}px {}px {}px {}",
922                    self.shadow_offset_x.unwrap_or(0),
923                    self.shadow_offset_y.unwrap_or(0),
924                    self.shadow_blur.unwrap_or(0),
925                    self.shadow_size.unwrap_or(0),
926                    color(shadow_color),
927                ),
928            );
929        }
930        if let Some(p) = &self.anchor {
931            d.insert(s("position"), p.to_postion());
932        }
933        if let Some(p) = &self.gradient_direction {
934            d.insert(s("background-image"), gradient(p, &self.gradient_colors));
935        }
936        if let Some(p) = &self.background_image {
937            d.insert(s("background-image"), format!("url({})", p));
938            if self.background_repeat {
939                d.insert(s("background-repeat"), s("repeat"));
940            } else {
941                d.insert(s("background-size"), s("cover"));
942                d.insert(s("background-position"), s("center"));
943            }
944            if self.background_parallax {
945                d.insert(s("background-attachment"), s("fixed"));
946            }
947        }
948
949        match &self.anchor {
950            Some(_) => {
951                for (key, value) in non_static_container_align(&self.position, self.inner) {
952                    d.insert(s(key.as_str()), value);
953                }
954            }
955            None => {
956                for (key, value) in container_align(&self.position) {
957                    d.insert(s(key.as_str()), value);
958                }
959            }
960        }
961
962        let translate = get_translate(
963            &self.move_left,
964            &self.move_right,
965            &self.move_up,
966            &self.move_down,
967            &self.scale,
968            &self.scale_x,
969            &self.scale_y,
970            &self.rotate,
971            doc_id,
972        )
973        .unwrap();
974
975        if let Some(p) = translate {
976            let data = if let Some(d) = d.get_mut("transform") {
977                format!("{} {}", d, p)
978            } else {
979                p
980            };
981            d.insert(s("transform"), data);
982        }
983
984        d.insert(s("border-style"), s("solid"));
985        d.insert(s("border-width"), format!("{}px", self.border_width));
986        d.insert(s("border-radius"), format!("{}px", self.border_radius));
987        d.insert(s("box-sizing"), s("border-box"));
988        d.insert(s("white-space"), s("initial"));
989
990        d
991    }
992
993    fn attrs(&self) -> ftd_rt::Map {
994        let mut d: ftd_rt::Map = Default::default();
995        if let Some(ref id) = self.data_id {
996            d.insert(s("data-id"), escape(id));
997        }
998        if let Some(ref id) = self.id {
999            d.insert(s("id"), escape(id));
1000        }
1001        // TODO(move-to-ftd): the link should be escaped
1002        if let Some(ref link) = self.link {
1003            d.insert(s("href"), link.to_string());
1004        }
1005        if self.open_in_new_tab {
1006            d.insert(s("target"), escape("_blank"));
1007        }
1008        if let Some(ref link) = self.submit {
1009            if cfg!(feature = "realm") {
1010                d.insert(
1011                    s("onclick"),
1012                    format!("window.REALM_SUBMIT('{}');", escape(link)),
1013                );
1014            } else {
1015                d.insert(s("onclick"), "this.submit()".to_string());
1016            }
1017        }
1018        d
1019    }
1020}
1021impl ftd_rt::Container {
1022    fn style(&self) -> ftd_rt::Map {
1023        let mut d: ftd_rt::Map = Default::default();
1024        let mut count = count_children_with_absolute_parent(&self.children);
1025        if let Some((_, _, ref ext_children)) = self.external_children {
1026            count += count_children_with_absolute_parent(ext_children);
1027        }
1028        if count != 0 {
1029            d.insert(s("position"), s("relative"));
1030        }
1031        return d;
1032
1033        fn count_children_with_absolute_parent(children: &[ftd_rt::Element]) -> usize {
1034            children
1035                .iter()
1036                .filter(|v| {
1037                    let mut bool = false;
1038                    if let Some(common) = v.get_common() {
1039                        if Some(ftd_rt::Anchor::Parent) == common.anchor {
1040                            bool = true;
1041                        }
1042                    }
1043                    bool
1044                })
1045                .count()
1046        }
1047    }
1048    fn children_style(&self) -> ftd_rt::Map {
1049        let d: ftd_rt::Map = Default::default();
1050        d
1051    }
1052
1053    fn attrs(&self) -> ftd_rt::Map {
1054        let d: ftd_rt::Map = Default::default();
1055        d
1056    }
1057    fn add_class(&self) -> Vec<String> {
1058        let d: Vec<String> = Default::default();
1059        d
1060    }
1061}
1062
1063pub fn escape(s: &str) -> String {
1064    let s = s.replace('>', "\\u003E");
1065    let s = s.replace('<', "\\u003C");
1066    s.replace('&', "\\u0026")
1067}
1068
1069fn s(s: &str) -> String {
1070    s.to_string()
1071}
1072
1073pub fn color(c: &ftd_rt::Color) -> String {
1074    let ftd_rt::Color { r, g, b, alpha } = c;
1075    format!("rgba({},{},{},{})", r, g, b, alpha)
1076}
1077
1078pub fn length(l: &ftd_rt::Length, f: &str) -> (String, String) {
1079    let s = f.to_string();
1080    match l {
1081        ftd_rt::Length::Fill => (s, "100%".to_string()),
1082        ftd_rt::Length::Auto => (s, "auto".to_string()),
1083        ftd_rt::Length::Px { value } => (s, format!("{}px", value)),
1084        ftd_rt::Length::Portion { value } => ("flex-grow".to_string(), value.to_string()),
1085        ftd_rt::Length::Percent { value } => (s, format!("{}%", value)),
1086        ftd_rt::Length::FitContent => (s, "fit-content".to_string()),
1087        ftd_rt::Length::Calc { value } => (s, format!("calc({})", value)),
1088
1089        _ => (s, "100%".to_string()),
1090        //        ftd_rt::Length::Shrink => (s, "width".to_string()),   TODO
1091    }
1092}
1093
1094fn text_align(l: &ftd_rt::TextAlign) -> (String, String) {
1095    match l {
1096        ftd_rt::TextAlign::Center => ("text-align".to_string(), "center".to_string()),
1097        ftd_rt::TextAlign::Left => ("text-align".to_string(), "left".to_string()),
1098        ftd_rt::TextAlign::Right => ("text-align".to_string(), "right".to_string()),
1099        ftd_rt::TextAlign::Justify => ("text-align".to_string(), "justify".to_string()),
1100    }
1101}
1102
1103fn style(l: &ftd_rt::Weight) -> (String, String) {
1104    match l {
1105        ftd_rt::Weight::Heavy => ("font-weight".to_string(), "900".to_string()),
1106        ftd_rt::Weight::ExtraBold => ("font-weight".to_string(), "800".to_string()),
1107        ftd_rt::Weight::Bold => ("font-weight".to_string(), "700".to_string()),
1108        ftd_rt::Weight::SemiBold => ("font-weight".to_string(), "600".to_string()),
1109        ftd_rt::Weight::Medium => ("font-weight".to_string(), "500".to_string()),
1110        ftd_rt::Weight::Regular => ("font-weight".to_string(), "400".to_string()),
1111        ftd_rt::Weight::Light => ("font-weight".to_string(), "300".to_string()),
1112        ftd_rt::Weight::ExtraLight => ("font-weight".to_string(), "200".to_string()),
1113        ftd_rt::Weight::HairLine => ("font-weight".to_string(), "100".to_string()),
1114    }
1115}
1116
1117pub fn overflow(l: &ftd_rt::Overflow, f: &str) -> (String, String) {
1118    let s = f.to_string();
1119    match l {
1120        ftd_rt::Overflow::Auto => (s, "auto".to_string()),
1121        ftd_rt::Overflow::Hidden => (s, "hidden".to_string()),
1122        ftd_rt::Overflow::Scroll => (s, "scroll".to_string()),
1123        ftd_rt::Overflow::Visible => (s, "visible".to_string()),
1124    }
1125}
1126
1127fn gradient(d: &ftd_rt::GradientDirection, c: &[ftd_rt::Color]) -> String {
1128    let color = c
1129        .iter()
1130        .map(|v| color(v))
1131        .collect::<Vec<String>>()
1132        .join(",");
1133    let gradient_style = match d {
1134        ftd_rt::GradientDirection::BottomToTop => "linear-gradient(to top ".to_string(),
1135        ftd_rt::GradientDirection::TopToBottom => "linear-gradient(to bottom ".to_string(),
1136        ftd_rt::GradientDirection::LeftToRight => "linear-gradient(to right".to_string(),
1137        ftd_rt::GradientDirection::RightToLeft => "linear-gradient(to left".to_string(),
1138        ftd_rt::GradientDirection::BottomRightToTopLeft => {
1139            "linear-gradient(to top left".to_string()
1140        }
1141        ftd_rt::GradientDirection::TopLeftBottomRight => {
1142            "linear-gradient(to bottom right".to_string()
1143        }
1144        ftd_rt::GradientDirection::BottomLeftToTopRight => {
1145            "linear-gradient(to top right".to_string()
1146        }
1147        ftd_rt::GradientDirection::TopRightToBottomLeft => {
1148            "linear-gradient(to bottom left".to_string()
1149        }
1150        ftd_rt::GradientDirection::Center => "radial-gradient(circle ".to_string(),
1151        ftd_rt::GradientDirection::Angle { value } => format!("linear-gradient({}deg", value),
1152    };
1153    format!("{}, {} )", gradient_style, color)
1154}
1155
1156pub fn anchor(l: &ftd_rt::Anchor) -> String {
1157    match l {
1158        ftd_rt::Anchor::Parent => ("absolute".to_string()),
1159        ftd_rt::Anchor::Window => ("fixed".to_string()),
1160    }
1161}
1162
1163fn container_align(l: &ftd_rt::Position) -> Vec<(String, String)> {
1164    match l {
1165        ftd_rt::Position::Center => vec![
1166            ("align-self".to_string(), "center".to_string()),
1167            ("margin-bottom".to_string(), "auto".to_string()),
1168            ("margin-top".to_string(), "auto".to_string()),
1169        ],
1170        ftd_rt::Position::Top => vec![
1171            ("align-self".to_string(), "center".to_string()),
1172            ("margin-bottom".to_string(), "auto".to_string()),
1173        ],
1174        ftd_rt::Position::Left => vec![
1175            ("align-self".to_string(), "flex-start".to_string()),
1176            ("margin-bottom".to_string(), "auto".to_string()),
1177            ("margin-top".to_string(), "auto".to_string()),
1178        ],
1179        ftd_rt::Position::Right => vec![
1180            ("align-self".to_string(), "flex-end".to_string()),
1181            ("margin-bottom".to_string(), "auto".to_string()),
1182            ("margin-top".to_string(), "auto".to_string()),
1183            ("margin-left".to_string(), "auto".to_string()),
1184        ],
1185        ftd_rt::Position::Bottom => vec![
1186            ("align-self".to_string(), "center".to_string()),
1187            ("margin-bottom".to_string(), "0".to_string()),
1188            ("margin-top".to_string(), "auto".to_string()),
1189        ],
1190        ftd_rt::Position::TopLeft => vec![("align-self".to_string(), "flex-start".to_string())],
1191        ftd_rt::Position::TopRight => vec![
1192            ("align-self".to_string(), "flex-end".to_string()),
1193            ("margin-left".to_string(), "auto".to_string()),
1194        ],
1195        ftd_rt::Position::BottomLeft => vec![
1196            ("align-self".to_string(), "flex-start".to_string()),
1197            ("margin-bottom".to_string(), "0".to_string()),
1198            ("margin-top".to_string(), "auto".to_string()),
1199        ],
1200        ftd_rt::Position::BottomRight => vec![
1201            ("align-self".to_string(), "flex-end".to_string()),
1202            ("margin-bottom".to_string(), "0".to_string()),
1203            ("margin-top".to_string(), "auto".to_string()),
1204            ("margin-left".to_string(), "auto".to_string()),
1205        ],
1206    }
1207}
1208
1209fn non_static_container_align(l: &ftd_rt::Position, inner: bool) -> Vec<(String, String)> {
1210    match l {
1211        ftd_rt::Position::Center => vec![
1212            ("left".to_string(), "50%".to_string()),
1213            ("top".to_string(), "50%".to_string()),
1214            ("transform".to_string(), "translate(-50%,-50%)".to_string()),
1215        ],
1216        ftd_rt::Position::Top => {
1217            if inner {
1218                vec![
1219                    ("top".to_string(), "0".to_string()),
1220                    ("left".to_string(), "50%".to_string()),
1221                    ("transform".to_string(), "translateX(-50%)".to_string()),
1222                ]
1223            } else {
1224                vec![
1225                    ("bottom".to_string(), "100%".to_string()),
1226                    ("left".to_string(), "50%".to_string()),
1227                    ("transform".to_string(), "translateX(-50%)".to_string()),
1228                ]
1229            }
1230        }
1231        ftd_rt::Position::Left => {
1232            if inner {
1233                vec![
1234                    ("left".to_string(), "0".to_string()),
1235                    ("top".to_string(), "50%".to_string()),
1236                    ("transform".to_string(), "translateY(-50%)".to_string()),
1237                ]
1238            } else {
1239                vec![
1240                    ("right".to_string(), "100%".to_string()),
1241                    ("top".to_string(), "50%".to_string()),
1242                    ("transform".to_string(), "translateY(-50%)".to_string()),
1243                ]
1244            }
1245        }
1246        ftd_rt::Position::Right => {
1247            if inner {
1248                vec![
1249                    ("right".to_string(), "0".to_string()),
1250                    ("top".to_string(), "50%".to_string()),
1251                    ("transform".to_string(), "translate(-50%)".to_string()),
1252                ]
1253            } else {
1254                vec![
1255                    ("left".to_string(), "100%".to_string()),
1256                    ("top".to_string(), "50%".to_string()),
1257                    ("transform".to_string(), "translate(-50%)".to_string()),
1258                ]
1259            }
1260        }
1261        ftd_rt::Position::Bottom => {
1262            if inner {
1263                vec![
1264                    ("bottom".to_string(), "0".to_string()),
1265                    ("left".to_string(), "50".to_string()),
1266                    ("transform".to_string(), "translateX(-50%)".to_string()),
1267                ]
1268            } else {
1269                vec![
1270                    ("top".to_string(), "100%".to_string()),
1271                    ("left".to_string(), "50".to_string()),
1272                    ("transform".to_string(), "translateX(-50%)".to_string()),
1273                ]
1274            }
1275        }
1276        ftd_rt::Position::TopLeft => {
1277            if inner {
1278                vec![
1279                    ("top".to_string(), "0".to_string()),
1280                    ("left".to_string(), "0".to_string()),
1281                ]
1282            } else {
1283                vec![
1284                    ("bottom".to_string(), "100%".to_string()),
1285                    ("right".to_string(), "100%".to_string()),
1286                ]
1287            }
1288        }
1289        ftd_rt::Position::TopRight => {
1290            if inner {
1291                vec![
1292                    ("top".to_string(), "0".to_string()),
1293                    ("right".to_string(), "0".to_string()),
1294                ]
1295            } else {
1296                vec![
1297                    ("bottom".to_string(), "100%".to_string()),
1298                    ("left".to_string(), "100%".to_string()),
1299                ]
1300            }
1301        }
1302        ftd_rt::Position::BottomLeft => {
1303            if inner {
1304                vec![
1305                    ("bottom".to_string(), "0".to_string()),
1306                    ("left".to_string(), "0".to_string()),
1307                ]
1308            } else {
1309                vec![
1310                    ("top".to_string(), "100%".to_string()),
1311                    ("right".to_string(), "100%".to_string()),
1312                ]
1313            }
1314        }
1315        ftd_rt::Position::BottomRight => {
1316            if inner {
1317                vec![
1318                    ("bottom".to_string(), "0".to_string()),
1319                    ("right".to_string(), "0".to_string()),
1320                ]
1321            } else {
1322                vec![
1323                    ("top".to_string(), "100%".to_string()),
1324                    ("left".to_string(), "100%".to_string()),
1325                ]
1326            }
1327        }
1328    }
1329}
1330
1331#[allow(clippy::too_many_arguments)]
1332fn get_translate(
1333    left: &Option<i64>,
1334    right: &Option<i64>,
1335    up: &Option<i64>,
1336    down: &Option<i64>,
1337    scale: &Option<f64>,
1338    scale_x: &Option<f64>,
1339    scale_y: &Option<f64>,
1340    rotate: &Option<i64>,
1341    doc_id: &str,
1342) -> ftd_rt::Result<Option<String>> {
1343    let mut translate = match (left, right, up, down) {
1344        (Some(_), Some(_), Some(_), Some(_)) => {
1345            return ftd_rt::e(
1346                "move-up, move-down, move-left and move-right all 4 can't be used at once!",
1347                doc_id,
1348            )
1349        }
1350        (Some(_), Some(_), _, _) => {
1351            return ftd_rt::e("move-left, move-right both can't be used at once!", doc_id)
1352        }
1353        (_, _, Some(_), Some(_)) => {
1354            return ftd_rt::e("move-up, move-down both can't be used at once!", doc_id)
1355        }
1356        (Some(l), None, None, None) => Some(format!("translateX(-{}px) ", l)),
1357        (Some(l), None, Some(u), None) => Some(format!("translate(-{}px, -{}px) ", l, u)),
1358        (Some(l), None, None, Some(d)) => Some(format!("translate(-{}px, {}px) ", l, d)),
1359        (None, Some(r), None, None) => Some(format!("translateX({}px) ", r)),
1360        (None, Some(r), Some(u), None) => Some(format!("translate({}px, -{}px) ", r, u)),
1361        (None, Some(r), None, Some(d)) => Some(format!("translate({}px, {}px) ", r, d)),
1362        (None, None, Some(u), None) => Some(format!("translateY(-{}px) ", u)),
1363        (None, None, None, Some(d)) => Some(format!("translateY({}px) ", d)),
1364        _ => None,
1365    };
1366
1367    if let Some(ref scale) = scale {
1368        if let Some(d) = translate {
1369            translate = Some(format!("{} scale({})", d, scale));
1370        } else {
1371            translate = Some(format!("scale({})", scale));
1372        };
1373    }
1374    if let Some(ref scale) = scale_x {
1375        if let Some(d) = translate {
1376            translate = Some(format!("{} scaleX({})", d, scale));
1377        } else {
1378            translate = Some(format!("scaleX({})", scale));
1379        };
1380    }
1381    if let Some(ref scale) = scale_y {
1382        if let Some(d) = translate {
1383            translate = Some(format!("{} scaleY({})", d, scale));
1384        } else {
1385            translate = Some(format!("scaleY({})", scale));
1386        };
1387    }
1388    if let Some(ref rotate) = rotate {
1389        if let Some(d) = translate {
1390            translate = Some(format!("{} rotate({}deg)", d, rotate));
1391        } else {
1392            translate = Some(format!("rotate({}deg)", rotate));
1393        };
1394    }
1395    Ok(translate)
1396}