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>
import init, * as bindings from '/wordl-frontend-421403a0bf0fc1dd87e39290748fb0fd1d0011ad9d39c436612b9d175949ff30f7f20814f79234fe65bf2a8cf40890a2.js';
init('/wordl-frontend-421403a0bf0fc1dd87e39290748fb0fd1d0011ad9d39c436612b9d175949ff30f7f20814f79234fe65bf2a8cf40890a2_bg.wasm');
<!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-b37792d44628fb3b78168810b71a60fea38c34a958601a1772eb7b1f5529877fd311f47b9d8228bc97b5e72221ddfaba.js';
init('/wordl-frontend-b37792d44628fb3b78168810b71a60fea38c34a958601a1772eb7b1f5529877fd311f47b9d8228bc97b5e72221ddfaba_bg.wasm');
window.wasmBindings = bindings;
</script></body></html>

View File

@ -544,13 +544,16 @@ video {
--tw-backdrop-sepia: ;
}
.mx-12 {
margin-left: 3rem;
margin-right: 3rem;
.mt-24 {
margin-top: 6rem;
}
.mt-6 {
margin-top: 1.5rem;
.mt-8 {
margin-top: 2rem;
}
.mt-\[15\%\] {
margin-top: 15%;
}
.flex {
@ -561,24 +564,28 @@ video {
height: 4rem;
}
.h-full {
height: 100%;
.h-4\/6 {
height: 66.666667%;
}
.h-screen {
height: 100vh;
}
.w-12 {
width: 3rem;
}
.w-4 {
width: 1rem;
.w-16 {
width: 4rem;
}
.w-72 {
width: 18rem;
}
.w-full {
width: 100%;
.flex-1 {
flex: 1 1 0%;
}
.flex-row {
@ -597,8 +604,16 @@ video {
justify-content: center;
}
.justify-between {
justify-content: space-between;
.justify-items-center {
justify-items: center;
}
.gap-8 {
gap: 2rem;
}
.rounded-xl {
border-radius: 0.75rem;
}
.border-2 {
@ -630,13 +645,51 @@ video {
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 {
--tw-bg-opacity: 1;
background-color: rgb(250 204 21 / var(--tw-bg-opacity));
}
.p-3 {
padding: 0.75rem;
.object-center {
-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 {

View File

@ -544,13 +544,16 @@ video {
--tw-backdrop-sepia: ;
}
.mx-12 {
margin-left: 3rem;
margin-right: 3rem;
.mt-24 {
margin-top: 6rem;
}
.mt-6 {
margin-top: 1.5rem;
.mt-8 {
margin-top: 2rem;
}
.mt-\[15\%\] {
margin-top: 15%;
}
.flex {
@ -561,24 +564,28 @@ video {
height: 4rem;
}
.h-full {
height: 100%;
.h-4\/6 {
height: 66.666667%;
}
.h-screen {
height: 100vh;
}
.w-12 {
width: 3rem;
}
.w-4 {
width: 1rem;
.w-16 {
width: 4rem;
}
.w-72 {
width: 18rem;
}
.w-full {
width: 100%;
.flex-1 {
flex: 1 1 0%;
}
.flex-row {
@ -597,8 +604,16 @@ video {
justify-content: center;
}
.justify-between {
justify-content: space-between;
.justify-items-center {
justify-items: center;
}
.gap-8 {
gap: 2rem;
}
.rounded-xl {
border-radius: 0.75rem;
}
.border-2 {
@ -630,13 +645,51 @@ video {
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 {
--tw-bg-opacity: 1;
background-color: rgb(250 204 21 / var(--tw-bg-opacity));
}
.p-3 {
padding: 0.75rem;
.object-center {
-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 {

View File

@ -544,13 +544,16 @@ video {
--tw-backdrop-sepia: ;
}
.mx-12 {
margin-left: 3rem;
margin-right: 3rem;
.mt-24 {
margin-top: 6rem;
}
.mt-6 {
margin-top: 1.5rem;
.mt-8 {
margin-top: 2rem;
}
.mt-\[15\%\] {
margin-top: 15%;
}
.flex {
@ -561,24 +564,28 @@ video {
height: 4rem;
}
.h-full {
height: 100%;
.h-4\/6 {
height: 66.666667%;
}
.h-screen {
height: 100vh;
}
.w-12 {
width: 3rem;
}
.w-4 {
width: 1rem;
.w-16 {
width: 4rem;
}
.w-72 {
width: 18rem;
}
.w-full {
width: 100%;
.flex-1 {
flex: 1 1 0%;
}
.flex-row {
@ -597,8 +604,16 @@ video {
justify-content: center;
}
.justify-between {
justify-content: space-between;
.justify-items-center {
justify-items: center;
}
.gap-8 {
gap: 2rem;
}
.rounded-xl {
border-radius: 0.75rem;
}
.border-2 {
@ -630,13 +645,51 @@ video {
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 {
--tw-bg-opacity: 1;
background-color: rgb(250 204 21 / var(--tw-bg-opacity));
}
.p-3 {
padding: 0.75rem;
.object-center {
-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 {

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 storage;
mod input;
use std::collections::HashMap;
#[derive(Debug, PartialEq, Eq, Clone)]

View File

@ -1,19 +1,30 @@
use web_sys::wasm_bindgen::convert::OptionIntoWasmAbi;
use yew::prelude::*;
use yew::{classes, function_component, html, Callback, Html};
use crate::CharStatus;
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! {
<ul
class={
classes!(
"flex",
"flex-row",
"justify-between",
"w-72",
"mx-12"
"gap-8",
"mt-8",
)
}
>
@ -41,12 +52,21 @@ fn string_to_html(input: &Vec<CharStatus<String>>) -> Html {
},
};
html!{
<li
<li
class={
classes!(
"flex",
"items-center"
)
}
>
<span
class={
classes.clone()
}
>
{text}
</span>
</li>
}}).collect::<Html>()
}
@ -58,136 +78,206 @@ fn string_to_html(input: &Vec<CharStatus<String>>) -> Html {
pub fn Home() -> Html {
let got_word = "HALLO";
let submitted_words = yew::use_state(|| vec![]);
let input = yew::use_state(|| {
vec![
"".to_owned(),
"".to_owned(),
"".to_owned(),
"".to_owned(),
"".to_owned(),
]
let node_refs = use_state(|| vec![NodeRef::default(); 5]);
let input_values = use_state(|| vec!["".to_string(); 5]);
let game_over = use_state(|| false);
let game_over_check = {
let submitted_words = submitted_words.clone();
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 input = input.clone();
let input_values = input_values.clone();
let submitted_words = submitted_words.clone();
Callback::from(move |_: MouseEvent| {
let mut new_items = (*submitted_words).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();
let game_over = game_over.clone();
let game_over_check = game_over_check.clone();
Callback::from(move |_| {
if let Some(index) = *editing_index {
let mut new_input = input.to_vec();
new_input[index] = editing_value.to_uppercase().to_string();
input.set(new_input);
if *game_over {
submitted_words.set(vec![]);
// input_values.set(vec![]);
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());
})
};
html! {
<div
let view = {
let node_refs = node_refs.clone();
let input_values = input_values.clone();
move || {
html! {
<div
class={
classes!(
"flex",
"flex-col",
"mt-[15%]",
// "justify-center",
// "justify-items-center",
"items-center",
"h-screen",
)
}
>
<div
class="h-4/6"
>
<form
// onsubmit={on_submit}
>
<div
class={
classes!(
"flex",
"flex-row",
"gap-8",
)
}
>
{ node_refs.iter().enumerate().map(|(index, node_ref)| {
let on_input = {
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! {
<div
class="flex gap-8"
>
<input
ref={node_ref.clone()}
value={input_values[index].clone()}
oninput={on_input}
class={
classes!(
"w-16",
"h-16",
"flex-1",
"text-center",
// "px-4",
// "py-2",
"bg-gray-600"
)
}
/>
</div>
}
}).collect::<Html>() }
</div>
</form>
{ for submitted_words.iter().map(|w| string_to_html(w))}
</div>
<button
class={
classes!(
"w-full",
"flex",
"flex-col",
"mt-6",
"input-center"
)
}
>
{submitted_words.iter().map(|w| string_to_html(&w)).collect::<Html>()}
// <div>{format!("{:?}",res)}</div>
// <div
// class={
// classes!(
// "w-full",
// "h-16"
// )
// }
// >{
// got_word
// }</div>
<ul
class={
classes!(
"flex",
"flex-row",
"justify-between",
"w-72",
"h-16",
"mx-12"
)
}
>
{ input.to_vec().iter().enumerate().map(|(index, item)| {
if let Some(editing_idx) = *editing_index {
if editing_idx == index {
html! {
<li
class="w-12 bg-gray-600 flex items-center justify-center"
>
<input
class="bg-gray-600 h-full w-full"
type="text"
value={editing_value.to_string()}
onblur={on_blur.clone()}
oninput={on_input.reform(|e: InputEvent| e.data().unwrap_or_default())}
/>
</li>
}
} else {
html! { <li
class="w-4 bg-gray-600"
>{item}</li> }
"w-72",
"h-16",
// "mt-24",
"text-2xl",
"font-bold",
"rounded-xl",
"bg-green-700",
)
}
onclick={on_submit} type="submit">
{
if *game_over {
"Play again"
}
else {
"Submit"
}
}
</button>
</div>
}
} else {
html! { <li
class="w-12 bg-gray-600"
onclick={on_click.reform(move |_| index)}>{item}</li> }
}
}).collect::<Html>() }
</ul>
// <input
// value={got_word}
// type="text"
// class={
// classes!(
// "w-full",
// "h-16"
// )
// }
// />
<button onclick={on_submit}>{"Submit"}</button>
</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>
// }
}