ft/updates frontend to a presentable state

This commit is contained in:
itsscb 2024-06-14 00:24:43 +02:00
parent 236fed176d
commit 85c13c53a6
10 changed files with 504 additions and 174 deletions

View File

@ -1,6 +1,6 @@
<!doctype html><html lang=en><meta charset=UTF-8><meta content="width=device-width,initial-scale=1" name=viewport><title>wordl</title><link href=/styles-5be5e26778525bb4.css integrity=sha384-BFc0SAke1_1dylfdy26EEHJElxQCLB-DE7o_4TXFLpItCL4On0ZXkSV61ifrEl87 rel=stylesheet><link href=public/manifest.json rel=manifest><link as=fetch crossorigin href=/wordl-frontend-421403a0bf0fc1dd87e39290748fb0fd1d0011ad9d39c436612b9d175949ff30f7f20814f79234fe65bf2a8cf40890a2_bg.wasm integrity=sha384-QhQDoL8Pwd2H45KQdI-w_R0AEa2dOcQ2YSudF1lJ_zD38ggU95I0_mW_Koz0CJCi rel=preload type=application/wasm><link crossorigin href=/wordl-frontend-421403a0bf0fc1dd87e39290748fb0fd1d0011ad9d39c436612b9d175949ff30f7f20814f79234fe65bf2a8cf40890a2.js integrity=sha384-zFzbv1bQho_A-EsUClHqrakXgf-m71aVFaJe-WLfJ7mTNYsbbpvqd3cABILbxMoq rel=modulepreload></head><body class="bg-black text-white"><script type=module> <!doctype html><html lang=en><meta charset=UTF-8><meta content="width=device-width,initial-scale=1" name=viewport><title>wordl</title><link href=/styles-55e4f03e1faef870.css integrity=sha384-J9yM87Qcz-IljugmrvRnK_g3Z2KtgOHIK0ByeH3QR4lXZeBUhTT9c7Bsx6pQQR-j rel=stylesheet><link href=public/manifest.json rel=manifest><link as=fetch crossorigin href=/wordl-frontend-b37792d44628fb3b78168810b71a60fea38c34a958601a1772eb7b1f5529877fd311f47b9d8228bc97b5e72221ddfaba_bg.wasm integrity=sha384-s3eS1EYo-zt4FogQtxpg_qOMNKlYYBoXcut7H1Uph3_TEfR7nYIovJe15yIh3fq6 rel=preload type=application/wasm><link crossorigin href=/wordl-frontend-b37792d44628fb3b78168810b71a60fea38c34a958601a1772eb7b1f5529877fd311f47b9d8228bc97b5e72221ddfaba.js integrity=sha384-tB5yPwFTUKnrprHbsXQf687G5htgOuiCmo_ixsxdmoNFfIcvywWT718KrVIPMEmr rel=modulepreload></head><body class="bg-black text-white"><script type=module>
import init, * as bindings from '/wordl-frontend-421403a0bf0fc1dd87e39290748fb0fd1d0011ad9d39c436612b9d175949ff30f7f20814f79234fe65bf2a8cf40890a2.js'; import init, * as bindings from '/wordl-frontend-b37792d44628fb3b78168810b71a60fea38c34a958601a1772eb7b1f5529877fd311f47b9d8228bc97b5e72221ddfaba.js';
init('/wordl-frontend-421403a0bf0fc1dd87e39290748fb0fd1d0011ad9d39c436612b9d175949ff30f7f20814f79234fe65bf2a8cf40890a2_bg.wasm'); init('/wordl-frontend-b37792d44628fb3b78168810b71a60fea38c34a958601a1772eb7b1f5529877fd311f47b9d8228bc97b5e72221ddfaba_bg.wasm');
window.wasmBindings = bindings; window.wasmBindings = bindings;
</script></body></html> </script></body></html>

View File

@ -544,13 +544,16 @@ video {
--tw-backdrop-sepia: ; --tw-backdrop-sepia: ;
} }
.mx-12 { .mt-24 {
margin-left: 3rem; margin-top: 6rem;
margin-right: 3rem;
} }
.mt-6 { .mt-8 {
margin-top: 1.5rem; margin-top: 2rem;
}
.mt-\[15\%\] {
margin-top: 15%;
} }
.flex { .flex {
@ -561,24 +564,28 @@ video {
height: 4rem; height: 4rem;
} }
.h-full { .h-4\/6 {
height: 100%; height: 66.666667%;
}
.h-screen {
height: 100vh;
} }
.w-12 { .w-12 {
width: 3rem; width: 3rem;
} }
.w-4 { .w-16 {
width: 1rem; width: 4rem;
} }
.w-72 { .w-72 {
width: 18rem; width: 18rem;
} }
.w-full { .flex-1 {
width: 100%; flex: 1 1 0%;
} }
.flex-row { .flex-row {
@ -597,8 +604,16 @@ video {
justify-content: center; justify-content: center;
} }
.justify-between { .justify-items-center {
justify-content: space-between; justify-items: center;
}
.gap-8 {
gap: 2rem;
}
.rounded-xl {
border-radius: 0.75rem;
} }
.border-2 { .border-2 {
@ -630,13 +645,51 @@ video {
background-color: rgb(74 222 128 / var(--tw-bg-opacity)); background-color: rgb(74 222 128 / var(--tw-bg-opacity));
} }
.bg-green-700 {
--tw-bg-opacity: 1;
background-color: rgb(21 128 61 / var(--tw-bg-opacity));
}
.bg-yellow-400 { .bg-yellow-400 {
--tw-bg-opacity: 1; --tw-bg-opacity: 1;
background-color: rgb(250 204 21 / var(--tw-bg-opacity)); background-color: rgb(250 204 21 / var(--tw-bg-opacity));
} }
.p-3 { .object-center {
padding: 0.75rem; -o-object-position: center;
object-position: center;
}
.px-4 {
padding-left: 1rem;
padding-right: 1rem;
}
.py-2 {
padding-top: 0.5rem;
padding-bottom: 0.5rem;
}
.py-4 {
padding-top: 1rem;
padding-bottom: 1rem;
}
.text-center {
text-align: center;
}
.text-2xl {
font-size: 1.5rem;
line-height: 2rem;
}
.font-bold {
font-weight: 700;
}
.leading-tight {
line-height: 1.25;
} }
.text-white { .text-white {

View File

@ -544,13 +544,16 @@ video {
--tw-backdrop-sepia: ; --tw-backdrop-sepia: ;
} }
.mx-12 { .mt-24 {
margin-left: 3rem; margin-top: 6rem;
margin-right: 3rem;
} }
.mt-6 { .mt-8 {
margin-top: 1.5rem; margin-top: 2rem;
}
.mt-\[15\%\] {
margin-top: 15%;
} }
.flex { .flex {
@ -561,24 +564,28 @@ video {
height: 4rem; height: 4rem;
} }
.h-full { .h-4\/6 {
height: 100%; height: 66.666667%;
}
.h-screen {
height: 100vh;
} }
.w-12 { .w-12 {
width: 3rem; width: 3rem;
} }
.w-4 { .w-16 {
width: 1rem; width: 4rem;
} }
.w-72 { .w-72 {
width: 18rem; width: 18rem;
} }
.w-full { .flex-1 {
width: 100%; flex: 1 1 0%;
} }
.flex-row { .flex-row {
@ -597,8 +604,16 @@ video {
justify-content: center; justify-content: center;
} }
.justify-between { .justify-items-center {
justify-content: space-between; justify-items: center;
}
.gap-8 {
gap: 2rem;
}
.rounded-xl {
border-radius: 0.75rem;
} }
.border-2 { .border-2 {
@ -630,13 +645,51 @@ video {
background-color: rgb(74 222 128 / var(--tw-bg-opacity)); background-color: rgb(74 222 128 / var(--tw-bg-opacity));
} }
.bg-green-700 {
--tw-bg-opacity: 1;
background-color: rgb(21 128 61 / var(--tw-bg-opacity));
}
.bg-yellow-400 { .bg-yellow-400 {
--tw-bg-opacity: 1; --tw-bg-opacity: 1;
background-color: rgb(250 204 21 / var(--tw-bg-opacity)); background-color: rgb(250 204 21 / var(--tw-bg-opacity));
} }
.p-3 { .object-center {
padding: 0.75rem; -o-object-position: center;
object-position: center;
}
.px-4 {
padding-left: 1rem;
padding-right: 1rem;
}
.py-2 {
padding-top: 0.5rem;
padding-bottom: 0.5rem;
}
.py-4 {
padding-top: 1rem;
padding-bottom: 1rem;
}
.text-center {
text-align: center;
}
.text-2xl {
font-size: 1.5rem;
line-height: 2rem;
}
.font-bold {
font-weight: 700;
}
.leading-tight {
line-height: 1.25;
} }
.text-white { .text-white {

View File

@ -544,13 +544,16 @@ video {
--tw-backdrop-sepia: ; --tw-backdrop-sepia: ;
} }
.mx-12 { .mt-24 {
margin-left: 3rem; margin-top: 6rem;
margin-right: 3rem;
} }
.mt-6 { .mt-8 {
margin-top: 1.5rem; margin-top: 2rem;
}
.mt-\[15\%\] {
margin-top: 15%;
} }
.flex { .flex {
@ -561,24 +564,28 @@ video {
height: 4rem; height: 4rem;
} }
.h-full { .h-4\/6 {
height: 100%; height: 66.666667%;
}
.h-screen {
height: 100vh;
} }
.w-12 { .w-12 {
width: 3rem; width: 3rem;
} }
.w-4 { .w-16 {
width: 1rem; width: 4rem;
} }
.w-72 { .w-72 {
width: 18rem; width: 18rem;
} }
.w-full { .flex-1 {
width: 100%; flex: 1 1 0%;
} }
.flex-row { .flex-row {
@ -597,8 +604,16 @@ video {
justify-content: center; justify-content: center;
} }
.justify-between { .justify-items-center {
justify-content: space-between; justify-items: center;
}
.gap-8 {
gap: 2rem;
}
.rounded-xl {
border-radius: 0.75rem;
} }
.border-2 { .border-2 {
@ -630,13 +645,51 @@ video {
background-color: rgb(74 222 128 / var(--tw-bg-opacity)); background-color: rgb(74 222 128 / var(--tw-bg-opacity));
} }
.bg-green-700 {
--tw-bg-opacity: 1;
background-color: rgb(21 128 61 / var(--tw-bg-opacity));
}
.bg-yellow-400 { .bg-yellow-400 {
--tw-bg-opacity: 1; --tw-bg-opacity: 1;
background-color: rgb(250 204 21 / var(--tw-bg-opacity)); background-color: rgb(250 204 21 / var(--tw-bg-opacity));
} }
.p-3 { .object-center {
padding: 0.75rem; -o-object-position: center;
object-position: center;
}
.px-4 {
padding-left: 1rem;
padding-right: 1rem;
}
.py-2 {
padding-top: 0.5rem;
padding-bottom: 0.5rem;
}
.py-4 {
padding-top: 1rem;
padding-bottom: 1rem;
}
.text-center {
text-align: center;
}
.text-2xl {
font-size: 1.5rem;
line-height: 2rem;
}
.font-bold {
font-weight: 700;
}
.leading-tight {
line-height: 1.25;
} }
.text-white { .text-white {

79
frontend/src/input.rs Normal file
View File

@ -0,0 +1,79 @@
use web_sys::HtmlInputElement;
use yew::prelude::*;
#[derive(Properties, PartialEq)]
pub struct InputStringProps {
pub value: String,
}
pub enum Msg {
CharInput(usize, String),
}
pub struct InputString {
value: String,
nodes: Vec<NodeRef>,
focused_index: usize,
}
impl Component for InputString {
type Message = Msg;
type Properties = InputStringProps;
fn create(ctx: &Context<Self>) -> Self {
let value = ctx.props().value.clone();
let nodes = vec![NodeRef::default(); value.len()];
Self {
value,
nodes,
focused_index: 0,
}
}
fn update(&mut self, _ctx: &Context<Self>, msg: Self::Message) -> bool {
match msg {
Msg::CharInput(index, new_char) => {
let mut new_value = self.value.clone();
new_value.replace_range(index..index + 1, &new_char);
self.value = new_value;
if index < self.value.len() - 1 {
self.focused_index = index + 1;
if let Some(next_node) = self.nodes.get(self.focused_index) {
if let Some(input) = next_node.cast::<HtmlInputElement>() {
input.focus().unwrap();
}
}
}
true
}
}
}
fn view(&self, ctx: &Context<Self>) -> Html {
let chars = self.value.chars().enumerate().map(|(index, char)| {
let on_input = ctx.link().callback(move |input: InputEvent| {
let new_char = input.data();
Msg::CharInput(index, new_char.unwrap())
});
html! {
<input
type="text"
maxlength=1
value={char.to_string()}
oninput={on_input}
class="w-12 h-16 text-center"
ref={self.nodes.get(index).unwrap().clone()}
style={if index == self.focused_index { "background-color: yellow;" } else { "" }}
/>
}
});
html! {
<div style="display: flex; gap: 0.5rem;">
{ for chars }
</div>
}
}
}

View File

@ -2,6 +2,8 @@ pub mod pages;
pub mod router; pub mod router;
pub mod storage; pub mod storage;
mod input;
use std::collections::HashMap; use std::collections::HashMap;
#[derive(Debug, PartialEq, Eq, Clone)] #[derive(Debug, PartialEq, Eq, Clone)]

View File

@ -1,19 +1,30 @@
use web_sys::wasm_bindgen::convert::OptionIntoWasmAbi;
use yew::prelude::*; use yew::prelude::*;
use yew::{classes, function_component, html, Callback, Html}; use yew::{classes, function_component, html, Callback, Html};
use crate::CharStatus; use crate::CharStatus;
fn string_to_html(input: &Vec<CharStatus<String>>) -> Html { fn string_to_html(input: &Vec<CharStatus<String>>) -> Html {
let classes = classes!("p-3"); let classes = classes!(
"bg-gray-700",
"w-16",
"h-16",
"text-center",
"py-4",
// "justify-center",
// // "justify-items-center",
// "object-center",
// "items-center",
// "leading-tight"
);
html! { html! {
<ul <ul
class={ class={
classes!( classes!(
"flex", "flex",
"flex-row", "flex-row",
"justify-between", "gap-8",
"w-72", "mt-8",
"mx-12"
) )
} }
> >
@ -42,11 +53,20 @@ fn string_to_html(input: &Vec<CharStatus<String>>) -> Html {
}; };
html!{ html!{
<li <li
class={
classes!(
"flex",
"items-center"
)
}
>
<span
class={ class={
classes.clone() classes.clone()
} }
> >
{text} {text}
</span>
</li> </li>
}}).collect::<Html>() }}).collect::<Html>()
} }
@ -58,136 +78,206 @@ fn string_to_html(input: &Vec<CharStatus<String>>) -> Html {
pub fn Home() -> Html { pub fn Home() -> Html {
let got_word = "HALLO"; let got_word = "HALLO";
let submitted_words = yew::use_state(|| vec![]); let submitted_words = yew::use_state(|| vec![]);
let input = yew::use_state(|| {
vec![ let node_refs = use_state(|| vec![NodeRef::default(); 5]);
"".to_owned(), let input_values = use_state(|| vec!["".to_string(); 5]);
"".to_owned(), let game_over = use_state(|| false);
"".to_owned(), let game_over_check = {
"".to_owned(), let submitted_words = submitted_words.clone();
"".to_owned(), let game_over = game_over.clone();
] Callback::from(move |_| {
if submitted_words.iter().count() >= 4 {
game_over.set(true);
}
})
};
got_word.chars().enumerate().for_each(|(_, _)| {
let input_values = input_values.clone();
let mut values = (*input_values).clone();
values.push("".to_string());
let node_refs = node_refs.clone();
let mut values = (*node_refs).clone();
values.push(NodeRef::default());
}); });
let on_submit = { let on_submit = {
let input = input.clone(); let input_values = input_values.clone();
let submitted_words = submitted_words.clone(); let submitted_words = submitted_words.clone();
Callback::from(move |_: MouseEvent| { let game_over = game_over.clone();
let mut new_items = (*submitted_words).clone(); let game_over_check = game_over_check.clone();
new_items.push(crate::compare_strings(&got_word, &input.join("")));
submitted_words.set(new_items);
})
};
let editing_index = yew::use_state(|| None);
let editing_value = yew::use_state(|| String::new());
let on_click = {
let editing_index = editing_index.clone();
let editing_value = editing_value.clone();
let input = input.clone();
Callback::from(move |index: usize| {
editing_index.set(Some(index));
editing_value.set(input.to_vec()[index].clone());
})
};
let on_input = {
let editing_value = editing_value.clone();
Callback::from(move |value: String| {
editing_value.set(value);
})
};
let on_blur = {
let editing_index = editing_index.clone();
let editing_value = editing_value.clone();
let input = input.clone();
Callback::from(move |_| { Callback::from(move |_| {
if let Some(index) = *editing_index { if *game_over {
let mut new_input = input.to_vec(); submitted_words.set(vec![]);
new_input[index] = editing_value.to_uppercase().to_string(); // input_values.set(vec![]);
input.set(new_input); game_over.set(false);
return;
} }
editing_index.set(None); let values: Vec<_> = input_values.iter().map(|value| value.clone()).collect();
if !values.iter().all(|v| !v.is_empty()) {
return;
}
let mut new_items = (*submitted_words).clone();
new_items.push(crate::compare_strings(&got_word, &values.join("")));
submitted_words.set(new_items);
game_over_check.emit(MouseEvent::none());
}) })
}; };
let view = {
let node_refs = node_refs.clone();
let input_values = input_values.clone();
move || {
html! { html! {
<div <div
class={ class={
classes!( classes!(
"w-full",
"flex", "flex",
"flex-col", "flex-col",
"mt-6", "mt-[15%]",
"input-center" // "justify-center",
// "justify-items-center",
"items-center",
"h-screen",
) )
} }
> >
{submitted_words.iter().map(|w| string_to_html(&w)).collect::<Html>()} <div
// <div>{format!("{:?}",res)}</div> class="h-4/6"
// <div >
// class={ <form
// classes!( // onsubmit={on_submit}
// "w-full", >
// "h-16" <div
// )
// }
// >{
// got_word
// }</div>
<ul
class={ class={
classes!( classes!(
"flex", "flex",
"flex-row", "flex-row",
"justify-between", "gap-8",
"w-72",
"h-16",
"mx-12"
) )
} }
> >
{ input.to_vec().iter().enumerate().map(|(index, item)| { { node_refs.iter().enumerate().map(|(index, node_ref)| {
if let Some(editing_idx) = *editing_index { let on_input = {
if editing_idx == index { let node_ref = node_ref.clone();
let input_values = input_values.clone();
Callback::from(move |event: InputEvent| {
let value = event.data().unwrap();
let mut values = (*input_values).clone();
values[index] = value.to_uppercase();
input_values.set(values);
if let Some(input) = node_ref.cast::<web_sys::HtmlInputElement>() {
input.value();
}
})
};
html! { html! {
<li <div
class="w-12 bg-gray-600 flex items-center justify-center" class="flex gap-8"
> >
<input <input
class="bg-gray-600 h-full w-full" ref={node_ref.clone()}
type="text" value={input_values[index].clone()}
value={editing_value.to_string()} oninput={on_input}
onblur={on_blur.clone()} class={
oninput={on_input.reform(|e: InputEvent| e.data().unwrap_or_default())} classes!(
"w-16",
"h-16",
"flex-1",
"text-center",
// "px-4",
// "py-2",
"bg-gray-600"
)
}
/> />
</li> </div>
}
} else {
html! { <li
class="w-4 bg-gray-600"
>{item}</li> }
}
} else {
html! { <li
class="w-12 bg-gray-600"
onclick={on_click.reform(move |_| index)}>{item}</li> }
} }
}).collect::<Html>() } }).collect::<Html>() }
</ul> </div>
// <input </form>
// value={got_word} { for submitted_words.iter().map(|w| string_to_html(w))}
// type="text" </div>
// class={ <button
// classes!( class={
// "w-full", classes!(
// "h-16" "w-72",
// ) "h-16",
// } // "mt-24",
// /> "text-2xl",
<button onclick={on_submit}>{"Submit"}</button> "font-bold",
"rounded-xl",
"bg-green-700",
)
}
onclick={on_submit} type="submit">
{
if *game_over {
"Play again"
}
else {
"Submit"
}
}
</button>
</div> </div>
} }
} }
};
view()
// html! {
// <div
// class={
// classes!(
// "mt-[15%]",
// "flex",
// "flex-col",
// "justify-center",
// "items-center"
// )
// }
// >
// <div
// class={
// classes!(
// "flex",
// "flex-row",
// "gap-8"
// )
// }
// >
// {view()}
// { for input.iter().map(|i| {
// html!{
// <input
// class={
// classes!(
// "w-16",
// "h-16",
// "bg-gray-600"
// )
// }
// value={<std::string::String as Clone>::clone(&*i)}
// />
// }
// })}
// </div>
// <InputString value={" ".to_string()}/>
// <button
// class={
// classes!(
// "w-72",
// "h-16",
// "mt-24",
// "text-2xl",
// "font-bold",
// "rounded-xl",
// "bg-green-700",
// )
// }
// onclick={on_submit}>{"Submit"}</button>
// </div>
// }
}