use gloo_net::http::Request; use web_sys::wasm_bindgen::convert::OptionIntoWasmAbi; use web_sys::wasm_bindgen::JsCast; use web_sys::HtmlElement; use yew::prelude::*; use yew::{classes, function_component, Callback, Html}; use crate::pages::game::GameResult; use crate::CharStatus; use super::game::WordList; static NEW_WORD_URI: &str = "https://wordl.shuttleapp.rs/word"; static WORDS_URI: &str = "https://wordl.shuttleapp.rs/public/wordlist.json"; static MAX_TRIES: usize = 5; fn set_focus(index: usize) { let prefix = match index { 0 => "", _ => "-" }; if let Some(w) = web_sys::window() { if let Some(d) = w.document() { if let Some(n) = d .query_selector(&format!("[tabindex='{prefix}{index}']")) .ok() .flatten() { if let Some(e) = n.dyn_ref::() { e.focus().ok(); } } } } } fn string_to_html(input: &[CharStatus]) -> Html { let classes = classes!( "bg-gray-700", "w-16", "h-16", "text-center", "py-4", "font-bold", "text-lg", "mb-4", ); html! ( ) } #[allow(clippy::too_many_arguments)] fn fetch_new_word( word: &UseStateHandle, loading: &UseStateHandle, submitted_words: &UseStateHandle>>>, input_values: &UseStateHandle>, game_over: &UseStateHandle, length: &UseStateHandle, node_refs: &UseStateHandle>, result: &UseStateHandle, ) { let loading = loading.clone(); let submitted_words = submitted_words.clone(); let input_values = input_values.clone(); let game_over = game_over.clone(); let length = length.clone(); let node_refs = node_refs.clone(); let result = result.clone(); let word = word.clone(); wasm_bindgen_futures::spawn_local(async move { loading.set(true); let res = Request::get(NEW_WORD_URI).send().await; if let Ok(r) = res { if let Ok(w) = r.text().await { length.set(w.len()); node_refs.set(vec![NodeRef::default(); w.len()]); input_values.set(vec![String::new(); w.len()]); word.set(w.to_uppercase()); submitted_words.set(Vec::with_capacity(MAX_TRIES)); game_over.set(false); result.set(GameResult::Lose); loading.set(false); } } }); } #[allow(dead_code)] fn fetch_words(state: &UseStateHandle) { let state = state.clone(); wasm_bindgen_futures::spawn_local(async move { let res = Request::get(WORDS_URI).send().await; if let Ok(r) = res { if let Ok(w) = r.text().await { state.set(WordList::from_json(&w)); } } }); } #[function_component] pub fn Home() -> Html { let word: UseStateHandle = use_state(String::new); let loading: UseStateHandle = use_state(|| true); let curr_index: UseStateHandle = use_state(|| 0usize); let length = use_state(|| 0usize); let submitted_words: UseStateHandle>>> = use_state(|| std::vec::Vec::with_capacity(MAX_TRIES)); let node_refs = use_state(|| vec![NodeRef::default(); 10]); let input_values: UseStateHandle> = use_state(|| vec![String::new(); *length]); let game_over = use_state(|| false); let result = use_state(|| GameResult::Lose); { let handle = word.clone(); let loading = loading.clone(); let submitted_words = submitted_words.clone(); let input_values = input_values.clone(); let game_over = game_over.clone(); let length = length.clone(); let node_refs = node_refs.clone(); let result = result.clone(); use_effect_with((), move |()| { fetch_new_word( &handle, &loading, &submitted_words, &input_values, &game_over, &length, &node_refs, &result, ); }); } let game_over_check = { let word = word.clone(); let submitted_words = submitted_words.clone(); let input_values = input_values.clone(); let game_over = game_over.clone(); let length = length.clone(); let result = result.clone(); Callback::from(move |_| { if submitted_words.iter().count() >= *length - 1 || crate::compare_strings(&word, &input_values.join("")) .iter() .all(|v| matches!(v, CharStatus::Match(_))) { if crate::compare_strings(&word, &input_values.join("")) .iter() .all(|v| matches!(v, CharStatus::Match(_))) { result.set(GameResult::Win); } game_over.set(true); } }) }; let on_disabled = { let curr_index = curr_index.clone(); let input_values = input_values.clone(); Callback::from(move |_e: MouseEvent| { let index = input_values.iter().enumerate().find(|(_, v)| v.is_empty()).map_or(0, |(i,_)| i); set_focus(index); curr_index.set(index); }) }; let on_submit = { let input_values = input_values.clone(); let submitted_words = submitted_words.clone(); let game_over = game_over.clone(); let length = length.clone(); let word = word.clone(); let node_refs = node_refs.clone(); let loading = loading.clone(); let result = result.clone(); let curr_index = curr_index.clone(); Callback::from(move |_e: MouseEvent| { if *game_over { curr_index.set(0); let input_values = input_values.clone(); let submitted_words = submitted_words.clone(); let game_over = game_over.clone(); let length = length.clone(); let word = word.clone(); let loading = loading.clone(); let node_refs = node_refs.clone(); let result = result.clone(); fetch_new_word( &word, &loading, &submitted_words, &input_values, &game_over, &length, &node_refs, &result, ); return; } let values: Vec<_> = input_values.iter().cloned().collect(); if !values.iter().all(|v| !v.is_empty()) { return; } let mut new_items = (*submitted_words).clone(); new_items.push(crate::compare_strings(&word, &values.join(""))); submitted_words.set(new_items); input_values.set(vec![String::new(); word.len()]); set_focus(0); curr_index.set(0); game_over_check.emit(MouseEvent::none()); }) }; let on_enter = { let on_submit = on_submit.clone(); let curr_index = curr_index.clone(); let node_refs = node_refs.clone(); let input_values = input_values.clone(); let length = length.clone(); Callback::from(move |e: KeyboardEvent| match e.key().as_ref() { "Enter" => { if let Ok(m) = MouseEvent::new("click") { on_submit.emit(m); } } "Backspace" => { e.prevent_default(); let mut index = *curr_index; let mut values = (*input_values).clone(); if index >= *length { curr_index.set(*length - 1); index = *length - 1; } if node_refs[index] .cast::() .is_some() && index > 0 { values[index] = String::new(); input_values.set(values); let index = index - 1; curr_index.set(index); set_focus(index); } } _ => {} }) }; let on_input = { let curr_index = curr_index.clone(); let length = length.clone(); let input_values = input_values.clone(); Callback::from(move |e: InputEvent| { if let Some(value) = e.data() { let value = value.to_uppercase(); let index = *curr_index; let mut values = (*input_values).clone(); if index >= *length { values[index - 1] = value; input_values.set(values); } else if value.len() < values[index].len() && index > 0 && index <= *length { values[index] = String::new(); input_values.set(values); let new_index = index - 1; curr_index.set(new_index); set_focus(new_index); } else if value.len() == 1 && value.chars().all(char::is_alphabetic) { values[index] = value; input_values.set(values); if index < *length { let new_index = index + 1; curr_index.set(new_index); set_focus(new_index); } } else { values[index] = String::new(); input_values.set(values); } } }) }; let view = { move || { html! {
if *loading {

{"Loading..."}

} else {
{ for submitted_words.iter().map(|e| {string_to_html(e)})}
{ if *game_over { let (text, color) = match *result { GameResult::Win => { ("FOUND", "bg-green-600") }, GameResult::Lose => { ("WANTED", "bg-red-600") } }; html! (

{ text }

    { word.chars().map(|e|{ let text = e; html!{
  • {text}
  • }}).collect::() }
) } else if !*game_over { node_refs.iter().enumerate().map(|(index, node_ref)| { let on_focus = { let curr_index = curr_index.clone(); Callback::from(move |e: FocusEvent| { let target = e.target_unchecked_into::(); if let Some(index) = target.get_attribute("tabindex") { if let Ok(i) = index.parse::() { curr_index.set(i); } } }) }; let prefix = match index { 0 => String::new(), _ => "-".to_owned(), }; html! { "one", 1 => "two", 2 => "three", 3 => "four", 4 => "five", _ => "", })} onkeyup={on_enter.clone()} oninput={on_input.clone()} tabindex={ format!("{prefix}{index}")} ref={node_ref.clone()} value={input_values[index].clone()} onfocus={on_focus.clone()} class={ classes!( "w-16", "h-16", "text-center", "bg-gray-600" ) } /> } }).collect::() } else { html!(
) } }
{ if *loading { html!{<>} } else { html!{
} } }
}
} } }; view() }