commit 6a0067838a60debaab6c22e41322cf9ed84545a3 Author: Denis Kolodin Date: Sat Dec 16 17:19:27 2017 +0300 Minimal prototype diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..0196246bd --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ + +/target/ +**/*.rs.bk diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 000000000..39554c8df --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,54 @@ +[[package]] +name = "dtoa" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "itoa" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "num-traits" +version = "0.1.41" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "serde" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "serde_json" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "dtoa 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", + "itoa 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", + "num-traits 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.24 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "stdweb" +version = "0.1.3" +source = "git+https://github.com/koute/stdweb#5eb5f0ff176464dbd30d41f31b53804a8bb2847b" +dependencies = [ + "serde 1.0.24 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_json 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "yew-prototype" +version = "0.1.0" +dependencies = [ + "stdweb 0.1.3 (git+https://github.com/koute/stdweb)", +] + +[metadata] +"checksum dtoa 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "09c3753c3db574d215cba4ea76018483895d7bff25a31b49ba45db21c48e50ab" +"checksum itoa 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "8324a32baf01e2ae060e9de58ed0bc2320c9a2833491ee36cd3b4c414de4db8c" +"checksum num-traits 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)" = "cacfcab5eb48250ee7d0c7896b51a2c5eec99c1feea5f32025635f5ae4b00070" +"checksum serde 1.0.24 (registry+https://github.com/rust-lang/crates.io-index)" = "1c57ab4ec5fa85d08aaf8ed9245899d9bbdd66768945b21113b84d5f595cb6a1" +"checksum serde_json 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)" = "7cf5b0b5b4bd22eeecb7e01ac2e1225c7ef5e4272b79ee28a8392a8c8489c839" +"checksum stdweb 0.1.3 (git+https://github.com/koute/stdweb)" = "" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 000000000..daf7da56b --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "yew-prototype" +version = "0.1.0" +authors = ["Denis Kolodin "] + +[dependencies] +stdweb = { git = "https://github.com/koute/stdweb" } diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 000000000..369ccfbf5 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,171 @@ +#[macro_use] +extern crate stdweb; + +struct Model { + value: u8, +} + +enum Msg { + Increment, + Decrement, +} + +fn update(model: &mut Model, msg: Msg) { + match msg { + Msg::Increment => { + model.value = model.value + 1; + } + Msg::Decrement => { + model.value = model.value - 1; + } + } +} + +fn view(model: &Model) -> html::Html { + use html::*; + div(vec![ + onclick(|_| Msg::Increment) + ]) +} + +/* +html! { +
{ model.x }> +} +*/ + +fn main() { + let model = Model { + value: 0, + }; + html::program(model, update, view); +} + +mod html { + use std::rc::Rc; + use std::cell::RefCell; + + /* + pub trait Message {} + + impl Fn(T) -> Self for Message { + } + */ + + use stdweb; + + use stdweb::web::{ + INode, + IEventTarget, + Element, + document, + }; + use stdweb::web::event::{ + ClickEvent, + }; + + fn replace_body(element: Element) { + let body = document().query_selector("body").unwrap(); + while body.has_child_nodes() { + body.remove_child(&body.last_child().unwrap()).unwrap(); + } + body.append_child(&element); + } + + pub fn program(mut model: M, update: U, view: V) + where + M: 'static, + MSG: 'static, + U: Fn(&mut M, MSG) + 'static, + V: Fn(&M) -> Html + 'static, + { + stdweb::initialize(); + // No messages at start + let messages = Rc::new(RefCell::new(Vec::new())); + let mut callback = move || { + println!("Process messages"); + let mut borrowed = messages.borrow_mut(); + for msg in borrowed.drain(..) { + update(&mut model, msg); + } + let html = view(&model); + let element = html.render(messages.clone()); + replace_body(element); + }; + // Initial call for first rendering + callback(); + js! { + let callback = @{callback}; + window.yew_loop = function() { + callback(); + } + }; + stdweb::event_loop(); + } + + pub type Html = Tag; + + pub trait Listener { + fn attach(&mut self, element: &Element, messages: Messages); + } + + type Messages = Rc>>; + type Listeners = Vec>>; + + pub struct Tag { + tag: &'static str, + listeners: Listeners, + } + + impl Tag { + fn new(tag: &'static str, listeners: Listeners) -> Self { + Tag { tag, listeners } + } + + fn render(self, messages: Messages) -> Element { + let element = document().create_element(self.tag); + for mut listener in self.listeners { + listener.attach(&element, messages.clone()); + } + element + } + } + + pub fn div(listeners: Vec>>) -> Tag { + Tag::new("div", listeners) + } + + pub fn button(listeners: Vec>>) -> Tag { + Tag::new("button", listeners) + } + + pub fn onclick(handler: F) -> Box> + where + MSG: 'static, + F: Fn(ClickEvent) -> MSG + 'static + { + Box::new(OnClick(Some(handler))) + } + + struct OnClick(Option); + + impl Listener for OnClick + where + MSG: 'static, + T: Fn(ClickEvent) -> MSG + 'static, + { + fn attach(&mut self, element: &Element, messages: Messages) { + let handler = self.0.take().unwrap(); + let sender = move |event| { + println!("Clicked!"); + let msg = handler(event); + messages.borrow_mut().push(msg); + js! { + yew_loop(); + } + }; + element.add_event_listener(sender); + } + } +} +