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(Debug, Default, Clone)] pub struct AppState { pub books: RcSignal>, pub search: RcSignal, pub openlibrary: RcSignal, pub adding: RcSignal, pub addingbook: 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) } async fn add_book(record: BookUI) -> Result { let url = format!("http://localhost:8081/api/create"); let _resp = Request::post(&url).body(serde_wasm_bindgen::to_value(&record).unwrap()).send().await?; Ok("OK".to_string()) } async fn delete_book(id: i32) -> Result { let url = format!("http://localhost:8081/api/delete/{}", id); let _resp = Request::post(&url).send().await?; Ok("OK".to_string()) } async fn list_books() -> Result, reqwasm::Error> { let url = format!("http://localhost:8081/api/list"); let resp = Request::post(&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); app_state.openlibrary.set(true); println!("Fetching search\n"); info!("Fetching search\n"); value.set("".to_string()); input_ref .get::() .unchecked_into::() .set_value(""); } } }; let click_listall = |_| (app_state.openlibrary.set(false)); let click_addbook = |_| { app_state.adding.set(true); app_state.addingbook.set(BookUI::default()); }; view! { cx, header(class="header") { h1 { "Search OpenLibrary" } input(ref=input_ref, class="new-todo", placeholder="What needs to be done?", bind:value=value, on:keyup=handle_submit, ) button(on:click=click_listall) { "List All DB" } button(on:click=click_addbook) { "+ Add New" } } } } #[component] async fn ListDB(cx: Scope<'_>) -> View { let app_state = use_context::(cx); create_effect(cx, || { let app_state = app_state.clone(); if *app_state.openlibrary.get() == false { spawn_local(async move { app_state.books.set( list_books() .await .unwrap(), ) }); } }); let docs = create_memo(cx, || app_state.books.get().iter().cloned().collect::>()); view! {cx, p { (if *app_state.openlibrary.get() == false { view!{ cx, //span {(app_state.books.get().num_found)} ul { Keyed( iterable=docs, view=move |cx, x| view! { cx, li { (format!("{:?}",x)) } }, key =|x| x.id ) } } } else { view!{cx,""} } ) } } } #[component] async fn ListOL(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 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.openlibrary.get() == true { view!{ cx, ul { Keyed( iterable=docs, view=move |cx, x| view! { cx, li { (format!("{:?}",x)) } }, key =|x| x.id ) } } } else { view!{cx,""} } ) } } } #[component] async fn AddingUI(cx: Scope<'_>) -> View { let app_state = use_context::(cx); view! {cx, p { (if *app_state.adding.get() == true { view!{ cx,"Hello" } } else { view!{cx,""} } ) } } } #[component] fn App(cx: Scope) -> View { let app_state = AppState { books: create_rc_signal(Vec::new()), search: create_rc_signal(String::default()), openlibrary: create_rc_signal(bool::default()), adding: create_rc_signal(bool::default()), addingbook: create_rc_signal(BookUI::default()), }; provide_context(cx, app_state); view! { cx, div { Header {} AddingUI{} ListOL {} ListDB{} } } } fn main() { console_error_panic_hook::set_once(); console_log::init_with_level(log::Level::Debug).unwrap(); sycamore::render(|cx| view! { cx, App {} }); }