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
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 }
</>
}
}
}
}