lib.rs 14.1 KB
mod data;

use artshop_common::types::MarkdownJson;
use js_sys::JsString;
use log::Level;
use mogwai::prelude::*;
use web_sys::{RequestInit, RequestMode, Request, Response, MouseEvent};
use serde::{Deserialize, Serialize};
use std::panic;
use wasm_bindgen::prelude::*;

#[derive(Clone)]
enum AppLogic {
    Update,
    Toggle,
    Store,
}

fn md_to_html(source: &str) -> String {
    use pulldown_cmark::{Parser, Options, html};

    let parser = Parser::new_ext(source, Options::all());
    let mut html_output = String::new();
    html::push_html(&mut html_output, parser);

    html_output
}

async fn md_from_db() -> Vec<MarkdownJson> {
    let window = web_sys::window().unwrap();
    let mut opts = RequestInit::new();
    opts.method("GET").mode(RequestMode::Cors);

    let request = Request::new_with_str_and_init(
          "/api/v0/markdowns/md-example?patch=1" // patch=1 always gets all patches.
        , &opts ).unwrap();
    request.headers().set("Accept", "application/json").unwrap();

    let response = JsFuture::from(window.fetch_with_request(&request))
                 . await.unwrap()
                 . dyn_into::<Response>().unwrap();
    let data = String::from( JsFuture::from(response.text().unwrap())
                           . await.unwrap()
                           . dyn_into::<JsString>().unwrap()
                           );
    let data :Vec<MarkdownJson> = serde_json::from_str(data.as_str()).unwrap();

    data
}

async fn md_to_db(md :MarkdownJson) {
    let encoded = serde_json::to_string(&md).unwrap();

    let window = web_sys::window().unwrap();
    let mut opts = RequestInit::new();
    opts . method("PUT")
         . mode(RequestMode::Cors)
         . body(Some(&encoded.into()));

    let url_str = format!("/api/v0/markdowns/{}", md.name);
    let request = Request::new_with_str_and_init(url_str.as_str(), &opts)
                . unwrap();
    request.headers().set("Content-Type", "application/json").unwrap();

    let _response = JsFuture::from(window.fetch_with_request(&request))
                  . await.unwrap()
                  . dyn_into::<Response>().unwrap();

    /* do something with the response here.... */
}

