Add search frontend code
This commit is contained in:
parent
988849e598
commit
3e91f34303
22 changed files with 379 additions and 73 deletions
|
|
@ -45,6 +45,7 @@ web-sys = { version = "0.3", features = [
|
|||
"HtmlElement",
|
||||
"MediaQueryList",
|
||||
"HtmlDivElement",
|
||||
"HtmlSpanElement",
|
||||
"HtmlLabelElement",
|
||||
"HtmlInputElement",
|
||||
"HtmlImageElement",
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ mod pages;
|
|||
mod recipe_scheduler;
|
||||
mod request;
|
||||
mod ron_request;
|
||||
mod search;
|
||||
mod shopping_list;
|
||||
mod toast;
|
||||
mod utils;
|
||||
|
|
@ -140,6 +141,9 @@ pub fn main() -> Result<(), JsValue> {
|
|||
})
|
||||
.forget();
|
||||
|
||||
// Search box.
|
||||
search::setup();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,9 +1,8 @@
|
|||
use std::{cell::RefCell, rc, sync::Mutex};
|
||||
|
||||
use common::{web_api, utils::substitute};
|
||||
use common::{utils::substitute, web_api};
|
||||
use gloo::{
|
||||
events::{EventListener, EventListenerOptions},
|
||||
net::http::Request,
|
||||
utils::{document, window},
|
||||
};
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
|
@ -15,7 +14,6 @@ use web_sys::{
|
|||
|
||||
use crate::{
|
||||
modal_dialog, request, ron_request,
|
||||
toast::{self, Level},
|
||||
utils::{SelectorExt, by_id, get_current_lang, selector, selector_and_clone},
|
||||
};
|
||||
|
||||
|
|
|
|||
143
frontend/src/search.rs
Normal file
143
frontend/src/search.rs
Normal file
|
|
@ -0,0 +1,143 @@
|
|||
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::<HtmlInputElement>().unwrap(),
|
||||
_ => return,
|
||||
};
|
||||
|
||||
let (sender, mut receiver): (UnboundedSender<String>, UnboundedReceiver<String>) = 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::<HtmlDivElement>("#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::<Element>("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<web_api::RecipeSearchResult> = 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::<Element>("ul");
|
||||
for recipe in search_result {
|
||||
let li_element: Element = selector_and_clone("#hidden-templates-search li");
|
||||
let a_element = li_element.selector::<Element>("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::<HtmlSpanElement>("span") {
|
||||
m.style().set_property("display", "none").unwrap();
|
||||
}
|
||||
|
||||
message_element
|
||||
.style()
|
||||
.set_property("display", "block")
|
||||
.unwrap();
|
||||
|
||||
match message {
|
||||
Message::TooFewCharacters => message_element
|
||||
.selector::<HtmlSpanElement>(".to-small")
|
||||
.style()
|
||||
.set_property("display", "inline")
|
||||
.unwrap(),
|
||||
Message::NoResults => message_element
|
||||
.selector::<HtmlSpanElement>(".no-results")
|
||||
.style()
|
||||
.set_property("display", "inline")
|
||||
.unwrap(),
|
||||
Message::None => message_element
|
||||
.style()
|
||||
.set_property("display", "none")
|
||||
.unwrap(),
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue