verdigris 0.1.1

Browser application to explore, learn and debug CoAP
Documentation
//! A YEW editor for CoAP messages
//!
//! This is not expressed in a component as that'd mean cloning around a message, and it stands to
//! reason that in a generic CoAP tool context there may be multiple ways of editing the same
//! being-prepared message (eg. set from template, set URI) that are external to the editor and
//! will want to modify the same message.

use coap_message::heapmessage::HeapMessage;

use coap_message::{MinimalWritableMessage, ReadableMessage, MessageOption};
use crate::binentry;

#[derive(Debug)]
pub enum EditMsg {
    ChangeCode(u8),
    ChangePayload(Vec<u8>),
    ChangeOpt(u16, usize, Vec<u8>),
    RemoveOpt(u16, usize),

    AddOptSet(u16),
    AddOptDo,
}

/// State that the user of a message editor needs to carry in addition to a message
pub struct EditorState {
    /// Message to be added
    ///
    /// In a component, this could be a non-property member
    add_coapopt: u16,
}

impl Default for EditorState {
    fn default() -> Self { Self { add_coapopt: 4 } }
}

/// Apply a message generated by the editor to a mutable shared message.
///
/// This may panic (FIXME: should be "may err silently") if the message is modified between
/// generation of the callbacks (rendering) and the arrival of the EditMsg.
///
/// This works on a HeapMessage and not a generic WritableMessage as a WritableMessage has no
/// API for late manipulation of options
pub fn apply(change: EditMsg, coapmessage: &mut HeapMessage, editorstate: &mut EditorState) {
    match change {
        EditMsg::ChangeCode(c) => {
            coapmessage.set_code(c);
        }
        EditMsg::ChangePayload(p) => {
            coapmessage.set_payload(&p);
        }
        EditMsg::ChangeOpt(optnum, index, value) => {
            coapmessage.change_option(optnum, index, value);
        }
        EditMsg::RemoveOpt(optnum, index) => {
            coapmessage.remove_option(optnum, index);
        }
        EditMsg::AddOptDo => {
            coapmessage.add_option(editorstate.add_coapopt, &[]);
        }
        EditMsg::AddOptSet(o) => {
            editorstate.add_coapopt = o;
        }
    }
}

pub fn render<COMP: yew::Component>(coapmessage: &HeapMessage, editorstate: &EditorState, link: &yew::html::Scope<COMP>, wrap: impl 'static + Copy + Fn(EditMsg) -> COMP::Message) -> yew::virtual_dom::vnode::VNode {
    use yew::prelude::*;

    html!{<>
        <p><label> {"Code:"}
            <input
                type="number"
                value=coapmessage.code()
                onchange=link.callback(move |v| wrap(EditMsg::ChangeCode(match v {
                    yew::events::ChangeData::Value(s) => s.parse().expect("Numeric input is numeric"),
                    _ => unreachable!("Numeric input has value"),
                })))
                />
            { " " } { coap_numbers::code::to_dotted(coapmessage.code()) }
            { " " } { coap_numbers::code::to_name(coapmessage.code()).unwrap_or("unknown") } </label></p>
        <ul> {
            for coapmessage.options()
            .map({
                let mut occurrence = 0;
                let mut last_opt = 0;
                move |opt| {
                    if opt.number() != last_opt {
                        last_opt = opt.number();
                        occurrence = 0;
                    }
                    let ret = (occurrence, opt);
                    occurrence += 1;
                    ret
                }
            })
            .map(|(i, opt)| { let optnum = opt.number(); html!{
                <li> { format!("{} #{}: {} ", optnum, i, coap_numbers::option::to_name(optnum).unwrap_or("unknown")) }
                <binentry::BinaryEntry
                onchange=link.callback(move |v| wrap(EditMsg::ChangeOpt(optnum, i, v)))
                value=opt.value().to_vec()
                />
                <button onclick=link.callback(move |_| wrap(EditMsg::RemoveOpt(optnum, i)))> { "x" } </button>
                </li>
            }})}
            <li>
                { "New: " }
                <input
                    type="number"
                    value=editorstate.add_coapopt
                    onchange=link.callback(move |v| wrap(EditMsg::AddOptSet(match v {
                        yew::events::ChangeData::Value(s) => s.parse().expect("Numeric input is numeric"),
                        _ => unreachable!("Numeric input has value"),
                    })))
                    />
                    { coap_numbers::option::to_name(editorstate.add_coapopt).unwrap_or("") }
                <button onclick=link.callback(move |_| wrap(EditMsg::AddOptDo))> { "+" } </button>
            </li>
        </ul>
        <p><label> {"Payload:"} <br /><binentry::BinaryEntry
            onchange=link.callback(move |v| wrap(EditMsg::ChangePayload(v)))
            value=coapmessage.payload().to_vec()
            textarea=true
            /></label></p>
    </>}
}