use common::web_api; use futures::{ StreamExt, channel::mpsc::{UnboundedReceiver, UnboundedSender, unbounded}, }; use gloo::{events::EventListener, utils::document}; use wasm_bindgen::prelude::*; use wasm_bindgen_futures::spawn_local; use web_sys::{Element, HtmlDivElement, HtmlInputElement, HtmlSpanElement, KeyboardEvent}; use crate::{ ron_request, utils::{SelectorExt, get_current_lang, selector, selector_and_clone}, }; const MAX_NB_SEARCH_RESULT: u32 = 10; pub fn setup() { // Here we check if the search input exists. // Otherwise the search elements are considered as not present. let input_search = match document().query_selector("#search input") { Ok(Some(e)) => e.dyn_into::().unwrap(), _ => return, }; let (sender, mut receiver): (UnboundedSender, UnboundedReceiver) = unbounded(); spawn_local(async move { while let Some(search_term) = receiver.next().await { perform_search(search_term).await; } }); EventListener::new(&input_search, "input", move |event| { let input_search: HtmlInputElement = event.target().unwrap().dyn_into().unwrap(); let search_term = input_search.value().trim().to_string(); sender.unbounded_send(search_term).unwrap(); }) .forget(); EventListener::new(&input_search, "keydown", move |event| { let key_event: &KeyboardEvent = event.dyn_ref().unwrap(); if key_event.key() == "Escape" { let input_search: HtmlInputElement = key_event.target().unwrap().dyn_into().unwrap(); input_search.set_value(""); selector::("#search .results") .style() .set_property("display", "none") .unwrap(); } }) .forget(); } async fn perform_search(search_term: String) { let results_element: HtmlDivElement = selector("#search .results"); if search_term.is_empty() { results_element .style() .set_property("display", "none") .unwrap(); return; } results_element .selector::("ul") .set_text_content(None); if search_term.len() < common::consts::MIN_SEARCH_STRING_LENGTH { display_message(Message::TooFewCharacters); } else { display_message(Message::None); let search_result: Vec = ron_request::get_with_params( "/ron-api/recipe/search", web_api::SearchByTitleParams { search_term, max_nb_results: MAX_NB_SEARCH_RESULT, }, ) .await .unwrap(); if search_result.is_empty() { display_message(Message::NoResults); } else { let ul_element = results_element.selector::("ul"); for recipe in search_result { let li_element: Element = selector_and_clone("#hidden-templates-search li"); let a_element = li_element.selector::("a"); a_element .set_attribute( "href", &format!("/{}/recipe/view/{}", get_current_lang(), recipe.recipe_id), ) .unwrap(); a_element.set_inner_html(&recipe.title_highlighted); ul_element.append_child(&li_element).unwrap(); } } } results_element .style() .set_property("display", "block") .unwrap(); } enum Message { None, TooFewCharacters, NoResults, } fn display_message(message: Message) { let message_element: HtmlDivElement = selector("#search .message"); for m in message_element.selector_all::("span") { m.style().set_property("display", "none").unwrap(); } message_element .style() .set_property("display", "block") .unwrap(); match message { Message::TooFewCharacters => message_element .selector::(".to-small") .style() .set_property("display", "inline") .unwrap(), Message::NoResults => message_element .selector::(".no-results") .style() .set_property("display", "inline") .unwrap(), Message::None => message_element .style() .set_property("display", "none") .unwrap(), } }