Skip to main content

topcoat_runtime/
reactive_scope.rs

1use serde::Serialize;
2use topcoat_core::context::Cx;
3use topcoat_view::{NodeViewParts, PartsWriter, ViewHandle};
4use uuid::Uuid;
5
6use crate::{Js, SHARD_ROUTE_PREFIX, ShardId};
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
9#[serde(transparent)]
10pub struct ReactiveScopeId(Uuid);
11
12impl ReactiveScopeId {
13    #[inline]
14    #[must_use]
15    pub fn new() -> Self {
16        Self(Uuid::new_v4())
17    }
18}
19
20impl Default for ReactiveScopeId {
21    #[inline]
22    fn default() -> Self {
23        Self::new()
24    }
25}
26
27pub struct ReactiveScope {
28    id: ReactiveScopeId,
29    shard_id: ShardId,
30    exprs: Vec<Js>,
31    placeholder: ViewHandle,
32}
33
34impl ReactiveScope {
35    #[inline]
36    #[must_use]
37    pub fn new(shard_id: ShardId, exprs: Vec<Js>, placeholder: ViewHandle) -> Self {
38        Self {
39            id: ReactiveScopeId::new(),
40            shard_id,
41            exprs,
42            placeholder,
43        }
44    }
45}
46
47impl NodeViewParts for ReactiveScope {
48    fn into_view_parts(self, cx: &Cx, parts: &mut PartsWriter<'_>) {
49        let shard_id = self.shard_id.as_str();
50
51        // <!-- ::topcoat::scope::start("<id>", "<path>", ["<js>", ...]) -->
52        //
53        // Each parameter's JavaScript source is wrapped in a quoted string.
54        // The source parts are sealed with the comment context, so any `"`
55        // inside the source renders as `&quot;` and the quotes stay
56        // unambiguous delimiters on the client.
57        parts.push_comment(|comment| {
58            comment
59                .push_promoted_str_unescaped(&"::topcoat::scope::start(")
60                .push_string_unescaped(serde_json::to_string(&self.id).unwrap())
61                .push_promoted_str_unescaped(&", ")
62                .push_string_unescaped(
63                    serde_json::to_string(&format!("{SHARD_ROUTE_PREFIX}/{shard_id}")).unwrap(),
64                )
65                .push_promoted_str_unescaped(&", [");
66            let last = self.exprs.len().saturating_sub(1);
67            for (index, expr) in self.exprs.iter().enumerate() {
68                comment.push_promoted_str_unescaped(&"\"");
69                expr.write(comment);
70                comment.push_promoted_str_unescaped(&"\"");
71                if index != last {
72                    comment.push_promoted_str_unescaped(&", ");
73                }
74            }
75            comment.push_promoted_str_unescaped(&"])");
76        });
77        self.placeholder.into_view_parts(cx, parts);
78        parts.push_comment(|comment| {
79            comment
80                .push_promoted_str_unescaped(&"::topcoat::scope::end(")
81                .push_string_unescaped(serde_json::to_string(&self.id).unwrap())
82                .push_promoted_str_unescaped(&")");
83        });
84    }
85}