1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
#[cfg(any(client, doc))]
use sycamore::utils::render::insert;
#[cfg(all(not(feature = "hydrate"), any(client, doc)))]
use sycamore::web::DomNode;
#[cfg(engine)]
use sycamore::web::SsrNode;
use sycamore::{prelude::Scope, view::View};

/// Renders or hydrates the given view to the given node,
/// depending on feature flags. This will atuomatically handle
/// proper scoping.
///
/// This has the option to force a render by ignoring the initial elements.
///
/// **Warning:** if hydration is being used, it is expected that
/// the given view was created inside a `with_hydration_context()` closure.
// XXX This is *highly* dependent on internal Sycamore implementation
// details! (TODO PR for `hydrate_to_with_scope` etc.)
#[cfg(any(client, doc))]
#[allow(unused_variables)]
pub(crate) fn render_or_hydrate(
    cx: Scope,
    view: View<crate::template::BrowserNodeType>,
    parent: web_sys::Element,
    force_render: bool,
) {
    use sycamore::utils::hydrate::{with_hydration_context, with_no_hydration_context};

    #[cfg(feature = "hydrate")]
    {
        use sycamore::web::HydrateNode;

        // If we're forcing a proper render, then we'll have to remove existing content
        if force_render {
            parent.set_inner_html("");
        }

        // We need `sycamore::hydrate_to_with_scope()`!
        // --- Verbatim copy from Sycamore, changed for known scope ---
        // Get children from parent into a View to set as the initial node value.
        let mut children = Vec::new();
        let child_nodes = parent.child_nodes();
        for i in 0..child_nodes.length() {
            children.push(child_nodes.get(i).unwrap());
        }
        let children = children
            .into_iter()
            .map(|x| View::new_node(HydrateNode::from_web_sys(x)))
            .collect::<Vec<_>>();

        insert(
            cx,
            &HydrateNode::from_web_sys(parent.into()),
            if force_render {
                with_no_hydration_context(|| view)
            } else {
                with_hydration_context(|| view)
            },
            if force_render {
                None
            } else {
                Some(View::new_fragment(children))
            },
            None,
            false,
        );
    }
    #[cfg(not(feature = "hydrate"))]
    {
        // We have to delete the existing content before we can render the new stuff
        parent.set_inner_html("");
        insert(
            cx,
            &DomNode::from_web_sys(parent.into()),
            view,
            None,
            None,
            false,
        );
    }
}

/// Renders the given view to a string in a fallible manner, managing hydration
/// automatically.
// XXX This is *highly* dependent on internal Sycamore implementation
// details!
#[cfg(engine)]
pub(crate) fn ssr_fallible<E>(
    view_fn: impl FnOnce(Scope) -> Result<View<SsrNode>, E>,
) -> Result<String, E> {
    use sycamore::web::WriteToString;
    use sycamore::{prelude::create_scope_immediate, utils::hydrate::with_hydration_context}; // XXX This may become private one day!

    let mut ret = Ok(String::new());
    create_scope_immediate(|cx| {
        // Usefully, this wrapper can return anything!
        let view_res = with_hydration_context(|| view_fn(cx));
        match view_res {
            Ok(view) => {
                let mut view_str = String::new();
                for node in view.flatten() {
                    node.write_to_string(&mut view_str);
                }
                ret = Ok(view_str);
            }
            Err(err) => {
                ret = Err(err);
            }
        }
    });

    ret
}