Skip to content

Commit

Permalink
Merge pull request #10 from TheBestTvarynka/byte-input-component
Browse files Browse the repository at this point in the history
  • Loading branch information
TheBestTvarynka authored Apr 8, 2023
2 parents 596324f + f63103b commit 94e7ef5
Show file tree
Hide file tree
Showing 15 changed files with 278 additions and 175 deletions.
61 changes: 31 additions & 30 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ rand = { version = "0.8.5", default-features = false, features = ["small_rng"] }
rand_chacha = "0.3.1"
serde_json = "1.0.89"
base64 = "0.13.1"
time = { version = "0.3.20", features = ["local-offset", "wasm-bindgen"] }

# crypto
picky-krb = { git = "https://github.com/TheBestTravynka/picky-rs.git", rev = "604a246" }
Expand Down
11 changes: 11 additions & 0 deletions public/styles/input.scss
Original file line number Diff line number Diff line change
Expand Up @@ -103,3 +103,14 @@
color: #e0e0e0;
background-color: #736664;
}

.bytes-input {
width: 100%;
background: transparent;
border-radius: 0.2em;
display: flex;
flex-direction: column;
justify-content: flex-start;
align-items: flex-start;
flex-wrap: wrap;
}
107 changes: 107 additions & 0 deletions src/common/byte_input.rs
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()} />
}
}
68 changes: 65 additions & 3 deletions src/common/mod.rs
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);
})
}
Loading

0 comments on commit 94e7ef5

Please sign in to comment.