Respo in Rust

Respo Crate

Reimagine Respo.cljs in Rust.

Status: experiment

Respo was initially designed to work in a dynamic language with persistent data and hot code swapping, which is dramatically different from Rust. So this is more like an experiment.

Docs https://docs.rs/respo

Usage

A preview example:

```rust

[derive(Debug, Clone, Deserialize, Serialize)]

pub struct Store { pub counted: i32, pub tasks: Vec, pub states: StatesTree, }

[derive(Debug, Clone, Serialize, Deserialize)]

pub struct Task { pub id: String, pub done: bool, pub content: String, pub time: f32, }

[derive(Clone, Debug, PartialEq, Eq)]

pub enum ActionOp { Increment, Decrement, StatesChange(Vec, Option), AddTask(String, String), RemoveTask(String), UpdateTask(String, String), ToggleTask(String), }

pub fn applyaction(store: &mut Store, op: ActionOp) -> Result<(), String> { match op { ActionOp::Increment => { store.counted += 1; } ActionOp::Decrement => { store.counted -= 1; } ActionOp::StatesChange(path, newstate) => { store.states = store.states.setin(&path, newstate); } ActionOp::AddTask(id, content) => store.tasks.push(Task { id, content, time: 0.0, done: false, }), ActionOp::RemoveTask(id) => { store.tasks.retain(|task| task.id != id); } ActionOp::UpdateTask(id, content) => { let mut found = false; for task in &mut store.tasks { if task.id == id { task.content = content.to_owned(); found = true; } } if !found { return Err(format!("task {} not found", id)); } } ActionOp::ToggleTask(id) => { let mut found = false; for task in &mut store.tasks { if task.id == id { util::log!("change task {:?}", task); task.done = !task.done; found = true; } } if !found { return Err(format!("task {} not found", id)); } } } Ok(()) } ```

```rust

[wasmbindgen(jsname = loadDemoApp)]

pub fn loaddemoapp() -> JsValue { panic::sethook(Box::new(consoleerrorpanichook::hook));

let mounttarget = queryselect_node(".app").expect("found mount target");

// need to push store inside function to keep all in one thread let global_store = Rc::new(RefCell::new(Store { counted: 0, states: StatesTree::default(), tasks: vec![], }));

let storetoaction = globalstore.clone(); let dispatchaction = move |op: ActionOp| -> Result<(), String> { // util::log!("action {:?} store, {:?}", op, storetoaction.borrow()); let mut store = storetoaction.borrowmut(); applyaction(&mut store, op)?;

// util::log!("store after action {:?}", store);
Ok(())

};

rendernode( mounttarget, Box::new(move || -> Result, String> { let store = global_store.borrow(); let states = store.states.clone();

  // util::log!("global store: {:?}", store);

  Ok(
    div()
      .class(ui_global())
      .add_style(RespoStyle::default().padding(12.0).to_owned())
      .add_children([
        comp_counter(&states.pick("counter"), store.counted),
        comp_panel(&states.pick("panel"))?,
        comp_todolist(&states.pick("todolist"), &store.tasks)?,
      ])
      .to_owned(),
  )
}),
DispatchFn::new(dispatch_action),

) .expect("rendering node");

JsValue::NULL } ```

CSS-in-Rust:

rust static_style!( style_remove_button, &[ ( "$0".to_owned(), RespoStyle::default() .width(CssSize::Px(16.0)) .height(CssSize::Px(16.0)) .margin(4.) .cursor("pointer".to_owned()) .margin4(0.0, 0.0, 0.0, 16.0) .color(CssColor::Hsl(0, 90, 90)), ), ("$0:hover".to_owned(), RespoStyle::default().color(CssColor::Hsl(0, 90, 80))), ] );

License

Apache License 2.0 .