Showing
2 changed files
with
0 additions
and
261 deletions
mogwai-list/Cargo.toml
deleted
100644 → 0
| 1 | -[package] | |
| 2 | -name = "mogwai-list" | |
| 3 | -version = "0.0.0" | |
| 4 | -authors = ["Georg Hopp <georg@steffers.org>"] | |
| 5 | -workspace = ".." | |
| 6 | -edition = "2018" | |
| 7 | - | |
| 8 | -[lib] | |
| 9 | -crate-type = ["cdylib", "rlib"] | |
| 10 | - | |
| 11 | -[features] | |
| 12 | -default = ["console_error_panic_hook"] | |
| 13 | - | |
| 14 | -[dependencies] | |
| 15 | -artshop-common = { path = "../common" } | |
| 16 | -katex = { version = "0.4", default-features = false, features = ["wasm-js"] } | |
| 17 | -pulldown-cmark = "0.9" | |
| 18 | -console_log = "^0.1" | |
| 19 | -log = "^0.4" | |
| 20 | -serde = { version = "^1.0", features = ["derive"] } | |
| 21 | -serde_json = "^1.0" | |
| 22 | -wasm-bindgen = "^0.2" | |
| 23 | -wasm-bindgen-futures = "^0.4" | |
| 24 | - | |
| 25 | -# The `console_error_panic_hook` crate provides better debugging of panics by | |
| 26 | -# logging them with `console.error`. This is great for development, but requires | |
| 27 | -# all the `std::fmt` and `std::panicking` infrastructure, so isn't great for | |
| 28 | -# code size when deploying. | |
| 29 | -console_error_panic_hook = { version = "0.1.6", optional = true } | |
| 30 | -wee_alloc = { version = "0.4.2", optional = true } | |
| 31 | -js-sys = "^0.3" | |
| 32 | - | |
| 33 | -[dependencies.mogwai] | |
| 34 | -version = "^0.5" | |
| 35 | - | |
| 36 | -[dependencies.web-sys] | |
| 37 | -version = "^0.3" | |
| 38 | -features = [ | |
| 39 | - "Document", | |
| 40 | - "DomParser", | |
| 41 | - "Headers", | |
| 42 | - "HtmlElement", | |
| 43 | - "HtmlInputElement", | |
| 44 | - "MouseEvent", | |
| 45 | - "Node", | |
| 46 | - "Request", | |
| 47 | - "RequestInit", | |
| 48 | - "RequestMode", | |
| 49 | - "Response", | |
| 50 | - "SupportedType", | |
| 51 | - "Window", | |
| 52 | -] | |
| 53 | - | |
| 54 | -[dev-dependencies] | |
| 55 | -wasm-bindgen-test = "0.2" |
mogwai-list/src/lib.rs
deleted
100644 → 0
| 1 | -#![allow(unused_braces)] | |
| 2 | -use log::Level; | |
| 3 | -use mogwai::{futures, prelude::*}; | |
| 4 | -use std::panic; | |
| 5 | -use wasm_bindgen::prelude::*; | |
| 6 | - | |
| 7 | -/// An item widget. | |
| 8 | -/// Keeps track of clicks. | |
| 9 | -#[derive(Clone, Debug)] | |
| 10 | -struct Item { | |
| 11 | - id: usize, | |
| 12 | - clicks: Model<u32>, | |
| 13 | -} | |
| 14 | - | |
| 15 | -/// An item's update messages. | |
| 16 | -#[derive(Clone)] | |
| 17 | -enum ItemMsg { | |
| 18 | - /// The user clicked | |
| 19 | - Click, | |
| 20 | - /// The user requested this item be removed | |
| 21 | - Remove, | |
| 22 | -} | |
| 23 | - | |
| 24 | -/// One item's logic loop. | |
| 25 | -async fn item_logic( | |
| 26 | - id: usize, | |
| 27 | - clicks: Model<u32>, | |
| 28 | - mut from_view: broadcast::Receiver<ItemMsg>, | |
| 29 | - to_list: broadcast::Sender<ListMsg>, | |
| 30 | -) { | |
| 31 | - loop { | |
| 32 | - match from_view.recv().await { | |
| 33 | - Ok(ItemMsg::Click) => { | |
| 34 | - clicks.visit_mut(|c| *c += 1).await; | |
| 35 | - } | |
| 36 | - Ok(ItemMsg::Remove) => { | |
| 37 | - to_list.broadcast(ListMsg::RemoveItem(id)).await.unwrap(); | |
| 38 | - break; | |
| 39 | - } | |
| 40 | - Err(_) => break, | |
| 41 | - } | |
| 42 | - } | |
| 43 | - log::info!("item {} logic loop is done", id); | |
| 44 | -} | |
| 45 | - | |
| 46 | -// ANCHOR: item_view | |
| 47 | -fn item_view( | |
| 48 | - clicks: impl Stream<Item = u32> + Sendable, | |
| 49 | - to_logic: broadcast::Sender<ItemMsg>, | |
| 50 | -) -> ViewBuilder<Dom> { | |
| 51 | - builder! { | |
| 52 | - <li> | |
| 53 | - <button | |
| 54 | - style:cursor="pointer" | |
| 55 | - on:click=to_logic.sink().contra_map(|_| ItemMsg::Click)> | |
| 56 | - "Increment" | |
| 57 | - </button> | |
| 58 | - <button | |
| 59 | - style:cursor="pointer" | |
| 60 | - on:click=to_logic.sink().contra_map(|_| ItemMsg::Remove)> | |
| 61 | - "Remove" | |
| 62 | - </button> | |
| 63 | - " " | |
| 64 | - <span> | |
| 65 | - { | |
| 66 | - ("", clicks.map(|clicks| match clicks { | |
| 67 | - 1 => "1 click".to_string(), | |
| 68 | - n => format!("{} clicks", n), | |
| 69 | - })) | |
| 70 | - } | |
| 71 | - </span> | |
| 72 | - </li> | |
| 73 | - } | |
| 74 | -} | |
| 75 | -// ANCHOR_END: item_view | |
| 76 | - | |
| 77 | -/// Create a new item component. | |
| 78 | -fn item(id: usize, clicks: Model<u32>, to_list: broadcast::Sender<ListMsg>) -> Component<Dom> { | |
| 79 | - let (tx, rx) = broadcast::bounded(1); | |
| 80 | - Component::from(item_view(clicks.stream(), tx)).with_logic(item_logic(id, clicks, rx, to_list)) | |
| 81 | -} | |
| 82 | - | |
| 83 | -#[derive(Clone)] | |
| 84 | -enum ListMsg { | |
| 85 | - /// Create a new item | |
| 86 | - NewItem, | |
| 87 | - /// Remove the item with the given id | |
| 88 | - RemoveItem(usize), | |
| 89 | -} | |
| 90 | - | |
| 91 | -// ANCHOR: list_logic_coms | |
| 92 | -/// Launch the logic loop of our list of items. | |
| 93 | -async fn list_logic( | |
| 94 | - input: broadcast::Receiver<ListMsg>, | |
| 95 | - tx_patch_children: mpmc::Sender<ListPatch<ViewBuilder<Dom>>>, | |
| 96 | -) { | |
| 97 | - // Set up our communication from items to this logic loop by | |
| 98 | - // * creating a list patch model | |
| 99 | - // * creating a channel to go from item to list logic (aka here) | |
| 100 | - // * creating a side-effect stream (for_each) that runs for each item patch | |
| 101 | - // * map patches of Item to patches of builders and send that to our view | |
| 102 | - // through tx_patch_children | |
| 103 | - let mut items: ListPatchModel<Item> = ListPatchModel::new(); | |
| 104 | - let (to_list, from_items) = broadcast::bounded::<ListMsg>(1); | |
| 105 | - let to_list = to_list.clone(); | |
| 106 | - let all_item_patches = items.stream().map(move |patch| { | |
| 107 | - log::info!("mapping patch for item: {:?}", patch); | |
| 108 | - let to_list = to_list.clone(); | |
| 109 | - patch.map(move |Item { id, clicks }: Item| { | |
| 110 | - let to_list = to_list.clone(); | |
| 111 | - let component = item(id, clicks, to_list); | |
| 112 | - let builder: ViewBuilder<Dom> = component.into(); | |
| 113 | - builder | |
| 114 | - }) | |
| 115 | - }).for_each(move |patch| { | |
| 116 | - let tx_patch_children = tx_patch_children.clone(); | |
| 117 | - async move { | |
| 118 | - tx_patch_children.send(patch).await.unwrap(); | |
| 119 | - } | |
| 120 | - }); | |
| 121 | - mogwai::spawn(all_item_patches); | |
| 122 | - // ANCHOR_END: list_logic_coms | |
| 123 | - // ANCHOR: list_logic_loop | |
| 124 | - // Combine the input from our view with the input from our items | |
| 125 | - let mut input = futures::stream::select_all(vec![input, from_items]); | |
| 126 | - let mut next_id = 0; | |
| 127 | - loop { | |
| 128 | - match input.next().await { | |
| 129 | - Some(ListMsg::NewItem) => { | |
| 130 | - log::info!("creating a new item"); | |
| 131 | - let item: Item = Item { | |
| 132 | - id: next_id, | |
| 133 | - clicks: Model::new(0), | |
| 134 | - }; | |
| 135 | - next_id += 1; | |
| 136 | - // patch our items easily and _item_patch_stream's for_each runs automatically, | |
| 137 | - // keeping the list of item views in sync | |
| 138 | - items.list_patch_push(item); | |
| 139 | - } | |
| 140 | - Some(ListMsg::RemoveItem(id)) => { | |
| 141 | - log::info!("removing item: {}", id); | |
| 142 | - let mut may_index = None; | |
| 143 | - 'find_item_by_id: for (item, index) in items.read().await.iter().zip(0..) { | |
| 144 | - if item.id == id { | |
| 145 | - may_index = Some(index); | |
| 146 | - break 'find_item_by_id; | |
| 147 | - } | |
| 148 | - } | |
| 149 | - | |
| 150 | - if let Some(index) = may_index { | |
| 151 | - // patch our items to remove the item at the index | |
| 152 | - let _ = items.list_patch_remove(index); | |
| 153 | - } | |
| 154 | - } | |
| 155 | - _ => { | |
| 156 | - log::error!("Leaving list logic loop - this shouldn't happen"); | |
| 157 | - break; | |
| 158 | - }, | |
| 159 | - } | |
| 160 | - } | |
| 161 | - // ANCHOR_END: list_logic_loop | |
| 162 | -} | |
| 163 | - | |
| 164 | -// ANCHOR: list_view | |
| 165 | -fn list_view<T>(to_logic: broadcast::Sender<ListMsg>, children: T) -> ViewBuilder<Dom> | |
| 166 | -where | |
| 167 | - T: Stream<Item = ListPatch<ViewBuilder<Dom>>> + Sendable, | |
| 168 | -{ | |
| 169 | - builder! { | |
| 170 | - <fieldset> | |
| 171 | - <legend>"A List of Gizmos"</legend> | |
| 172 | - <button style:cursor="pointer" on:click=to_logic.sink().contra_map(|_| ListMsg::NewItem)> | |
| 173 | - "Create a new item" | |
| 174 | - </button> | |
| 175 | - <fieldset> | |
| 176 | - <legend>"Items"</legend> | |
| 177 | - <ol patch:children=children> | |
| 178 | - </ol> | |
| 179 | - </fieldset> | |
| 180 | - </fieldset> | |
| 181 | - } | |
| 182 | -} | |
| 183 | -// ANCHOR_END: list_view | |
| 184 | - | |
| 185 | -/// Create our list component. | |
| 186 | -fn list() -> Component<Dom> { | |
| 187 | - let (logic_tx, logic_rx) = broadcast::bounded(1); | |
| 188 | - let (item_patch_tx, item_patch_rx) = mpmc::bounded(1); | |
| 189 | - Component::from(list_view(logic_tx, item_patch_rx)) | |
| 190 | - .with_logic(list_logic(logic_rx, item_patch_tx)) | |
| 191 | -} | |
| 192 | - | |
| 193 | -#[wasm_bindgen] | |
| 194 | -pub fn main(parent_id: Option<String>) -> Result<(), JsValue> { | |
| 195 | - panic::set_hook(Box::new(console_error_panic_hook::hook)); | |
| 196 | - console_log::init_with_level(Level::Trace).unwrap(); | |
| 197 | - let component = list(); | |
| 198 | - let view = component.build().unwrap(); | |
| 199 | - | |
| 200 | - if let Some(id) = parent_id { | |
| 201 | - let parent = mogwai::utils::document().get_element_by_id(&id).unwrap(); | |
| 202 | - view.run_in_container(&parent) | |
| 203 | - } else { | |
| 204 | - view.run() | |
| 205 | - } | |
| 206 | -} |
Please
register
or
login
to post a comment