at main 149 lines 4.8 kB view raw
1//! Theme preview component with sample markdown rendering. 2 3use dioxus::prelude::*; 4 5use crate::components::css::{ThemePreviewInput, generate_theme_preview}; 6use crate::components::inline_theme_editor::InlineThemeValues; 7 8const NOTEBOOK_DEFAULTS_CSS: Asset = asset!("/assets/styling/notebook-defaults.css"); 9 10/// Sample markdown to render for theme preview. 11const SAMPLE_MARKDOWN: &str = r#"# Heading 1 12 13Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. 14 15## Heading 2 16 17Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris. Here's a [link to somewhere](#) and some **bold text** with *italics*. 18 19### Heading 3 20 21> A blockquote for emphasis. This tests the border and muted text colours. 22 23Here's a list of items: 24 25- First item with some text 26- Second item with `inline code` 27- Third item 28 29And some numbered steps: 30 311. Do the first thing 322. Then the second 333. Finally the third 34 35```rust 36fn main() { 37 // A code block to test syntax highlighting 38 let message = "Hello, world!"; 39 println!("{}", message); 40} 41``` 42 43--- 44 45That's the end of the preview. 46"#; 47 48/// Render markdown to HTML using the full pipeline with syntax highlighting. 49fn render_markdown(markdown: &str) -> String { 50 use markdown_weaver::Parser; 51 use weaver_renderer::atproto::ClientWriter; 52 use weaver_renderer::default_md_options; 53 54 let parser = Parser::new_ext(markdown, default_md_options()).into_offset_iter(); 55 let mut html = String::new(); 56 ClientWriter::<_, _, ()>::new(parser, &mut html, markdown) 57 .run() 58 .ok(); 59 html 60} 61 62/// Props for ThemePreview with signal for reactivity. 63#[derive(Props, Clone, PartialEq)] 64pub struct ThemePreviewProps { 65 /// Theme values to preview (signal for reactivity). 66 pub values: Signal<InlineThemeValues>, 67 /// Whether to show dark variant (false = light). 68 #[props(default = false)] 69 pub dark: bool, 70} 71 72/// Theme preview component that renders sample markdown with theme applied. 73#[component] 74pub fn ThemePreview(props: ThemePreviewProps) -> Element { 75 let dark = props.dark; 76 let values = props.values; 77 78 let mut preview_resource = use_resource(move || { 79 let values = values(); 80 async move { 81 // Skip if background is empty (invalid). 82 if values.background.is_empty() { 83 return Err(ServerFnError::new("No theme values set")); 84 } 85 86 let input = ThemePreviewInput { 87 background: values.background.clone(), 88 text: values.text.clone(), 89 primary: values.primary.clone(), 90 link: values.link.clone(), 91 light_background: values.light_background.clone(), 92 light_text: values.light_text.clone(), 93 light_primary: values.light_primary.clone(), 94 light_link: values.light_link.clone(), 95 dark_background: values.dark_background.clone(), 96 dark_text: values.dark_text.clone(), 97 dark_primary: values.dark_primary.clone(), 98 dark_link: values.dark_link.clone(), 99 light_code_theme: values.light_code_theme.clone(), 100 dark_code_theme: values.dark_code_theme.clone(), 101 }; 102 generate_theme_preview(input).await 103 } 104 }); 105 106 // Restart resource when values change. 107 use_effect(move || { 108 let _ = values(); 109 preview_resource.restart(); 110 }); 111 112 let rendered_html = render_markdown(SAMPLE_MARKDOWN); 113 114 match preview_resource() { 115 Some(Ok(output)) => { 116 let palette = if dark { &output.dark_palette } else { &output.light_palette }; 117 let css_vars = palette.to_css_vars(); 118 119 // Scoped CSS: variables -> notebook defaults -> syntax highlighting 120 let scoped_css = format!(".theme-preview {{ {} }}", css_vars); 121 122 rsx! { 123 // 1. CSS variables scoped to .theme-preview 124 style { dangerous_inner_html: "{scoped_css}" } 125 126 // 2. Notebook content styles (uses the variables) 127 document::Stylesheet { href: NOTEBOOK_DEFAULTS_CSS } 128 129 // 3. Syntax highlighting CSS 130 style { dangerous_inner_html: "{output.syntax_css}" } 131 132 div { 133 class: "theme-preview notebook-content", 134 dangerous_inner_html: "{rendered_html}" 135 } 136 } 137 } 138 Some(Err(e)) => rsx! { 139 div { class: "theme-preview theme-preview--error", 140 "Failed to generate theme preview: {e}" 141 } 142 }, 143 None => rsx! { 144 div { class: "theme-preview theme-preview--loading", 145 "Loading preview..." 146 } 147 }, 148 } 149}