initial commit: rewrote in (embarassingly bad) wasm
This commit is contained in:
commit
772c15bcc1
|
@ -0,0 +1,2 @@
|
||||||
|
/dist/
|
||||||
|
/target/
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,16 @@
|
||||||
|
[package]
|
||||||
|
name = "bits-rs"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2024"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
[dependencies]
|
||||||
|
log = "0.4.26"
|
||||||
|
seq-macro = "0.3.6"
|
||||||
|
wasm-logger = "0.2.0"
|
||||||
|
web-sys = { version = "0.3", features = [
|
||||||
|
"HtmlTableCellElement",
|
||||||
|
"Event",
|
||||||
|
"EventTarget",
|
||||||
|
] }
|
||||||
|
yew = { version = "0.21", features = ["csr"] }
|
|
@ -0,0 +1,14 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<title>bits n bits</title>
|
||||||
|
<link data-trunk rel="sass" href="index.scss" />
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
|
@ -0,0 +1,142 @@
|
||||||
|
body {
|
||||||
|
background: linear-gradient(to bottom right, bisque, hsl(24, 57%, 70%));
|
||||||
|
height: 100vw;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#bytes_in {
|
||||||
|
text-align: center;
|
||||||
|
border: 0px;
|
||||||
|
font-size: 56px;
|
||||||
|
font-family: 'Cute Font';
|
||||||
|
filter: sepia(100%);
|
||||||
|
border-radius: 10px;
|
||||||
|
background-color: #eeeeee;
|
||||||
|
}
|
||||||
|
|
||||||
|
#input_box {
|
||||||
|
filter:
|
||||||
|
// drop-shadow(5px 5px red)
|
||||||
|
sepia(100%) // drop-shadow(-5px -5px hsl(24, 27%, 40%))
|
||||||
|
drop-shadow(10px 10px 10px 10px hsl(24, 27%, 40%));
|
||||||
|
height: 150px;
|
||||||
|
display: flex;
|
||||||
|
width: 100%;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bit {
|
||||||
|
color: blue;
|
||||||
|
font-weight: 200;
|
||||||
|
font-size: 80px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
th {
|
||||||
|
background-color: bisque;
|
||||||
|
}
|
||||||
|
|
||||||
|
th:nth-child(odd) {
|
||||||
|
background-color: burlywood;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#bit_wrap table:nth-of-type(even) {
|
||||||
|
margin-left: 5px;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
margin-right: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#bit_wrap table:nth-of-type(odd) {
|
||||||
|
margin-left: 0px;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
margin-right: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
table {
|
||||||
|
width: 45%;
|
||||||
|
}
|
||||||
|
|
||||||
|
#activated_ext {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
flex-basis: content;
|
||||||
|
justify-content: center;
|
||||||
|
-moz-user-select: none;
|
||||||
|
-webkit-user-select: none;
|
||||||
|
-ms-user-select: none;
|
||||||
|
user-select: none;
|
||||||
|
-o-user-select: none;
|
||||||
|
text-align: center;
|
||||||
|
border: 0px;
|
||||||
|
font-size: 36px;
|
||||||
|
font-family: 'Cute Font';
|
||||||
|
|
||||||
|
background: -webkit-linear-gradient(hsl(24, 27%, 40%), hsl(24, 27%, 50%));
|
||||||
|
background-clip: border-box;
|
||||||
|
-webkit-background-clip: text;
|
||||||
|
-webkit-text-fill-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
#activated_wrap {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
flex-basis: content;
|
||||||
|
justify-content: center;
|
||||||
|
-moz-user-select: none;
|
||||||
|
-webkit-user-select: none;
|
||||||
|
-ms-user-select: none;
|
||||||
|
user-select: none;
|
||||||
|
-o-user-select: none;
|
||||||
|
text-align: center;
|
||||||
|
border: 0px;
|
||||||
|
font-size: 36px;
|
||||||
|
font-family: 'Cute Font';
|
||||||
|
|
||||||
|
background: -webkit-linear-gradient(hsl(24, 27%, 40%), hsl(24, 27%, 50%));
|
||||||
|
background-clip: border-box;
|
||||||
|
-webkit-background-clip: text;
|
||||||
|
-webkit-text-fill-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#bit_wrap {
|
||||||
|
filter: drop-shadow(3px 3px red) sepia(100%) drop-shadow(-3px -3px hsl(24, 27%, 40%));
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
flex-basis: content;
|
||||||
|
justify-content: center;
|
||||||
|
-moz-user-select: none;
|
||||||
|
-webkit-user-select: none;
|
||||||
|
-ms-user-select: none;
|
||||||
|
user-select: none;
|
||||||
|
-o-user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
#activated {
|
||||||
|
text-wrap: balance;
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 64px;
|
||||||
|
color: grey;
|
||||||
|
word-wrap: break-word;
|
||||||
|
|
||||||
|
text-align: center;
|
||||||
|
/* width: 90vw; */
|
||||||
|
}
|
||||||
|
|
||||||
|
#activated_text {
|
||||||
|
font-weight: bold;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 32px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bit:hover {
|
||||||
|
filter: brightness(85%) saturate(150%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.header {
|
||||||
|
font-size: x-large;
|
||||||
|
}
|
|
@ -0,0 +1,223 @@
|
||||||
|
#![allow(clippy::expect_fun_call)]
|
||||||
|
use core::ops::Range;
|
||||||
|
|
||||||
|
use web_sys::{HtmlInputElement, HtmlTableCellElement, wasm_bindgen::JsCast};
|
||||||
|
use yew::prelude::*;
|
||||||
|
|
||||||
|
struct BitsTable {
|
||||||
|
bits: Html,
|
||||||
|
value_box: Html,
|
||||||
|
}
|
||||||
|
|
||||||
|
const HEX: &[char] = &[
|
||||||
|
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f', 'A', 'B', 'C',
|
||||||
|
'D', 'E', 'F', 'x',
|
||||||
|
];
|
||||||
|
|
||||||
|
const ALLOWED_CHARS: &[char] = &[
|
||||||
|
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f', 'A', 'B', 'C',
|
||||||
|
'D', 'E', 'F', 'x', '_',
|
||||||
|
];
|
||||||
|
|
||||||
|
fn value_to_u32(value: &str) -> u32 {
|
||||||
|
let value = value.trim();
|
||||||
|
if let Some(value) = value.strip_prefix("0x") {
|
||||||
|
u32::from_str_radix(value.trim(), 16)
|
||||||
|
} else if let Some(value) = value.strip_prefix("0b") {
|
||||||
|
u32::from_str_radix(value.trim(), 2)
|
||||||
|
} else if let Some(value) = value.strip_prefix("0o") {
|
||||||
|
u32::from_str_radix(value.trim(), 8)
|
||||||
|
} else {
|
||||||
|
value.parse()
|
||||||
|
}
|
||||||
|
.unwrap_or_default()
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Component for BitsTable {
|
||||||
|
type Message = ();
|
||||||
|
|
||||||
|
type Properties = ();
|
||||||
|
|
||||||
|
fn create(_ctx: &Context<Self>) -> Self {
|
||||||
|
let bit_click = |bit: u8| {
|
||||||
|
move |ev: MouseEvent| {
|
||||||
|
let input = web_sys::window()
|
||||||
|
.unwrap()
|
||||||
|
.document()
|
||||||
|
.unwrap()
|
||||||
|
.get_element_by_id("bytes_in")
|
||||||
|
.unwrap()
|
||||||
|
.dyn_into::<HtmlInputElement>()
|
||||||
|
.unwrap();
|
||||||
|
let mut value = value_to_u32(&input.value());
|
||||||
|
if let Some(target) = ev
|
||||||
|
.target()
|
||||||
|
.and_then(|t| t.dyn_into::<HtmlTableCellElement>().ok())
|
||||||
|
{
|
||||||
|
match target.inner_text().as_str() {
|
||||||
|
"0" => {
|
||||||
|
target.set_inner_text("1");
|
||||||
|
value |= 1 << bit;
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
target.set_inner_text("0");
|
||||||
|
value &= !(1 << bit);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
input.set_value(format!("{value:#X}").as_str());
|
||||||
|
|
||||||
|
input
|
||||||
|
.dispatch_event(&InputEvent::new("input").unwrap())
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let bits = (0..4usize)
|
||||||
|
.rev()
|
||||||
|
.map(|idx| {
|
||||||
|
let headers = (idx * 8..idx * 8 + 8)
|
||||||
|
.rev()
|
||||||
|
.map(|idx| {
|
||||||
|
html! {
|
||||||
|
<th>{idx}</th>
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect::<Html>();
|
||||||
|
let values = (idx * 8..idx * 8 + 8)
|
||||||
|
.rev()
|
||||||
|
.map(|idx| html! {
|
||||||
|
<th class="bit" id={format!("bit{idx}")} onclick={bit_click(idx as _)}>{0}</th>
|
||||||
|
})
|
||||||
|
.collect::<Html>();
|
||||||
|
|
||||||
|
html! {
|
||||||
|
<table>
|
||||||
|
<tr class="header">
|
||||||
|
{headers}
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
{values}
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect::<Html>();
|
||||||
|
|
||||||
|
let on_input = move |ev: InputEvent| {
|
||||||
|
let input_element = ev
|
||||||
|
.target()
|
||||||
|
.and_then(|t| t.dyn_into::<HtmlInputElement>().ok())
|
||||||
|
.expect("on_input should have a target");
|
||||||
|
let text = input_element.value().matches(HEX).collect::<String>();
|
||||||
|
input_element.set_value(
|
||||||
|
&input_element
|
||||||
|
.value()
|
||||||
|
.matches(ALLOWED_CHARS)
|
||||||
|
.collect::<String>(),
|
||||||
|
);
|
||||||
|
let mut num: u32 = match text.strip_prefix("0x") {
|
||||||
|
Some(hex) => u32::from_str_radix(hex, 16).unwrap_or_default(),
|
||||||
|
None => text.parse().unwrap_or_default(),
|
||||||
|
};
|
||||||
|
let doc = web_sys::window().unwrap().document().unwrap();
|
||||||
|
if num == 0 {
|
||||||
|
doc.get_element_by_id("activated_wrap")
|
||||||
|
.unwrap()
|
||||||
|
.set_attribute("style", "visibility: hidden;")
|
||||||
|
.unwrap();
|
||||||
|
} else {
|
||||||
|
doc.get_element_by_id("activated_wrap")
|
||||||
|
.unwrap()
|
||||||
|
.set_attribute("style", "")
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
let mut act: Vec<Range<u8>> = vec![];
|
||||||
|
for idx in 0..32u8 {
|
||||||
|
let cell = doc
|
||||||
|
.get_element_by_id(format!("bit{idx}").as_str())
|
||||||
|
.expect("missing bit")
|
||||||
|
.dyn_into::<HtmlTableCellElement>()
|
||||||
|
.expect(format!("bit{idx} should be a table cell").as_str());
|
||||||
|
let set = match num & 1 {
|
||||||
|
0 => false,
|
||||||
|
1 => true,
|
||||||
|
_ => unreachable!(),
|
||||||
|
};
|
||||||
|
if set {
|
||||||
|
match act.iter_mut().last() {
|
||||||
|
Some(last) => {
|
||||||
|
if last.end + 1 == idx {
|
||||||
|
last.end += 1;
|
||||||
|
} else {
|
||||||
|
act.push(idx..idx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => act.push(idx..idx),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cell.set_inner_text(match set {
|
||||||
|
false => "0",
|
||||||
|
true => "1",
|
||||||
|
});
|
||||||
|
num >>= 1;
|
||||||
|
}
|
||||||
|
if act.is_empty() {
|
||||||
|
doc.get_element_by_id("activated")
|
||||||
|
.unwrap()
|
||||||
|
.set_text_content(None);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let act_str = act
|
||||||
|
.into_iter()
|
||||||
|
.map(|act| {
|
||||||
|
if act.is_empty() {
|
||||||
|
act.start.to_string()
|
||||||
|
} else {
|
||||||
|
format!("{act:?}")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.join(", ");
|
||||||
|
|
||||||
|
doc.get_element_by_id("activated")
|
||||||
|
.unwrap()
|
||||||
|
.set_text_content(Some(&act_str));
|
||||||
|
};
|
||||||
|
|
||||||
|
let value_box = html! {
|
||||||
|
<div id="input_box">
|
||||||
|
<input id="bytes_in" oninput={on_input} />
|
||||||
|
</div>
|
||||||
|
};
|
||||||
|
Self { bits, value_box }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn view(&self, _ctx: &Context<Self>) -> Html {
|
||||||
|
let bits = self.bits.clone();
|
||||||
|
let value_box = self.value_box.clone();
|
||||||
|
|
||||||
|
html! {
|
||||||
|
<>
|
||||||
|
{value_box}
|
||||||
|
<br />
|
||||||
|
<div id="bit_wrap">
|
||||||
|
{bits}
|
||||||
|
</div>
|
||||||
|
<div id="activated_ext">
|
||||||
|
<h2>{"activated"}</h2>
|
||||||
|
</div>
|
||||||
|
<div id="activated_wrap">
|
||||||
|
<p id="activated"></p>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
wasm_logger::init(wasm_logger::Config::default());
|
||||||
|
|
||||||
|
yew::Renderer::<BitsTable>::new().render();
|
||||||
|
}
|
Loading…
Reference in New Issue