lib.rs 8.36 KB
mod api;
mod data;
mod error;
mod client;

use api::markdown::Markdown;
use data::icons::*;
use log::Level;
use mogwai::prelude::*;
use std::panic;
use wasm_bindgen::prelude::*;

#[derive(Clone)]
enum AppLogic {
    Update,
    Toggle,
    Store,
    Select,
    Choose(Option<i32>),
    Discard,
}

async fn editor_logic( mut rx_logic: broadcast::Receiver<AppLogic>
                     , tx_logic: broadcast::Sender<AppLogic>
                     , tx_toggle: broadcast::Sender<bool>
                     , tx_patches: mpmc::Sender<ListPatch<ViewBuilder<Dom>>>
                     , mut rx_dom: broadcast::Receiver<Dom> )
{
    let dom = rx_dom.next().await.unwrap();
    let mut show_edit = false;
    let mut md = Markdown::new("md-example").await.unwrap();

    let container = match dom.inner_read() {
            Either::Left(dom_js) =>
                Some( ( dom_js
                      . to_owned()
                      . dyn_into::<Node>().unwrap()
                      . first_child().unwrap()
                      . first_child().unwrap()
                      . dyn_into::<HtmlElement>().unwrap()
                      , dom_js
                      . to_owned()
                      . dyn_into::<Node>().unwrap()
                      . child_nodes().get(1).unwrap()
                      . child_nodes().get(1).unwrap()
                      . dyn_into::<HtmlElement>().unwrap() )),
            _ => None,
    };

    let cont_ref = container.as_ref();

    let get_md = move || {
        match cont_ref {
            Some((md_cont, _)) => md_cont.inner_text(),
            None => String::from(""),
        }
    };

    let set_md = move |md :&str| {
        match cont_ref {
            Some((md_cont, _)) => md_cont.set_text_content(Some(md)),
            None => (),
        }
    };

    let update = move || {
        match cont_ref {
            Some((_, view_cont)) => {
                use pulldown_cmark::{Parser, Options, html};

                let mut html_out = String::new();
                let md = get_md();
                let parser = Parser::new_ext(&md, Options::all());

                html::push_html(&mut html_out, parser);
                view_cont.set_inner_html(&html_out)
            },
            None => (),
        }
    };

    set_md(md.json.content.as_str());
    update();

    /* play with katex ==== */
    let opts = katex::Opts::builder()
             . output_type(katex::opts::OutputType::Mathml)
             . build().unwrap();
    let formula1 = katex::render_with_opts("E = mc^2", &opts).unwrap();
    let formula2 = katex::render_with_opts("e^{i*\\pi} +1 = 0", &opts).unwrap();

    if let Either::Left(dom_js) = dom.inner_read() {
        dom_js . to_owned()
               . dyn_into::<Node>().unwrap()
               . child_nodes().get(1).unwrap()
               . child_nodes().get(2).unwrap()
               . dyn_into::<HtmlElement>().unwrap()
               . set_inner_html(formula1.as_str())
    };

    if let Either::Left(dom_js) = dom.inner_read() {
        dom_js . to_owned()
               . dyn_into::<Node>().unwrap()
               . child_nodes().get(1).unwrap()
               . child_nodes().get(3).unwrap()
               . dyn_into::<HtmlElement>().unwrap()
               . set_inner_html(formula2.as_str())
    };
    /* =========== */

    while let Some(msg) = rx_logic.next().await {
        match msg {
            AppLogic::Store => {
                md.json.content = get_md();
                md.save().await.unwrap();
            },
            AppLogic::Update => update(),
            AppLogic::Toggle => {
                show_edit = ! show_edit;
                tx_toggle.broadcast(show_edit).await.unwrap();
            },
            AppLogic::Discard => {
                set_md(md.json.content.as_str());
                update();
            },
            AppLogic::Select => {
                let patches = md
                            . patches().await.unwrap()
                            . into_iter()
                            . map(|diff| {
                                let id = Some(diff.id);
                                let choose_filter = tx_logic
                                    . sink()
                                    . contra_map(move |_| AppLogic::Choose(id));
                                builder! {
                                    <li><button on:click=choose_filter
                                                value=format!("{}", diff.id.to_owned())>
                                    {diff.date_created.to_owned()}
                                    </button></li>
                                }});
                let all = vec![builder! {
                    <li><button on:click=tx_logic.sink().contra_map(|_| AppLogic::Choose(None))>
                    "Current"
                    </button></li>
                }].into_iter().chain(patches);

                let list_replace = ListPatch::splice(.., all);
                tx_patches.send(list_replace).await.unwrap();
            },
            AppLogic::Choose(id) => {
                md.read(id).await.unwrap();
                set_md(md.json.content.as_str());
                update();
            },
        }
    }
}

