Whiteboard Components
Whiteboard components implement the WhiteboardComponent subtrait, which pins the associated types for trait object usage.
WhiteboardComponent Subtrait
Section titled “WhiteboardComponent Subtrait”trait WhiteboardComponent: Component<Metadata = WhiteboardChildMeta, Output = Vec<WhiteboardElement>> { fn id(&self) -> &'static str; fn name(&self) -> &'static str; fn description(&self) -> &'static str; fn icon(&self) -> &'static str;}Child metadata is WhiteboardChildMeta — the placement ID + bounding Rect derived from the placement’s WhiteboardPosition. Each component receives its metadata and resolved property values, then compiles to Vec<WhiteboardElement>.
Built-in Components
Section titled “Built-in Components”Element Components (the Typed Element Components RFC)
Section titled “Element Components (the Typed Element Components RFC)”Each whiteboard element type has its own Component with individual PropertyBinding entries — no JSON blob. Templates can bind any property independently via PropertyBinding::Reference.
| Component | Key | Properties |
|---|---|---|
ShapeComponent | "shape" | shape_type (Select), fill (Select), color (Color), stroke_color (Color), stroke_width (Number), start_arrow (Select), end_arrow (Select), rotation (Number) |
PathComponent | "path" | points (Json), color (Color), thickness (Number), is_eraser (Boolean) |
TextComponent | "text" | prose_bytes (Json), font_family (Select), text_align (Select) |
FileComponent | "file" | file_type (Select), url (Text), thumbnail_url (Text), file_name (Text), rotation (Number) |
UrlComponent | "url" | url_type (Select), url (Text), title (Text), description (Text), thumbnail_url (Text) |
GeogebraComponent | "geogebra" | geogebra_type (Select), material_id (Text), commands (Text) |
MaskComponent | "mask" | image_url (Text), mask_path (Text) |
Layout Components
Section titled “Layout Components”Higher-level components that compose multiple elements:
| Component | Key | Description |
|---|---|---|
TextBoxComponent | "text_box" | Rich text with optional background/border |
CardComponent | "card" | Bordered container with header and body |
ListComponent | "list" | Ordered or unordered list items |
Component Registration
Section titled “Component Registration”Components are registered via inventory::submit! and resolved by their id() string:
pub struct WhiteboardComponentEntry(pub &'static dyn WhiteboardComponent);inventory::collect!(WhiteboardComponentEntry);
pub fn resolve_component(id: &str) -> Option<&'static dyn WhiteboardComponent> { inventory::iter::<WhiteboardComponentEntry> .into_iter() .find(|e| e.0.id() == id) .map(|e| e.0)}Custom components can be loaded by component_id — the placement stores a UUID pointing to a component instance in storage.
Typed Properties (the Typed Component Properties RFC)
Section titled “Typed Properties (the Typed Component Properties RFC)”All whiteboard components use #[derive(ComponentProperties)] for typed property structs. Enum fields use #[derive(PropertyField)] to generate PropertyType::Select options. The PropertyField trait bridges Rust types to PropertyValue/PropertyType:
| Rust type | PropertyType | PropertyValue storage |
|---|---|---|
String | Text | String(s) |
f64 | Number | Number(f64) |
bool | Boolean | Bool(b) |
Color | Color | String("#AARRGGBB") |
Option<T> | same as T | Null when absent |
#[derive(PropertyField)] enum | Select { options } | String("lowerCamelCase") |
| Complex data (Vec<Point>) | Text | String(serde_json::to_string(...)) — JSON-encoded |
Binding construction in reducers
Section titled “Binding construction in reducers”Use Props::to_bindings() for full construction and PropsUpdate::apply() for partial updates:
// Create a new placement:let bindings = ShapeProps { shape_type: ShapeType::Rect, fill: ShapeFill::Solid, .. }.to_bindings();
// Update specific fields:ShapePropsUpdate { color: Some(Color(0xFFFF0000)), ..Default::default() }.apply(&mut p.bindings);Compile Flow
Section titled “Compile Flow”Each component implements TypedComponent and receives a typed properties struct:
impl TypedComponent for ShapeComponent { type Properties = ShapeProps; type Metadata = WhiteboardChildMeta; type Output = Vec<WhiteboardElement>;
fn id() -> &'static str { "shape" } fn name() -> &'static str { "Shape" } fn description() -> &'static str { "Geometric shape with fill and stroke" } fn icon() -> &'static str { "category" }
fn compile(&self, props: &ShapeProps, meta: &WhiteboardChildMeta) -> Result<Vec<WhiteboardElement>, CompileError> { let bounds = &meta.bounds; Ok(vec![WhiteboardElement::Shape(ShapeElement { id: next_id(), position: Offset { dx: bounds.x, dy: bounds.y }, size: Size { width: bounds.width, height: bounds.height }, shape_type: props.shape_type, fill: props.fill, color: props.color, stroke_color: props.stroke_color, stroke_width: props.stroke_width, start_arrow: props.start_arrow, end_arrow: props.end_arrow, rotation: props.rotation, ..Default::default() })]) }}The blanket Component impl handles HashMap extraction automatically — TypedComponent::compile() always receives typed props.
Related
Section titled “Related”- Overview — struct, types, compilation flow
- Intents — all dispatchable commands
Component— the core trait all components implementProperties— property schema and value system- Typed Element Components RFC — design rationale for per-type components
- Typed Component Properties RFC — design rationale for typed component properties