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