use log::info; //use log::Level; use reqwasm::http::Request; use serde::{Deserialize, Serialize}; use sycamore::futures::spawn_local; use sycamore::prelude::*; use sycamore::suspense::Suspense; use sycamore_router::Route; use wasm_bindgen::JsCast; use web_sys::{Event, HtmlInputElement, KeyboardEvent}; // 0.3.5 #[derive(Deserialize, Serialize, Debug, Default, Clone, PartialEq, Hash, Eq)] pub struct BookUI { id: i32, open_library_key: Option, title: String, edition_count: Option, first_publish_year: Option, median_page_count: Option, goodread_id: Option, description: Option, cover: Option, location: Option, time_added: Option, rating: Option, comments: Option, author_name: Option>, person: Option>, place: Option>, subject: Option>, time: Option>, isbn: Option>, } #[derive(Deserialize, Serialize, Debug, Default, Clone, PartialEq, Hash, Eq)] pub struct Docs { id: u32, key: String, title: String, first_publish_year: Option, number_of_pages_median: Option, isbn: Option>, cover_i: Option, author_name: Option>, person: Option>, place: Option>, subject: Option>, time: Option>, } #[derive(Deserialize, Serialize, Debug, Default)] pub struct Books { num_found: u32, docs: Vec, } #[derive(Debug, Default, Clone)] pub struct AppState { pub books: RcSignal>, pub search: RcSignal, } #[derive(Route)] enum AppRoutes { #[to("/")] Home, #[to("/hello-server")] HelloServer, #[not_found] NotFound, } async fn fetch_books(search: String) -> Result, reqwasm::Error> { let url = format!("http://localhost:8081/api/search_openlibrary?search={}", search); let resp = Request::get(&url).send().await?; println!("Fetching books\n"); let body = resp.json::>().await?; Ok(body) } #[component] pub fn Header(cx: Scope) -> View { let app_state = use_context::(cx); let value = create_signal(cx, String::new()); let input_ref = create_node_ref(cx); let handle_submit = |event: Event| { let event: KeyboardEvent = event.unchecked_into(); if event.key() == "Enter" { let mut task = value.get().as_ref().clone(); task = task.trim().to_string(); if !task.is_empty() { app_state.search.set(task); println!("Fetching search\n"); info!("Fetching search\n"); value.set("".to_string()); input_ref .get::() .unchecked_into::() .set_value(""); } } }; view! { cx, header(class="header") { h1 { "todos" } input(ref=input_ref, class="new-todo", placeholder="What needs to be done?", bind:value=value, on:keyup=handle_submit, ) } } } #[component] async fn VisitsCount(cx: Scope<'_>) -> View { let app_state = use_context::(cx); create_effect(cx, || { //info!( // "The state changed. New value: {} {}", // app_state.search.get(), //); let app_state = app_state.clone(); app_state.search.track(); spawn_local(async move { app_state.books.set( fetch_books(app_state.search.get().to_string()) .await .unwrap(), ) }); }); let state = create_signal(cx, 0i32); // let increment = |x: Docs| (state.set(x.first_publish_year.unwrap().try_into().unwrap())); let increment = |_| (state.set(2)); let docs = create_memo(cx, || app_state.books.get().iter().cloned().collect::>()); //let docs = create_signal(cx, vec![1, 2]); view! {cx, p { (if !app_state.search.get().is_empty() { view!{ cx, //span {(app_state.books.get().num_found)} ul { Keyed( iterable=docs, view=move |cx, x| view! { cx, li { (format!("{:?}",x)) button(on:click=increment) { "+" } } }, key =|x| x.id ) } } } else { view! { cx, span { "World" } } }) } } } #[component] fn App(cx: Scope) -> View { let app_state = AppState { books: create_rc_signal(Vec::new()), search: create_rc_signal(String::default()), }; provide_context(cx, app_state); view! { cx, div { Header {} p { "Page Visit Counter" } Suspense(fallback=view! { cx, "Loading..." }) { VisitsCount {} } } } } fn main() { console_error_panic_hook::set_once(); console_log::init_with_level(log::Level::Debug).unwrap(); sycamore::render(|cx| view! { cx, App {} }); }