async fn editor_logic( mut rx_logic: broadcast::Receiver<AppLogic>
                     , tx_view: broadcast::Sender<String>
                     , mut rx_dom: broadcast::Receiver<Dom>
                     , md :Vec<MarkdownJson> ) {
    let dom = rx_dom.next().await.unwrap();
    let mut show_edit = false;

    fn get_md(dom: &Dom) -> String {
        match dom.inner_read() {
            Either::Left(dom_js) => dom_js . to_owned()
                                           . dyn_into::<Node>().unwrap()
                                           . first_child().unwrap()
                                           . dyn_into::<HtmlElement>().unwrap()
                                           . inner_text(),
            _ => String::from(""),
        }
    }

    fn update(dom: &Dom) {
        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(1).unwrap()
                   . dyn_into::<HtmlElement>().unwrap()
                   . set_inner_html(md_to_html(get_md(dom).as_str()).as_str())
        };
    }

    update(&dom);

    /* 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 => {
                let mut new_md = md[0].clone();
                new_md.content = get_md(&dom);
                md_to_db(new_md).await;
            },
            AppLogic::Update => update(&dom),
            AppLogic::Toggle => {
                show_edit = ! show_edit;
                match show_edit {
                    true => tx_view . broadcast(String::from("block"))
                                    . await.unwrap(),
                    false => tx_view . broadcast(String::from("none"))
                                     . await.unwrap(),
                };
            },
        }
    }
}

fn editor_view( tx_logic: broadcast::Sender<AppLogic>
              , rx_view: broadcast::Receiver<String>
              , tx_dom: broadcast::Sender<Dom>
              , md: &Vec<MarkdownJson>
) -> ViewBuilder<Dom> {
    let ns = "http://www.w3.org/2000/svg";

    let input_filter = tx_logic
                     . sink()
                     . contra_map(|_| AppLogic::Update);
    let store_filter = tx_logic
                     . sink()
                     . 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);

    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", rx_view)>
                <pre on:click=store_filter>{md[0].content.clone()}</pre>
            </div>
            <div>
                <div>
                <button>
                    <svg xmlns=ns viewBox="0 -1 24 24"
                         style:width="1.5em" style:height="1.5em"
                         style:fill="none">
                        <path xmlns=ns
                              style:fill="black"
                              d="M9.17154 11.508
                                 L7.75732 10.0938
                                 L12 5.85113
                                 L16.2426 10.0938
                                 L14.8284 11.508
                                 L12 8.67956
                                 L9.17154 11.508Z"/>
                        <path xmlns=ns
                              style:fill="black"
                              d="M9.17154 12.492
                                 L7.75732 13.9062
                                 L12 18.1489
                                 L16.2426 13.9062
                                 L14.8284 12.492
                                 L12 15.3204
                                 L9.17154 12.492Z"/>
                        <path xmlns=ns
                              style:fill="black"
                              style:fill_rule="evenodd"
                              style:clip_rule="evenodd"
                              d="M1 5
                                 C1 2.79086 2.79086 1 5 1
                                 H19
                                 C21.2091 1 23 2.79086 23 5
                                 V19
                                 C23 21.2091 21.2091 23 19 23
                                 H5
                                 C2.79086 23 1 21.2091 1 19
                                 V5Z
                                 M5 3
                                 H19
                                 C20.1046 3 21 3.89543 21 5
                                 V19
                                 C21 20.1046 20.1046 21 19 21
                                 H5
                                 C3.89543 21 3 20.1046 3 19
                                 V5
                                 C3 3.89543 3.89543 3 5 3Z"/>
                    </svg>
                </button>
                <button>
                    <svg xmlns=ns viewBox="0 -1 32 32"
                         style:width="1.5em" style:height="1.5em"
                         style:enable_background="new 0 0 32 32"
                         style:fill="none"
                         style:stroke="#000"
                         style:stroke_width="2"
                         style:stroke_linecap="round"
                         style:stroke_linejoin="round"
                         style:stroke_miterlimit="10">
                        <ellipse xmlns=ns
                                 cx="14" cy="8" rx="10" ry="5"/>
                        <line xmlns=ns
                              x1="24" y1="16" x2="24" y2="8"/>
                        <path xmlns=ns
                              d="M4,8 v8 c0,2.8,4.5,5,10,5
                                  c1.2,0,2.3-0.1,3.4-0.3"/>
                        <path xmlns=ns
                              d="M4,16 v8 c0,2.8,4.5,5,10,5
                                  c2,0,3.8-0.3,5.3-0.8"/>
                        <circle xmlns=ns
                                cx="24" cy="23" r="7"/>
                        <line xmlns=ns
                              x1="24" y1="16" x2="24" y2="26"/>
                        <polyline xmlns=ns
                                  points="21,23 24,26 27,23"/>
                    </svg>
                </button>
                <button>
                    <svg xmlns=ns viewBox="10.3 3.8 76.2 88.7"
                         style:width="1.5em" style:height="1.5em">
                        <g xmlns=ns id="_x37_0">
                        <polygon xmlns=ns
                                 points="35.4,18.1 31.1,4.3 31.1,16.3
                                         24.5,16.3 24.5,10.9 18.3,18.9
                                         13.8,23.7 17,23.7 19.8,31.8
                                         27.9,34.1 27.9,37.8 38.7,41.8
                                         38.7,34.1 38.7,30.4 38.7,25.2
                                         46.8,25.2 "/>
                        <path xmlns=ns
                              d="M75.7,39.9 h-8.3 h-40.5 h-8.3 h-3 v9.5
                                  v2 h4.7 c2.2,13.3,6.8,42.2,6.8,42.2 h40
                                  c0,0,4.6-27.6,7-42.2 h4.7 v-2 v-9.5
                                 H75.7z
                                 M67.7,51.4 l-2.6,15.6 l-6.1-6.9 l8-8.7
                                 H67.7z
                                 M63.7,75.6 l-1.3,7.9 l-2.9-3.2
                                 L63.7,75.6z
                                 M56.6,77.2
                                 L50,70 l6.2-6.7 l6.4,7.3
                                 L56.6,77.2z
                                 M52.4,51.4 h8.9 l-5.1,5.5 l-4.8-5.4
                                 C51.8,51.4,52.1,51.4,52.4,51.4z
                                 M53.3,60 l-6.2,6.8 l-6.6-7.2 l6.4-6.9
                                 L53.3,60z
                                 M30.5,74.9 l4.6,5 l-3.2,3.5
                                 L30.5,74.9z
                                 M31.4,69.6 l6.2-6.8 l6.6,7.2 l-6.2,6.8
                                 L31.4,69.6z
                                 M41.9,51.4 c0.1,0,0.2,0,0.3,0 l-4.7,5.1
                                  l-4.7-5.1
                                 H41.9z
                                 M27.1,51.4 l7.6,8.3 l-5.7,6.2l-2.3-14.5
                                 H27.1z
                                 M34.6,86.7 l3.3-3.6 l3.3,3.6
                                 H34.6z
                                 M47.1,86.7 l-6.2-6.8 l6.2-6.8 l6.6,7.2
                                  l-5.8,6.4
                                 H47.1z
                                 M53.7,86.7 l2.9-3.2 l3,3.2
                                 H53.7z"/>
                        </g>
                    </svg>
                </button>
                <button on:click=toggle_filter>
                    <svg xmlns=ns viewBox="0 0 220.001 220.001"
                         style:width="1.5em" style:height="1.5em">
                        <g xmlns=ns>
                            <polygon xmlns=ns
                                     points="0,220 59.34,213.86 6.143,160.661"/>
                            <path xmlns=ns
                                  d="M132.018,34.787 l53.197,53.197
                                     L69.568,203.631
                                     L16.37,150.434
                                     L132.018,34.787z
                                     M212.696,60.502 
                                      c9.738,-9.738,9.742,-25.527,0,-35.268
                                      l-17.93,-17.93
                                      c-9.738,-9.74,-25.529,-9.738,-35.268,0
                                      l-17.346,17.347 l53.199,53.196
                                     L212.696,60.502z"/>
                        </g>
                    </svg>
                </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 md = md_from_db().await;

    let (tx_dom, rx_dom) = broadcast::bounded(1);
    let (tx_logic, rx_logic) = broadcast::bounded(1);
    let (tx_view, rx_view) = broadcast::bounded(1);
    let comp = Component::from( editor_view(tx_logic, rx_view, tx_dom, &md) )
             . with_logic( editor_logic(rx_logic, tx_view, rx_dom, md) );

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