use crate::Route; use gloo_net::http::Request; use itertools::Itertools; use lib::lib::*; use stylist::style; use wasm_bindgen::*; use web_sys::{EventTarget, HtmlInputElement}; use yew::prelude::*; use yew_router::scope_ext::RouterScopeExt; #[derive(Properties, Clone, PartialEq, Eq)] pub struct ResultComponentProps { result: IndexedResource, } #[function_component(ResultComponent)] fn result_component(props: &ResultComponentProps) -> Html { let style = style!( r#" a { text-decoration: none; } .url { font-size: 0.75em; } .title { font-size: 1.25em; } .title:hover { text-decoration: underline; } .description { font-size: 1em; } "# ) .unwrap(); let style = style.get_class_name().to_owned(); fn truncate(s: &str, max_chars: usize) -> &str { match s.char_indices().nth(max_chars) { None => s, Some((idx, _)) => &s[..idx], } } html! {

{props.result.url.clone()}

{match props.result.title.clone() { None => "No Title".to_string(), Some(title) => { truncate(&title, 70).to_string()//TODO: add ... if truncate }, }}

{match props.result.description.clone() { None => "No Description.".to_string(), Some(description) => { truncate(&description, 200).to_string() //TODO: add ... if truncate }, }}{format!("PRIO: {}", props.result.priority)}

} } pub struct SearchResult { query: String, results: Option, String>>, //none signifies not finished loading } pub struct OSSE { pub current_search_query: String, pub results: Option, //none signifies no query yet } #[derive(Properties, PartialEq, Eq)] pub struct OSSEProps { pub api_endpoint: String, pub initial_search_query: Option, } pub enum OSSEMessage { SearchSubmitted, SearchChanged(String), SearchFinished(Result, String>), } impl Component for OSSE { type Message = OSSEMessage; type Properties = OSSEProps; fn create(ctx: &Context) -> Self { let mut search_query = String::from(""); //we push an SearchSubmitted message if inital_search_query is not none if let Some(initial_search_query) = ctx.props().initial_search_query.clone() { search_query = initial_search_query; ctx.link().send_message(OSSEMessage::SearchSubmitted); } //WE may have data race between the future and the actual creation. OSSE { current_search_query: urlencoding::decode(search_query.as_str()) .to_owned() .unwrap() .to_string(), results: None, } } fn update(&mut self, ctx: &Context, msg: Self::Message) -> bool { let search_query = self.current_search_query.clone(); match msg { OSSEMessage::SearchSubmitted => { let api_endpoint = ctx.props().api_endpoint.clone(); let navigator = ctx.link().navigator().unwrap(); navigator.push(&Route::OSSESearch { query: urlencoding::encode(search_query.as_str()).to_string(), }); let search_query_clone = search_query.clone(); ctx.link().send_future(async move { let endpoint = format!("{}/search/{}", &api_endpoint, &search_query_clone); let fetched_response = match Request::get(endpoint.as_str()).send().await { Ok(response) => response, Err(_) => { return OSSEMessage::SearchFinished(Err( "Failed to connect to the API!".to_string(), )) } }; let fetched_results: Vec = match fetched_response.json().await { Err(_) => { return OSSEMessage::SearchFinished(Err( "Internal API Error!".to_string() )) } Ok(json) => json, }; OSSEMessage::SearchFinished(Ok(fetched_results)) }); self.results = Some(SearchResult { query: search_query, results: None, //none yet }); true } OSSEMessage::SearchChanged(search_query) => { self.current_search_query = search_query; true } OSSEMessage::SearchFinished(search_results) => { match search_results { Ok(results) => { self.results = Some(SearchResult { query: search_query, results: Some(Ok(results)), }); } Err(error) => { self.results = Some(SearchResult { query: search_query, results: Some(Err(error)), }); } }; true } } } fn view(&self, ctx: &Context) -> Html { let onsubmit = ctx.link().callback(|event: SubmitEvent| { event.prevent_default(); OSSEMessage::SearchSubmitted }); let oninput = ctx.link().callback(|event: InputEvent| { let target: EventTarget = event .target() .expect("Event should have a target when dispatched"); let input = target.unchecked_into::().value(); OSSEMessage::SearchChanged(input) }); let display_results = |maybe_results: &Option| -> Html { //not yet searched if maybe_results.is_none() { return html! {}; } let result = maybe_results.as_ref().unwrap(); let search_query = &result.query; let results = &result.results; //not yet loaded results if results.is_none() { return html! {
{"Loading..."}
}; } let results = results.as_ref().unwrap(); if results.is_err() { return html! {

{format!("ERROR: {}", results.as_ref().err().unwrap())}

}; } let results = results.as_ref().unwrap(); if results.is_empty() { return html! {

{"No results!"}

}; } html! { <> //Problem with margin: When no results or early return then mb not applied
{format!("{} Results for \"{}\"", results.len(), search_query)}
{results .iter() .sorted() .map(|r| { html! {
} }) .collect::()} } }; html! { <>
//SET AT MIDDLE OF VIEWPORT IF NO SEARCHING AND TOP 25% IF SEARCHING
{"OSSE"}

{"Your favorite independent search engine."}

{display_results(&self.results)}
} } } //Your favorite search engine in navbar //Search in middle