verdigris 0.2.1

Browser application to explore, learn and debug CoAP
#![recursion_limit="1024"]

mod ws;
mod coapws;
mod binentry;
mod message_editor;
mod messageexchanger;
mod colorserver;
mod colorpicker;
mod blebridge;
mod about;

mod coapwsmessage;
mod util;

use wasm_bindgen::prelude::*;
use yew::prelude::*;
use yew_router::{prelude::*, Switch};

/// A wrapper around a Route that enables fragment-only routing
///
/// When analyzing the current address, the route considers only the fragment identifier.
/// Conversely, all routes must start with a '#' sign rather than a slash.
///
/// This is useful for applications that are expected to be shipped as static files to any file
/// server, are supposed to work from any file name, and do not require any configuration on the
/// server side.
#[derive(Clone)]
struct FragmentOnlyRoute<I: Switch> {
    pub inner: I,
}

impl<I: Switch> Switch for FragmentOnlyRoute<I> {
    fn from_route_part<STATE>(part: String, state: Option<STATE>) -> (Option<Self>, Option<STATE>) {
        let part = match part.find('#') {
            Some(i) => &part[i..],
            None => "",
        }.to_string();
        let (slef, outstate) = I::from_route_part(part, state);
        (slef.map(|s| s.into()), outstate)
    }

    fn build_route_section<STATE>(self, route: &mut String) -> Option<STATE> {
        // No further adjustments are needed: As the inner route produces URI refrences starting
        // with a '#', they can just be applied and do not change the resource.
        self.inner.build_route_section(route)
    }
}

impl<I: Switch> From<I> for FragmentOnlyRoute<I> {
    fn from(inner: I) -> Self {
        Self { inner }
    }
}

struct Tabbing {
    link: ComponentLink<Self>,
    router: Box<dyn Bridge<RouteAgent>>,
}

impl Component for Tabbing {
    type Message = Msg;
    type Properties = ();

    fn create(_: Self::Properties, link: ComponentLink<Self>) -> Self {
        let callback = link.callback(|_| Msg::NoOp); // Workaround for the peculiarity that RouteAgent implements a Bridge towards us
        let router = RouteAgent::bridge(callback);
        Self { link, router }
    }

    fn update(&mut self, msg: Self::Message) -> ShouldRender {
        match msg {
            Msg::ColorServerChange { ep, rd } => {
                let route = Route::from(TabRoute::ColorServer { ep, rd });
                self.router.send(yew_router::agent::RouteRequest::ChangeRoute(route));
            }
            Msg::NoOp => {}
        }
        false
    }

    fn change(&mut self, _: Self::Properties) -> ShouldRender {
        false
    }

    fn view(&self) -> Html {
        let colorserver_onchange = self.link.callback(|(ep, rd)| Msg::ColorServerChange { ep, rd });

        html! { <>
            <nav>
            <ul>
                <li><RouterAnchor<TabRoute> route=TabRoute::MessageExchanger>{ "Message exchanger" } </RouterAnchor<TabRoute>></li>
                <li><RouterAnchor<TabRoute> route=TabRoute::ColorServer { ep: "-".to_string(), rd: "-".to_string()}> { "Color server" } </RouterAnchor<TabRoute>></li>
                <li><RouterAnchor<TabRoute> route=TabRoute::ColorPicker> { "Color client" } </RouterAnchor<TabRoute>></li>
                <li><RouterAnchor<TabRoute> route=TabRoute::BleBridge> { "BLE (GATT) bridge" } </RouterAnchor<TabRoute>></li>
                <li><RouterAnchor<TabRoute> route=TabRoute::About> { "About Verdigris" } </RouterAnchor<TabRoute>></li>
            </ul>
            </nav>
            <Router<FragmentOnlyRoute<TabRoute>>
                render=Router::render(move |switch: FragmentOnlyRoute<TabRoute>| {
                    match switch.inner {
                        TabRoute::MessageExchanger => html! { <messageexchanger::CoAPWSMessageExchanger /> },
                        TabRoute::ColorServer { ep, rd } => html! {
                            <colorserver::ColorServer
                                ep=match ep.as_str() { "-" => None, x => Some(ep) }
                                rd=match rd.as_str() { "-" => None, x => Some(rd) }
                                onchange=&colorserver_onchange
                            />
                        },
                        TabRoute::ColorPicker => html! { <colorpicker::ColorPicker /> },
                        TabRoute::BleBridge => html! { <blebridge::BleBridge /> },
                        TabRoute::About => html! { <about::About /> },
                    }})
                redirect=Router::redirect(|route: Route| FragmentOnlyRoute::from(TabRoute::About))
            />
        </> }
    }
}

pub enum Msg {
    ColorServerChange { ep: String, rd: String },
    NoOp,
}

#[derive(Debug, Clone, Switch)]
pub enum TabRoute {
    #[to = "#msgex"]
    MessageExchanger,
    #[to = "#colserv:{ep}:{*:rd}"]
    ColorServer { ep: String, rd: String },
    #[to = "#colcli"]
    ColorPicker,
    #[to = "#ble"]
    BleBridge,
    #[to = "#about"]
    About,
}

#[wasm_bindgen(start)]
pub fn run_app() {
    std::panic::set_hook(Box::new(console_error_panic_hook::hook));

    console_log::init_with_level(log::Level::Debug)
        .expect("Console not available for logging");

    App::<Tabbing>::new().mount_to_body();
}