fn editor_view( tx_logic: broadcast::Sender<AppLogic>
              , rx_toggle: broadcast::Receiver<bool>
              , rx_patches: mpmc::Receiver<ListPatch<ViewBuilder<Dom>>>
              , tx_dom: broadcast::Sender<Dom>
              ) -> ViewBuilder<Dom>
{
    let input_filter = tx_logic
                     . sink()
                     . contra_map(|_| AppLogic::Update);
    let store_filter = tx_logic
                     . sink()
                     . contra_map(|_| AppLogic::Store);
                     /* keep as example how to handle concrete events. ==
                     . contra_filter_map(|e :DomEvent| {
                         if let Either::Left(e) = e.clone_inner() {
                             let e = e.dyn_into::<MouseEvent>().unwrap();
                             match e.alt_key() {
                                 true => Some(AppLogic::Store),
                                 false => None
                             }
                         } else {
                             None
                         }
                     });
                     == */
    let toggle_filter = tx_logic
                      . sink()
                      . contra_map(|_| AppLogic::Toggle);
    let select_filter = tx_logic
                      . sink()
                      . contra_map(|_e| AppLogic::Select);
    let discard_filter = tx_logic
                       . sink()
                       . contra_map(|_| AppLogic::Discard);

    let toggle_map = rx_toggle
                   . map(|t| match t {
                       true => String::from("block"),
                       false => String::from("none") });

    builder! {
        <div class="input"
             style:width="33%"
             on:input=input_filter
             capture:view=tx_dom.sink()>
            <div contenteditable="true"
                 style:cursor="text"
                 style:display=("none", toggle_map)>
                <pre></pre>
            </div>
            <div>
                <div>
                    <div>
                    <button on:click=select_filter>{select_icon()}</button>
                    <ul patch:children=rx_patches>
                    </ul>
                    </div>
                    <button on:click=store_filter>{save_icon()}</button>
                    <button on:click=discard_filter>{discard_icon()}</button>
                    <button on:click=toggle_filter>{edit_icon()}</button>
                </div>
                <div></div>
                <div></div>
                <div></div>
            </div>
        </div>
    }
}

#[wasm_bindgen(start)]
pub async fn main() -> Result<(), JsValue> {
    panic::set_hook(Box::new(console_error_panic_hook::hook));
    console_log::init_with_level(Level::Trace).unwrap();

    let (tx_dom, rx_dom) = broadcast::bounded(1);
    let (tx_logic, rx_logic) = broadcast::bounded(1);
    let (tx_toggle, rx_toggle) = broadcast::bounded(1);
    let (tx_patches, rx_patches) = mpmc::bounded(1);

    let view = editor_view(tx_logic.clone(), rx_toggle, rx_patches, tx_dom);
    let logic = editor_logic(rx_logic, tx_logic, tx_toggle, tx_patches, rx_dom);
    let comp = Component::from(view).with_logic(logic);

    let page = Component::from(builder! {{comp}});
    page.build()?.run()
}