initial commit: rewrote in (embarassingly bad) wasm

This commit is contained in:
Emilis 2025-04-03 18:02:13 +01:00
commit 772c15bcc1
No known key found for this signature in database
6 changed files with 1641 additions and 0 deletions

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
/dist/
/target/

1244
Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

16
Cargo.toml Normal file
View File

@ -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"] }

14
index.html Normal file
View File

@ -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>

142
index.scss Normal file
View File

@ -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;
}

223
src/main.rs Normal file
View File

@ -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();
}