1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
#![recursion_limit="1024"]

mod coapws;
mod binentry;
mod message_editor;
mod messageexchanger;
mod colorserver;
mod colorpicker;
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::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::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 = "#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();
}