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,242 +2,21 @@ mod api; | ||
2 | mod data; | 2 | mod data; |
3 | mod error; | 3 | mod error; |
4 | mod client; | 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 | use log::Level; | 10 | use log::Level; |
9 | use mogwai::prelude::*; | 11 | use mogwai::prelude::*; |
10 | -use std::panic; | ||
11 | use wasm_bindgen::prelude::*; | 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 | #[wasm_bindgen(start)] | 14 | #[wasm_bindgen(start)] |
229 | pub async fn main() -> Result<(), JsValue> { | 15 | pub async fn main() -> Result<(), JsValue> { |
230 | panic::set_hook(Box::new(console_error_panic_hook::hook)); | 16 | panic::set_hook(Box::new(console_error_panic_hook::hook)); |
231 | console_log::init_with_level(Level::Trace).unwrap(); | 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 | let page = Component::from(builder! {{comp}}); | 21 | let page = Component::from(builder! {{comp}}); |
243 | page.build()?.run() | 22 | page.build()?.run() |
Please
register
or
login
to post a comment