|
| 1 | +use leptos::{ |
| 2 | + attr::Attribute, |
| 3 | + context::{Provider, ProviderProps}, |
| 4 | + prelude::*, |
| 5 | + tachys::{ |
| 6 | + hydration::Cursor, |
| 7 | + renderer::types, |
| 8 | + view::{any_view::AnyViewState, Position, PositionState}, |
| 9 | + }, |
| 10 | +}; |
| 11 | +use std::collections::HashMap; |
| 12 | + |
| 13 | +#[component] |
| 14 | +pub fn SSRMountStyleProvider<Chil>(children: TypedChildren<Chil>) -> impl IntoView |
| 15 | +where |
| 16 | + Chil: IntoView + 'static, |
| 17 | +{ |
| 18 | + let context = SSRMountStyleContext::default(); |
| 19 | + |
| 20 | + let children = Provider( |
| 21 | + ProviderProps::builder() |
| 22 | + .value(context.clone()) |
| 23 | + .children(children) |
| 24 | + .build(), |
| 25 | + ) |
| 26 | + .into_any(); |
| 27 | + SSRMountStyle { context, children } |
| 28 | +} |
| 29 | + |
| 30 | +#[derive(Debug, Clone)] |
| 31 | +pub struct SSRMountStyleContext { |
| 32 | + styles: ArcStoredValue<HashMap<String, String>>, |
| 33 | +} |
| 34 | + |
| 35 | +impl SSRMountStyleContext { |
| 36 | + pub fn use_context() -> Option<Self> { |
| 37 | + use_context() |
| 38 | + } |
| 39 | + |
| 40 | + pub fn push_style(&self, k: String, v: String) { |
| 41 | + self.styles.write_value().insert(k, v); |
| 42 | + } |
| 43 | + |
| 44 | + fn default() -> Self { |
| 45 | + Self { |
| 46 | + styles: Default::default(), |
| 47 | + } |
| 48 | + } |
| 49 | + |
| 50 | + fn html_len(&self) -> usize { |
| 51 | + const TEMPLATE_LEN: usize = r#"<style id=""></style>"#.len(); |
| 52 | + let mut html_len = 0; |
| 53 | + let styles = self.styles.write_value(); |
| 54 | + |
| 55 | + styles.iter().for_each(|(k, v)| { |
| 56 | + html_len += k.len() + v.len() + TEMPLATE_LEN; |
| 57 | + }); |
| 58 | + |
| 59 | + html_len |
| 60 | + } |
| 61 | + |
| 62 | + fn to_html(self) -> String { |
| 63 | + let mut styles = self.styles.write_value(); |
| 64 | + styles |
| 65 | + .drain() |
| 66 | + .into_iter() |
| 67 | + .map(|(k, v)| format!(r#"<style id="{k}">{v}</style>"#)) |
| 68 | + .collect::<String>() |
| 69 | + } |
| 70 | +} |
| 71 | + |
| 72 | +pub struct SSRMountStyle { |
| 73 | + context: SSRMountStyleContext, |
| 74 | + children: AnyView, |
| 75 | +} |
| 76 | + |
| 77 | +pub struct SSRMountStyleState { |
| 78 | + state: AnyViewState, |
| 79 | +} |
| 80 | + |
| 81 | +impl Render for SSRMountStyle { |
| 82 | + type State = SSRMountStyleState; |
| 83 | + |
| 84 | + fn build(self) -> Self::State { |
| 85 | + let state = self.children.build(); |
| 86 | + SSRMountStyleState { state } |
| 87 | + } |
| 88 | + |
| 89 | + fn rebuild(self, state: &mut Self::State) { |
| 90 | + self.children.rebuild(&mut state.state); |
| 91 | + } |
| 92 | +} |
| 93 | + |
| 94 | +impl AddAnyAttr for SSRMountStyle { |
| 95 | + type Output<SomeNewAttr: Attribute> = Self; |
| 96 | + |
| 97 | + fn add_any_attr<NewAttr: Attribute>(self, _attr: NewAttr) -> Self::Output<NewAttr> |
| 98 | + where |
| 99 | + Self::Output<NewAttr>: RenderHtml, |
| 100 | + { |
| 101 | + self |
| 102 | + } |
| 103 | +} |
| 104 | + |
| 105 | +impl RenderHtml for SSRMountStyle { |
| 106 | + type AsyncOutput = Self; |
| 107 | + |
| 108 | + const MIN_LENGTH: usize = 0; |
| 109 | + |
| 110 | + fn html_len(&self) -> usize { |
| 111 | + self.children.html_len() + self.context.html_len() |
| 112 | + } |
| 113 | + |
| 114 | + fn dry_resolve(&mut self) { |
| 115 | + self.children.dry_resolve(); |
| 116 | + } |
| 117 | + |
| 118 | + async fn resolve(self) -> Self::AsyncOutput { |
| 119 | + self |
| 120 | + } |
| 121 | + |
| 122 | + fn to_html_with_buf( |
| 123 | + self, |
| 124 | + buf: &mut String, |
| 125 | + position: &mut Position, |
| 126 | + escape: bool, |
| 127 | + mark_branches: bool, |
| 128 | + ) { |
| 129 | + self.children |
| 130 | + .to_html_with_buf(buf, position, escape, mark_branches); |
| 131 | + |
| 132 | + let head_loc = buf |
| 133 | + .find("<head>") |
| 134 | + .expect("you are using SSRMountStyleProvider without a <head> tag"); |
| 135 | + let marker_loc = buf |
| 136 | + .find(r#"<meta name="thaw-ui-style""#) |
| 137 | + .unwrap_or(head_loc + 6); |
| 138 | + |
| 139 | + buf.insert_str(marker_loc, &self.context.to_html()); |
| 140 | + } |
| 141 | + |
| 142 | + fn hydrate<const FROM_SERVER: bool>( |
| 143 | + self, |
| 144 | + cursor: &Cursor, |
| 145 | + position: &PositionState, |
| 146 | + ) -> Self::State { |
| 147 | + let state = self.children.hydrate::<FROM_SERVER>(cursor, position); |
| 148 | + SSRMountStyleState { state } |
| 149 | + } |
| 150 | +} |
| 151 | + |
| 152 | +impl Mountable for SSRMountStyleState { |
| 153 | + fn unmount(&mut self) { |
| 154 | + self.state.unmount(); |
| 155 | + } |
| 156 | + |
| 157 | + fn mount(&mut self, parent: &types::Element, marker: Option<&types::Node>) { |
| 158 | + self.state.mount(parent, marker); |
| 159 | + } |
| 160 | + |
| 161 | + fn insert_before_this(&self, child: &mut dyn Mountable) -> bool { |
| 162 | + self.state.insert_before_this(child) |
| 163 | + } |
| 164 | +} |
0 commit comments