Skip to main content

topcoat_runtime/
reactive_scope.rs

1use serde::Serialize;
2use topcoat_core::context::Cx;
3use topcoat_view::{NodeViewParts, PartsWriter, View, ViewPart};
4use uuid::Uuid;
5
6use crate::{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<ViewPart>,
31    placeholder: View,
32}
33
34impl ReactiveScope {
35    #[inline]
36    #[must_use]
37    pub fn new(shard_id: ShardId, exprs: Vec<ViewPart>, placeholder: View) -> 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_str_unescaped("<!-- ::topcoat::scope::start(");
58        parts.push_str_unescaped(serde_json::to_string(&self.id).unwrap());
59        parts.push_str_unescaped(", ");
60        parts.push_str_unescaped(
61            serde_json::to_string(&format!("{SHARD_ROUTE_PREFIX}/{shard_id}")).unwrap(),
62        );
63        parts.push_str_unescaped(", [");
64        let last = self.exprs.len().saturating_sub(1);
65        for (index, expr) in self.exprs.into_iter().enumerate() {
66            parts.push_str_unescaped("\"");
67            parts.push_part(expr);
68            parts.push_str_unescaped("\"");
69            if index != last {
70                parts.push_str_unescaped(", ");
71            }
72        }
73        parts.push_str_unescaped("]) -->");
74        self.placeholder.into_view_parts(cx, parts);
75        parts.push_str_unescaped("<!-- ::topcoat::scope::end(");
76        parts.push_str_unescaped(serde_json::to_string(&self.id).unwrap());
77        parts.push_str_unescaped(") -->");
78    }
79}