-
Notifications
You must be signed in to change notification settings - Fork 6
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #10 from TheBestTvarynka/byte-input-component
- Loading branch information
Showing
15 changed files
with
278 additions
and
175 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,107 @@ | ||
use time::Duration; | ||
use web_sys::HtmlInputElement; | ||
use yew::{classes, function_component, html, use_effect_with_deps, use_state, Callback, Html, Properties, TargetCast}; | ||
use yew_notifications::{use_notification, Notification, NotificationType}; | ||
|
||
use super::BytesFormat; | ||
use crate::common::{encode_bytes, get_format_button_class, get_set_format_callback, parse_bytes, BYTES_FORMATS}; | ||
|
||
#[derive(PartialEq, Properties, Clone)] | ||
pub struct ByteInputProps { | ||
#[prop_or(BytesFormat::Hex)] | ||
format: BytesFormat, | ||
#[prop_or_default] | ||
placeholder: String, | ||
bytes: Vec<u8>, | ||
setter: Callback<Vec<u8>>, | ||
} | ||
|
||
#[function_component(ByteInput)] | ||
pub fn byte_input(props: &ByteInputProps) -> Html { | ||
let ByteInputProps { | ||
format, | ||
bytes, | ||
setter, | ||
placeholder, | ||
} = &props; | ||
|
||
let raw_value = use_state(|| encode_bytes(bytes, *format)); | ||
let bytes = use_state(|| bytes.clone()); | ||
let bytes_format = use_state(|| *format); | ||
|
||
let format_setter = bytes_format.setter(); | ||
let raw_value_setter = raw_value.setter(); | ||
let parsed_bytes = (*bytes).clone(); | ||
use_effect_with_deps( | ||
move |format| { | ||
format_setter.set(**format); | ||
raw_value_setter.set(encode_bytes(parsed_bytes, **format)); | ||
}, | ||
bytes_format.clone(), | ||
); | ||
|
||
let bytes_setter = bytes.setter(); | ||
use_effect_with_deps( | ||
move |props| { | ||
bytes_setter.set(props.bytes.clone()); | ||
}, | ||
props.clone(), | ||
); | ||
|
||
let setter = setter.clone(); | ||
let raw_value_setter = raw_value.setter(); | ||
let notifications = use_notification::<Notification>(); | ||
let format = *bytes_format; | ||
let oninput = Callback::from(move |event: html::oninput::Event| { | ||
let input: HtmlInputElement = event.target_unchecked_into(); | ||
let value = input.value(); | ||
|
||
match parse_bytes(&value, format) { | ||
Ok(bytes) => setter.emit(bytes), | ||
Err(error) => notifications.spawn(Notification::new( | ||
NotificationType::Error, | ||
"Can not parse input", | ||
error, | ||
Duration::seconds(1), | ||
)), | ||
} | ||
|
||
raw_value_setter.set(value); | ||
}); | ||
|
||
html! { | ||
<div class={classes!("bytes-input", "vertical")}> | ||
<div class={classes!("formats-container")}>{ | ||
BYTES_FORMATS.iter().map(|format| { | ||
html! { | ||
<button | ||
class={get_format_button_class(*bytes_format == *format)} | ||
onclick={get_set_format_callback(*format, bytes_format.setter())} | ||
> | ||
{<&str>::from(format)} | ||
</button> | ||
} | ||
}).collect::<Html>() | ||
}</div> | ||
<textarea | ||
rows="2" | ||
placeholder={format!("{}: place {} encoded input here", placeholder, (*bytes_format).as_ref())} | ||
class={classes!("base-input")} | ||
value={(*raw_value).clone()} | ||
{oninput} | ||
/> | ||
<span class={classes!("total")}>{format!("total: {}", (*bytes).len())}</span> | ||
</div> | ||
} | ||
} | ||
|
||
pub fn build_byte_input( | ||
bytes: Vec<u8>, | ||
setter: Callback<Vec<u8>>, | ||
format: Option<BytesFormat>, | ||
placeholder: Option<String>, | ||
) -> Html { | ||
html! { | ||
<ByteInput {bytes} {setter} format={format.unwrap_or_default()} placeholder={placeholder.unwrap_or_default()} /> | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,9 +1,71 @@ | ||
mod byte_input; | ||
mod bytes_viewer; | ||
mod simple_input; | ||
mod simple_output; | ||
mod switch; | ||
|
||
pub use byte_input::{build_byte_input, ByteInput, ByteInputProps}; | ||
pub use bytes_viewer::{BytesViewer, BytesViewerProps}; | ||
pub use simple_input::{build_simple_input, SimpleInput, SimpleInputProps}; | ||
pub use simple_output::{build_simple_output, BytesFormat}; | ||
pub use simple_output::build_simple_output; | ||
pub use switch::{Switch, SwitchProps}; | ||
use web_sys::MouseEvent; | ||
use yew::{classes, Callback, Classes, UseStateSetter}; | ||
|
||
#[derive(PartialEq, Eq, Clone, Copy, Default)] | ||
pub enum BytesFormat { | ||
#[default] | ||
Hex, | ||
Base64, | ||
Ascii, | ||
} | ||
|
||
impl AsRef<str> for BytesFormat { | ||
fn as_ref(&self) -> &str { | ||
match self { | ||
BytesFormat::Hex => "hex", | ||
BytesFormat::Base64 => "base64", | ||
BytesFormat::Ascii => "ascii", | ||
} | ||
} | ||
} | ||
|
||
impl From<&BytesFormat> for &str { | ||
fn from(format: &BytesFormat) -> Self { | ||
match format { | ||
BytesFormat::Hex => "hex", | ||
BytesFormat::Base64 => "base64", | ||
BytesFormat::Ascii => "ascii", | ||
} | ||
} | ||
} | ||
|
||
pub const BYTES_FORMATS: [BytesFormat; 3] = [BytesFormat::Hex, BytesFormat::Base64, BytesFormat::Ascii]; | ||
|
||
fn encode_bytes(bytes: impl AsRef<[u8]>, format: BytesFormat) -> String { | ||
match format { | ||
BytesFormat::Hex => hex::encode(bytes), | ||
BytesFormat::Base64 => base64::encode(bytes), | ||
BytesFormat::Ascii => bytes.as_ref().iter().map(|c| *c as char).collect(), | ||
} | ||
} | ||
|
||
fn parse_bytes(raw: &str, format: BytesFormat) -> Result<Vec<u8>, String> { | ||
match format { | ||
BytesFormat::Hex => hex::decode(raw).map_err(|err| format!("invalid hex input:{:?}", err)), | ||
BytesFormat::Base64 => base64::decode(raw).map_err(|err| format!("invalid base64 input:{:?}", err)), | ||
BytesFormat::Ascii => Ok(raw.into()), | ||
} | ||
} | ||
|
||
fn get_format_button_class(selected: bool) -> Classes { | ||
if selected { | ||
classes!("format-button", "format-button-selected") | ||
} else { | ||
classes!("format-button") | ||
} | ||
} | ||
|
||
fn get_set_format_callback(format: BytesFormat, set_format: UseStateSetter<BytesFormat>) -> Callback<MouseEvent> { | ||
Callback::from(move |_event| { | ||
set_format.set(format); | ||
}) | ||
} |
Oops, something went wrong.