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 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191
// the render crate needs braces to work
#![allow(unused_braces)]
#![doc = include_str!("../README.md")]
pub mod utils;
/// Prelude to ensure all users have the required deps
pub mod prelude {
pub use crate::utils;
pub use lazy_static::lazy_static;
pub use render::{
// A macro to create components
component,
// A macro to render components in JSX fashion
html,
// A macro to compose components in JSX fashion
rsx,
// A trait for custom components
Render,
};
}
/// This macro takes $page, $input, $output and $imports and generates the Trait and the
/// default implementations using the given variables. The macro should also take care of importing
/// the necessary dependencies.
///
/// This macro generates the Trait and the default implementations using the given variables.
/// The macro should also take care of importing the necessary dependencies.
///
/// # Example
/// (disable doctest for this example)
/// ```ignore
/// use wurbo::generate_reactivity;
/// use render::{
/// // A macro to create components
/// component,
/// // A macro to render components in JSX fashion
/// html,
/// // A macro to compose components in JSX fashion
/// rsx,
/// // A trait for custom components
/// Render,
/// };
///
/// use crate::bindings::exports::demo::vowels::reactivity::Guest as WurboGuest;
/// use crate::bindings::demo::vowels::imports; // so the macro has access to the imports::types
///
/// /// The WIT Component struct for implementing the Guest trait
/// struct Component;
///
//? #[component]
/// pub fn Page<'a, Children: Render>(title: &'a str, children: Children) {
/// rsx! {
/// <div class={"p-4"}>
/// <div>
/// {children}
/// </div>
/// </div>
/// }
/// }
///
///
/// #[component]
/// pub fn Input<'a>(name: &'a str, id: &'a str) {
/// // Type of event listener to listen for
/// let ty = "keyup";
///
/// // Add this CSS selector to the list of selectors that will add event listeners
/// super::wurbo_tracker::track(format!("#{id}"), ty);
///
/// rsx! {
/// <div>
/// <div>
/// {"The data you enter can't be seen by anyone else, since it's in a WebAssembly sandbox. =)"}
/// </div>
/// <input id
/// value={name}
/// />
/// <div>
/// {"But it can still calculate how many vowels are in your words for you!"}
/// </div>
/// </div>
/// }
/// }
///
/// #[component]
/// pub fn Output<'a>(name: &'a str, id: &'a str) {
/// let count = count_vowels(name);
///
/// rsx! {
/// <div id>
/// <b>{name}</b>
/// {
/// match count {
/// 0 => { " has no vowels.".to_string() } ,
/// 1 => {format!(" has {count} vowel.")}
/// _ => {format!(" has {count} vowels.")}
/// }
/// }
/// <br/>
/// </div>
/// }
/// }
///
/// /// The macro combines your components together and injects the reactivity:
/// generate_reactivity! { WurboGuest, Component, Page, Input, Output, imports }
/// ```
#[macro_export]
macro_rules! generate_reactivity {
($guest: ident, $component: ident, $page:ident, $input:ident, $output:ident, $imports:ident) => {
use $crate::prelude::*;
use std::collections::HashMap;
use std::sync::Mutex;
use std::sync::OnceLock;
use std::sync::RwLock;
///Maps the #elementId to the event type
type ListenerMap = HashMap<String, &'static str>;
// We cannot have &self in the Component struct,
// so we use static variables to store the state between functions
// See https://crates.io/crates/lazy_static
lazy_static! {
// create Vec<bindings::component::cargo_comp::imports::ListenDetails>
static ref LISTENERS_MAP: Mutex<ListenerMap> = Mutex::new(HashMap::new());
// is_initialized
static ref IS_INITIALIZED: RwLock<bool> = RwLock::new(false);
}
/// The HTML element id of the output section so we can surgically render re-render it
static OUTPUT_ID: OnceLock<String> = OnceLock::new();
// unique namespace to clairfy and avoid collisions with other Guest code
mod wurbo_tracker {
/// Insert the element id and event type into the LISTENERS_MAP
///
/// # Example
///
/// ```rust
/// let my_CSS_selector = "#some_selector";
/// Interactive::activate(format!("#{my_CSS_selector}"), "keyup");
/// ```
pub fn track(elem_id: String, ty: &'static str) {
let mut listeners = super::LISTENERS_MAP.lock().unwrap();
listeners.insert(elem_id, ty);
}
}
impl $guest for $component {
/// Say hello!
fn render(name: String) -> String {
let name = &name;
if OUTPUT_ID.get().is_none() {
#[allow(clippy::redundant_closure)]
let id: &String = OUTPUT_ID.get_or_init(|| utils::rand_id());
// Render and return all HTML
html! {
<$page title={"CAN'T BE EVIL"}>
<$input name id={&utils::rand_id()} />
<$output name id />
</$page>
}
} else {
// If OUTPUT_ID is set, render only the output section
// This is so we keep our INPUT event listeners which were set above
// Render and return only the output section of HTML
html! {
<$output name id={OUTPUT_ID.get().unwrap()} />
}
}
}
/// Activate the component listeners
fn activate() {
let listeners = LISTENERS_MAP.lock().unwrap();
for (selector, ty) in listeners.iter() {
let deets = $imports::ListenDetails {
selector: selector.to_string(),
ty: ty.to_string(),
value: "TODO".to_string(),
};
$imports::addeventlistener(&deets);
}
}
}
};
}