verdigris 0.2.0

Browser application to explore, learn and debug CoAP
use yew::prelude::*;

pub struct BinaryEntry {
    hexmode: bool,
    link: ComponentLink<Self>,
    props: Props,
}

pub enum Msg {
    Change(String),
    SetHex(bool),
}

#[derive(PartialEq, Clone, Properties, Debug)]
pub struct Props {
    pub value: Vec<u8>,
    pub onchange: Callback<Vec<u8>>,
    #[prop_or(false)]
    pub textarea: bool,
}

/** Return the hexmode probably most suitable for the given data
 *
 * The criterion for non-hex display is that all bytes are printable ASCII; for empty strings,
 * non-hex is preferred */
fn guess_hex(data: &[u8]) -> bool {
    (data.len() > 0) && !data.iter().all(|&i| (i >= 0x20) && (i <= 0x80))
}

impl Component for BinaryEntry {
    type Message = Msg;
    type Properties = Props;

    fn create(props: Self::Properties, link: ComponentLink<Self>) -> Self {
        let hexmode = guess_hex(&props.value);
        Self { link, props, hexmode }
    }

    fn update(&mut self, msg: Self::Message) -> ShouldRender {
        match msg {
            Msg::SetHex(h) => {
                self.hexmode = h;
                true
            }
            Msg::Change(text) => {
                if self.hexmode {
                    if let Ok(d) = hex::decode(text.as_bytes()) {
                        self.props.onchange.emit(d);
                    }
                    // FIXME else show validation error?
                } else {
                    self.props.onchange.emit(text.into_bytes());
                }
                // If the parent accepts it, it'll come in as a change
                false
            }
        }
    }

    fn change(&mut self, props: Self::Properties) -> ShouldRender {
        // An update to the callback might happen eg. something further up changed, but should
        // *not* trigger a re-render as that'd only upset any editing flow by resetting the value.
        // (The callback will only be evaluated when this receives a message, and is not sent in
        // the message.)
        let shouldrender = props.value != self.props.value;

        self.props = props;

        shouldrender
    }

    fn view(&self) -> Html {
        let onchange = self.link.callback(move |c| {
            if let yew::events::ChangeData::Value(s) = c {
                Msg::Change(s)
            } else {
                unreachable!("Input elements only give string values")
            }
        });
        let oldmode = self.hexmode;
        let onhexchange = self.link.callback(move |_c| {
            // For unknown reasons this always reports Value("on"); making it toggle as a quick
            // workaround (FIXME with better understanding of yew)
            Msg::SetHex(!oldmode)
        });

        let style = if self.hexmode { "font-family:monospace" } else { "" };
        let value = if self.hexmode { hex::encode(&self.props.value) } else { String::from_utf8_lossy(&self.props.value).to_string() };
        let checkbox = html! {
            <label>
                <input type="checkbox" onchange=onhexchange checked=self.hexmode />
                {"  Hex?"}
            </label>
        };

        if self.props.textarea {
            html! { <>
                    <textarea
                        onchange=onchange
                        style=style
                    > { value } </textarea>
                    { checkbox }
                </>
            }
        } else {
            html! { <>
                    <input
                        onchange=onchange
                        style=style
                        value=value
                    />
                    { checkbox }
                </>
            }
        }
    }
}