Commit fdf4522cfe42a780852e72d7f2054f708e5cc2a5
1 parent
5de3a1b2
Add simple component creation code for markdown
Showing
6 changed files
with
245 additions
and
226 deletions
ui/src/component/markdown/logic.rs
0 → 100644
| 1 | +use crate::api::markdown::Markdown; | |
| 2 | +use mogwai::prelude::*; | |
| 3 | +use super::state::MarkdownState; | |
| 4 | + | |
| 5 | +#[derive(Clone)] | |
| 6 | +pub(super) enum MarkdownLogic { | |
| 7 | + Choose(Option<i32>), | |
| 8 | + Discard, | |
| 9 | + Select, | |
| 10 | + Store, | |
| 11 | + Toggle, | |
| 12 | + Update, | |
| 13 | +} | |
| 14 | + | |
| 15 | +pub(super) async fn markdown_logic( mut rx_logic: broadcast::Receiver<MarkdownLogic> | |
| 16 | + , tx_logic: broadcast::Sender<MarkdownLogic> | |
| 17 | + , tx_toggle: broadcast::Sender<bool> | |
| 18 | + , tx_patches: mpmc::Sender<ListPatch<ViewBuilder<Dom>>> | |
| 19 | + , mut rx_dom: broadcast::Receiver<Dom> ) | |
| 20 | +{ | |
| 21 | + let dom = rx_dom.next().await.unwrap(); | |
| 22 | + let mut md = Markdown::new("md-example").await.unwrap(); | |
| 23 | + let mut state = MarkdownState::new(dom).unwrap(); | |
| 24 | + | |
| 25 | + while let Some(msg) = rx_logic.next().await { | |
| 26 | + match msg { | |
| 27 | + MarkdownLogic::Store => { | |
| 28 | + let new_md = state.get_md(); | |
| 29 | + if md.json.content != new_md { | |
| 30 | + md.json.content = state.get_md(); | |
| 31 | + md.save().await.unwrap(); | |
| 32 | + } | |
| 33 | + }, | |
| 34 | + MarkdownLogic::Update => state.update(), | |
| 35 | + MarkdownLogic::Toggle => { | |
| 36 | + state.toggle_show_edit(); | |
| 37 | + tx_toggle.broadcast(state.show_edit).await.unwrap(); | |
| 38 | + }, | |
| 39 | + MarkdownLogic::Discard => { | |
| 40 | + state.set_md(Some(md.json.content.as_str())); | |
| 41 | + state.update(); | |
| 42 | + }, | |
| 43 | + MarkdownLogic::Select => { | |
| 44 | + state.toggle_show_patches(); | |
| 45 | + | |
| 46 | + match state.show_patches { | |
| 47 | + true => { | |
| 48 | + let patches = md | |
| 49 | + . patches().await.unwrap() | |
| 50 | + . into_iter() | |
| 51 | + . map(|diff| { | |
| 52 | + let id = Some(diff.id); | |
| 53 | + let choose_filter = tx_logic | |
| 54 | + . sink() | |
| 55 | + . contra_map(move |_| MarkdownLogic::Choose(id)); | |
| 56 | + builder! { | |
| 57 | + <li><button on:click=choose_filter | |
| 58 | + value=format!("{}", diff.id.to_owned())> | |
| 59 | + {diff.date_created.to_owned()} | |
| 60 | + </button></li> | |
| 61 | + }}); | |
| 62 | + let all = vec![builder! { | |
| 63 | + <li><button on:click=tx_logic.sink().contra_map(|_| MarkdownLogic::Choose(None))> | |
| 64 | + "Current" | |
| 65 | + </button></li> | |
| 66 | + }].into_iter().chain(patches); | |
| 67 | + | |
| 68 | + let list_replace = ListPatch::splice(.., all); | |
| 69 | + tx_patches.send(list_replace).await.unwrap(); | |
| 70 | + }, | |
| 71 | + false => tx_patches.send(ListPatch::drain()).await.unwrap(), | |
| 72 | + } | |
| 73 | + }, | |
| 74 | + MarkdownLogic::Choose(id) => { | |
| 75 | + md.read(id).await.unwrap(); | |
| 76 | + state.set_md(Some(md.json.content.as_str())); | |
| 77 | + state.update(); | |
| 78 | + tx_patches.send(ListPatch::drain()).await.unwrap(); | |
| 79 | + state.reset_show_patches(); | |
| 80 | + }, | |
| 81 | + } | |
| 82 | + } | |
| 83 | +} | ... | ... |
ui/src/component/markdown/mod.rs
0 → 100644
| 1 | +mod logic; | |
| 2 | +mod state; | |
| 3 | +mod view; | |
| 4 | + | |
| 5 | +use mogwai::prelude::*; | |
| 6 | + | |
| 7 | +use self::{logic::markdown_logic, view::markdown_view}; | |
| 8 | + | |
| 9 | +pub(crate) async fn new() -> Component<Dom> { | |
| 10 | + let (tx_dom, rx_dom) = broadcast::bounded(1); | |
| 11 | + let (tx_logic, rx_logic) = broadcast::bounded(1); | |
| 12 | + let (tx_toggle, rx_toggle) = broadcast::bounded(1); | |
| 13 | + let (tx_patches, rx_patches) = mpmc::bounded(1); | |
| 14 | + | |
| 15 | + let view = markdown_view(tx_logic.clone(), rx_toggle, rx_patches, tx_dom); | |
| 16 | + let logic = markdown_logic(rx_logic, tx_logic, tx_toggle, tx_patches, rx_dom); | |
| 17 | + Component::from(view).with_logic(logic) | |
| 18 | +} | ... | ... |
ui/src/component/markdown/state.rs
0 → 100644
| 1 | +use mogwai::prelude::*; | |
| 2 | +use wasm_bindgen::JsValue; | |
| 3 | + | |
| 4 | +pub(super) struct MarkdownState { | |
| 5 | + editor: HtmlElement, | |
| 6 | + display: HtmlElement, | |
| 7 | + pub(super) show_edit: bool, | |
| 8 | + pub(super) show_patches: bool, | |
| 9 | +} | |
| 10 | + | |
| 11 | +impl MarkdownState { | |
| 12 | + pub(super) fn new(dom: Dom) -> Result<Self, JsValue> { | |
| 13 | + let (editor, display) = match dom.inner_read() { | |
| 14 | + Either::Left(dom_js) => { | |
| 15 | + let node = &dom_js.to_owned().dyn_into::<Node>()?; | |
| 16 | + ( node . first_child().unwrap() | |
| 17 | + . first_child().unwrap() | |
| 18 | + . dyn_into::<HtmlElement>()? | |
| 19 | + , node . child_nodes().get(1).unwrap() | |
| 20 | + . child_nodes().get(1).unwrap() | |
| 21 | + . dyn_into::<HtmlElement>()? ) | |
| 22 | + }, | |
| 23 | + _ => Err(JsValue::UNDEFINED)?, | |
| 24 | + }; | |
| 25 | + let show_edit = false; | |
| 26 | + let show_patches = false; | |
| 27 | + | |
| 28 | + Ok(Self { editor, display, show_edit, show_patches }) | |
| 29 | + } | |
| 30 | + | |
| 31 | + pub(super) fn get_md(&self) -> String { | |
| 32 | + self.editor.inner_text() | |
| 33 | + } | |
| 34 | + | |
| 35 | + pub(super) fn set_md(&self, md :Option<&str>) { | |
| 36 | + self.editor.set_text_content(md) | |
| 37 | + } | |
| 38 | + | |
| 39 | + pub(super) fn update(&self) { | |
| 40 | + use pulldown_cmark::{Parser, Options, html}; | |
| 41 | + | |
| 42 | + let mut html_out = String::new(); | |
| 43 | + let md = self.get_md(); | |
| 44 | + let parser = Parser::new_ext(&md, Options::all()); | |
| 45 | + | |
| 46 | + html::push_html(&mut html_out, parser); | |
| 47 | + self.display.set_inner_html(&html_out) | |
| 48 | + } | |
| 49 | + | |
| 50 | + pub(super) fn toggle_show_edit(&mut self) { | |
| 51 | + self.show_edit = ! self.show_edit; | |
| 52 | + } | |
| 53 | + | |
| 54 | + #[allow(dead_code)] | |
| 55 | + pub(super) fn set_show_edit(&mut self) { | |
| 56 | + self.show_edit = true; | |
| 57 | + } | |
| 58 | + | |
| 59 | + #[allow(dead_code)] | |
| 60 | + pub(super) fn reset_show_edit(&mut self) { | |
| 61 | + self.show_edit = false; | |
| 62 | + } | |
| 63 | + | |
| 64 | + pub(super) fn toggle_show_patches(&mut self) { | |
| 65 | + self.show_patches = ! self.show_patches; | |
| 66 | + } | |
| 67 | + | |
| 68 | + #[allow(dead_code)] | |
| 69 | + pub(super) fn set_show_patches(&mut self) { | |
| 70 | + self.show_patches = true; | |
| 71 | + } | |
| 72 | + | |
| 73 | + pub(super) fn reset_show_patches(&mut self) { | |
| 74 | + self.show_patches = false; | |
| 75 | + } | |
| 76 | +} | ... | ... |
ui/src/component/markdown/view.rs
0 → 100644
| 1 | +use mogwai::prelude::*; | |
| 2 | + | |
| 3 | +use crate::data::icons::*; | |
| 4 | +use super::logic::MarkdownLogic; | |
| 5 | + | |
| 6 | +pub(super) fn markdown_view( tx_logic: broadcast::Sender<MarkdownLogic> | |
| 7 | + , rx_toggle: broadcast::Receiver<bool> | |
| 8 | + , rx_patches: mpmc::Receiver<ListPatch<ViewBuilder<Dom>>> | |
| 9 | + , tx_dom: broadcast::Sender<Dom> | |
| 10 | + ) -> ViewBuilder<Dom> | |
| 11 | +{ | |
| 12 | + let input_filter = tx_logic | |
| 13 | + . sink() | |
| 14 | + . contra_map(|_| MarkdownLogic::Update); | |
| 15 | + let store_filter = tx_logic | |
| 16 | + . sink() | |
| 17 | + . contra_map(|_| MarkdownLogic::Store); | |
| 18 | + let toggle_filter = tx_logic | |
| 19 | + . sink() | |
| 20 | + . contra_map(|_| MarkdownLogic::Toggle); | |
| 21 | + let select_filter = tx_logic | |
| 22 | + . sink() | |
| 23 | + . contra_map(|_e| MarkdownLogic::Select); | |
| 24 | + let discard_filter = tx_logic | |
| 25 | + . sink() | |
| 26 | + . contra_map(|_| MarkdownLogic::Discard); | |
| 27 | + | |
| 28 | + let toggle_map = rx_toggle | |
| 29 | + . map(|t| match t { | |
| 30 | + true => String::from("block"), | |
| 31 | + false => String::from("none") }); | |
| 32 | + | |
| 33 | + builder! { | |
| 34 | + <div class="input" | |
| 35 | + style:width="33%" | |
| 36 | + post:build=move |_: &mut Dom| { | |
| 37 | + tx_logic.try_broadcast(MarkdownLogic::Choose(None)).unwrap(); | |
| 38 | + } | |
| 39 | + capture:view=tx_dom.sink()> | |
| 40 | + <div contenteditable="true" | |
| 41 | + style:cursor="text" | |
| 42 | + on:input=input_filter | |
| 43 | + style:display=("none", toggle_map)> | |
| 44 | + <pre></pre> | |
| 45 | + </div> | |
| 46 | + <div> | |
| 47 | + <div> | |
| 48 | + <div> | |
| 49 | + <button on:click=select_filter>{select_icon()}</button> | |
| 50 | + <ul patch:children=rx_patches> | |
| 51 | + </ul> | |
| 52 | + </div> | |
| 53 | + <button on:click=store_filter>{save_icon()}</button> | |
| 54 | + <button on:click=discard_filter>{undo_point_icon()}</button> | |
| 55 | + <button on:click=toggle_filter>{edit_icon()}</button> | |
| 56 | + </div> | |
| 57 | + <div></div> | |
| 58 | + <div></div> | |
| 59 | + </div> | |
| 60 | + </div> | |
| 61 | + } | |
| 62 | +} | ... | ... |
ui/src/component/mod.rs
0 → 100644
| 1 | +pub(crate) mod markdown; | ... | ... |
| ... | ... | @@ -2,242 +2,21 @@ mod api; |
| 2 | 2 | mod data; |
| 3 | 3 | mod error; |
| 4 | 4 | mod client; |
| 5 | +mod component; | |
| 5 | 6 | |
| 6 | -use api::markdown::Markdown; | |
| 7 | -use data::icons::*; | |
| 7 | +use std::panic; | |
| 8 | + | |
| 9 | +use crate::component::markdown; | |
| 8 | 10 | use log::Level; |
| 9 | 11 | use mogwai::prelude::*; |
| 10 | -use std::panic; | |
| 11 | 12 | use wasm_bindgen::prelude::*; |
| 12 | 13 | |
| 13 | -#[derive(Clone)] | |
| 14 | -enum AppLogic { | |
| 15 | - Update, | |
| 16 | - Toggle, | |
| 17 | - Store, | |
| 18 | - Select, | |
| 19 | - Choose(Option<i32>), | |
| 20 | - Discard, | |
| 21 | -} | |
| 22 | - | |
| 23 | -async fn editor_logic( mut rx_logic: broadcast::Receiver<AppLogic> | |
| 24 | - , tx_logic: broadcast::Sender<AppLogic> | |
| 25 | - , tx_toggle: broadcast::Sender<bool> | |
| 26 | - , tx_patches: mpmc::Sender<ListPatch<ViewBuilder<Dom>>> | |
| 27 | - , mut rx_dom: broadcast::Receiver<Dom> ) | |
| 28 | -{ | |
| 29 | - let dom = rx_dom.next().await.unwrap(); | |
| 30 | - let mut show_edit = false; | |
| 31 | - let mut md = Markdown::new("md-example").await.unwrap(); | |
| 32 | - | |
| 33 | - let container = match dom.inner_read() { | |
| 34 | - Either::Left(dom_js) => | |
| 35 | - Some( ( dom_js | |
| 36 | - . to_owned() | |
| 37 | - . dyn_into::<Node>().unwrap() | |
| 38 | - . first_child().unwrap() | |
| 39 | - . first_child().unwrap() | |
| 40 | - . dyn_into::<HtmlElement>().unwrap() | |
| 41 | - , dom_js | |
| 42 | - . to_owned() | |
| 43 | - . dyn_into::<Node>().unwrap() | |
| 44 | - . child_nodes().get(1).unwrap() | |
| 45 | - . child_nodes().get(1).unwrap() | |
| 46 | - . dyn_into::<HtmlElement>().unwrap() )), | |
| 47 | - _ => None, | |
| 48 | - }; | |
| 49 | - | |
| 50 | - let cont_ref = container.as_ref(); | |
| 51 | - | |
| 52 | - let get_md = move || { | |
| 53 | - match cont_ref { | |
| 54 | - Some((md_cont, _)) => md_cont.inner_text(), | |
| 55 | - None => String::from(""), | |
| 56 | - } | |
| 57 | - }; | |
| 58 | - | |
| 59 | - let set_md = move |md :&str| { | |
| 60 | - match cont_ref { | |
| 61 | - Some((md_cont, _)) => md_cont.set_text_content(Some(md)), | |
| 62 | - None => (), | |
| 63 | - } | |
| 64 | - }; | |
| 65 | - | |
| 66 | - let update = move || { | |
| 67 | - match cont_ref { | |
| 68 | - Some((_, view_cont)) => { | |
| 69 | - use pulldown_cmark::{Parser, Options, html}; | |
| 70 | - | |
| 71 | - let mut html_out = String::new(); | |
| 72 | - let md = get_md(); | |
| 73 | - let parser = Parser::new_ext(&md, Options::all()); | |
| 74 | - | |
| 75 | - html::push_html(&mut html_out, parser); | |
| 76 | - view_cont.set_inner_html(&html_out) | |
| 77 | - }, | |
| 78 | - None => (), | |
| 79 | - } | |
| 80 | - }; | |
| 81 | - | |
| 82 | - set_md(md.json.content.as_str()); | |
| 83 | - update(); | |
| 84 | - | |
| 85 | - /* play with katex ==== */ | |
| 86 | - let opts = katex::Opts::builder() | |
| 87 | - . output_type(katex::opts::OutputType::Mathml) | |
| 88 | - . build().unwrap(); | |
| 89 | - let formula1 = katex::render_with_opts("E = mc^2", &opts).unwrap(); | |
| 90 | - let formula2 = katex::render_with_opts("e^{i*\\pi} +1 = 0", &opts).unwrap(); | |
| 91 | - | |
| 92 | - if let Either::Left(dom_js) = dom.inner_read() { | |
| 93 | - dom_js . to_owned() | |
| 94 | - . dyn_into::<Node>().unwrap() | |
| 95 | - . child_nodes().get(1).unwrap() | |
| 96 | - . child_nodes().get(2).unwrap() | |
| 97 | - . dyn_into::<HtmlElement>().unwrap() | |
| 98 | - . set_inner_html(formula1.as_str()) | |
| 99 | - }; | |
| 100 | - | |
| 101 | - if let Either::Left(dom_js) = dom.inner_read() { | |
| 102 | - dom_js . to_owned() | |
| 103 | - . dyn_into::<Node>().unwrap() | |
| 104 | - . child_nodes().get(1).unwrap() | |
| 105 | - . child_nodes().get(3).unwrap() | |
| 106 | - . dyn_into::<HtmlElement>().unwrap() | |
| 107 | - . set_inner_html(formula2.as_str()) | |
| 108 | - }; | |
| 109 | - /* =========== */ | |
| 110 | - | |
| 111 | - while let Some(msg) = rx_logic.next().await { | |
| 112 | - match msg { | |
| 113 | - AppLogic::Store => { | |
| 114 | - md.json.content = get_md(); | |
| 115 | - md.save().await.unwrap(); | |
| 116 | - }, | |
| 117 | - AppLogic::Update => update(), | |
| 118 | - AppLogic::Toggle => { | |
| 119 | - show_edit = ! show_edit; | |
| 120 | - tx_toggle.broadcast(show_edit).await.unwrap(); | |
| 121 | - }, | |
| 122 | - AppLogic::Discard => { | |
| 123 | - set_md(md.json.content.as_str()); | |
| 124 | - update(); | |
| 125 | - }, | |
| 126 | - AppLogic::Select => { | |
| 127 | - let patches = md | |
| 128 | - . patches().await.unwrap() | |
| 129 | - . into_iter() | |
| 130 | - . map(|diff| { | |
| 131 | - let id = Some(diff.id); | |
| 132 | - let choose_filter = tx_logic | |
| 133 | - . sink() | |
| 134 | - . contra_map(move |_| AppLogic::Choose(id)); | |
| 135 | - builder! { | |
| 136 | - <li><button on:click=choose_filter | |
| 137 | - value=format!("{}", diff.id.to_owned())> | |
| 138 | - {diff.date_created.to_owned()} | |
| 139 | - </button></li> | |
| 140 | - }}); | |
| 141 | - let all = vec![builder! { | |
| 142 | - <li><button on:click=tx_logic.sink().contra_map(|_| AppLogic::Choose(None))> | |
| 143 | - "Current" | |
| 144 | - </button></li> | |
| 145 | - }].into_iter().chain(patches); | |
| 146 | - | |
| 147 | - let list_replace = ListPatch::splice(.., all); | |
| 148 | - tx_patches.send(list_replace).await.unwrap(); | |
| 149 | - }, | |
| 150 | - AppLogic::Choose(id) => { | |
| 151 | - md.read(id).await.unwrap(); | |
| 152 | - set_md(md.json.content.as_str()); | |
| 153 | - update(); | |
| 154 | - }, | |
| 155 | - } | |
| 156 | - } | |
| 157 | -} | |
| 158 | - | |
| 159 | -fn editor_view( tx_logic: broadcast::Sender<AppLogic> | |
| 160 | - , rx_toggle: broadcast::Receiver<bool> | |
| 161 | - , rx_patches: mpmc::Receiver<ListPatch<ViewBuilder<Dom>>> | |
| 162 | - , tx_dom: broadcast::Sender<Dom> | |
| 163 | - ) -> ViewBuilder<Dom> | |
| 164 | -{ | |
| 165 | - let input_filter = tx_logic | |
| 166 | - . sink() | |
| 167 | - . contra_map(|_| AppLogic::Update); | |
| 168 | - let store_filter = tx_logic | |
| 169 | - . sink() | |
| 170 | - . contra_map(|_| AppLogic::Store); | |
| 171 | - /* keep as example how to handle concrete events. == | |
| 172 | - . contra_filter_map(|e :DomEvent| { | |
| 173 | - if let Either::Left(e) = e.clone_inner() { | |
| 174 | - let e = e.dyn_into::<MouseEvent>().unwrap(); | |
| 175 | - match e.alt_key() { | |
| 176 | - true => Some(AppLogic::Store), | |
| 177 | - false => None | |
| 178 | - } | |
| 179 | - } else { | |
| 180 | - None | |
| 181 | - } | |
| 182 | - }); | |
| 183 | - == */ | |
| 184 | - let toggle_filter = tx_logic | |
| 185 | - . sink() | |
| 186 | - . contra_map(|_| AppLogic::Toggle); | |
| 187 | - let select_filter = tx_logic | |
| 188 | - . sink() | |
| 189 | - . contra_map(|_e| AppLogic::Select); | |
| 190 | - let discard_filter = tx_logic | |
| 191 | - . sink() | |
| 192 | - . contra_map(|_| AppLogic::Discard); | |
| 193 | - | |
| 194 | - let toggle_map = rx_toggle | |
| 195 | - . map(|t| match t { | |
| 196 | - true => String::from("block"), | |
| 197 | - false => String::from("none") }); | |
| 198 | - | |
| 199 | - builder! { | |
| 200 | - <div class="input" | |
| 201 | - style:width="33%" | |
| 202 | - on:input=input_filter | |
| 203 | - capture:view=tx_dom.sink()> | |
| 204 | - <div contenteditable="true" | |
| 205 | - style:cursor="text" | |
| 206 | - style:display=("none", toggle_map)> | |
| 207 | - <pre></pre> | |
| 208 | - </div> | |
| 209 | - <div> | |
| 210 | - <div> | |
| 211 | - <div> | |
| 212 | - <button on:click=select_filter>{select_icon()}</button> | |
| 213 | - <ul patch:children=rx_patches> | |
| 214 | - </ul> | |
| 215 | - </div> | |
| 216 | - <button on:click=store_filter>{save_icon()}</button> | |
| 217 | - <button on:click=discard_filter>{discard_icon()}</button> | |
| 218 | - <button on:click=toggle_filter>{edit_icon()}</button> | |
| 219 | - </div> | |
| 220 | - <div></div> | |
| 221 | - <div></div> | |
| 222 | - <div></div> | |
| 223 | - </div> | |
| 224 | - </div> | |
| 225 | - } | |
| 226 | -} | |
| 227 | - | |
| 228 | 14 | #[wasm_bindgen(start)] |
| 229 | 15 | pub async fn main() -> Result<(), JsValue> { |
| 230 | 16 | panic::set_hook(Box::new(console_error_panic_hook::hook)); |
| 231 | 17 | console_log::init_with_level(Level::Trace).unwrap(); |
| 232 | 18 | |
| 233 | - let (tx_dom, rx_dom) = broadcast::bounded(1); | |
| 234 | - let (tx_logic, rx_logic) = broadcast::bounded(1); | |
| 235 | - let (tx_toggle, rx_toggle) = broadcast::bounded(1); | |
| 236 | - let (tx_patches, rx_patches) = mpmc::bounded(1); | |
| 237 | - | |
| 238 | - let view = editor_view(tx_logic.clone(), rx_toggle, rx_patches, tx_dom); | |
| 239 | - let logic = editor_logic(rx_logic, tx_logic, tx_toggle, tx_patches, rx_dom); | |
| 240 | - let comp = Component::from(view).with_logic(logic); | |
| 19 | + let comp = markdown::new().await; | |
| 241 | 20 | |
| 242 | 21 | let page = Component::from(builder! {{comp}}); |
| 243 | 22 | page.build()?.run() | ... | ... |
Please
register
or
login
to post a comment