use log::info; //use std::env; //use log::Level; use reqwasm::http::Request; use serde::{Deserialize, Serialize}; use serde_json; 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 use dotenv_codegen::dotenv; use itertools::Itertools; use std::collections::HashMap; //use gloo_timers::future::TimeoutFuture; //#[macro_use] //extern crate dotenv_codegen; #[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(Prop, Debug)] pub struct BookUIProp { bookitem: BookUI, } #[derive(Prop, Debug, Clone, PartialEq, Hash, Eq)] pub struct StringProp { value: String, } #[derive(Deserialize, Serialize, Debug, Clone)] struct PaginatedBookUIList { num_pages: u32, books: Vec, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct AxumUser { pub id: i32, pub password_hash: String, pub name: String, } #[derive(Debug, Default, Clone)] pub struct AppState { pub books: RcSignal>, pub search: RcSignal, pub openlibrary: RcSignal, pub internalsearch: RcSignal, pub pagedisplay: RcSignal, pub pagenum: RcSignal, pub maxpage: RcSignal, pub adding: RcSignal, pub updating: RcSignal, pub displaying: RcSignal, pub addingbook: RcSignal, pub displayingbook: RcSignal, pub refreshing: RcSignal, pub apibook: RcSignal, pub updatingrequest: RcSignal, pub addingrequest: RcSignal, pub deleteid: RcSignal, pub deleterequest: RcSignal, pub userlist: RcSignal>, pub userid: RcSignal, pub useridloggedin: RcSignal, pub userscreen: RcSignal, pub loggedin: RcSignal, pub editmode: RcSignal, pub dropdownselect: RcSignal, } /* TODO let callback: Closure = Closure::new(move || { info!("Got scroll event"); }); let window = web_sys::window().expect("Failed to get window"); window .add_event_listener_with_callback_and_bool( "scroll", callback.as_ref().unchecked_ref(), false, // Changing this to true does not help :/ ) .expect("Failed to set listener"); callback.forget();*/ async fn login_user(user: AxumUser) -> Result { let backend_url : &'static str = dotenv!("BACKEND_URL"); let url = format!("{}/api/login", backend_url); let resp = Request::post(&url).body(serde_wasm_bindgen::to_value(&serde_json::to_string(&user).unwrap()).unwrap()).header("content-type","application/json").send().await?; Ok(resp) } async fn register_user(user: AxumUser) -> Result { let backend_url : &'static str = dotenv!("BACKEND_URL"); let url = format!("{}/api/register", backend_url); let resp = Request::post(&url).body(serde_wasm_bindgen::to_value(&serde_json::to_string(&user).unwrap()).unwrap()).header("content-type","application/json").send().await?; Ok(resp) } async fn logout_user() -> Result { let backend_url : &'static str = dotenv!("BACKEND_URL"); let url = format!("{}/api/logout", backend_url); let resp = Request::post(&url).send().await?; Ok(resp) } async fn list_users() -> Result, reqwasm::Error> { let backend_url : &'static str = dotenv!("BACKEND_URL"); let url = format!("{}/api/list_users", backend_url); let resp = Request::post(&url).send().await?; let users = resp.json::>().await?; let mut res = HashMap::new(); for user in users { res.insert(user.name, user.id); } Ok(res) } async fn fetch_books(search: String) -> Result, reqwasm::Error> { //dotenvy::dotenv().ok(); let backend_url : &'static str = dotenv!("BACKEND_URL"); //env::var("BACKEND_URL").expect("BACKEND_URL is not set in .env file"); let url = format!("{}/api/search_openlibrary?search={}", backend_url, search); let resp = Request::get(&url).send().await?; println!("Fetching books\n"); let body = resp.json::>().await?; Ok(body) } async fn search_books(search: String, page: u32, userid:i32) -> Result { let backend_url : &'static str = dotenv!("BACKEND_URL"); //"http://localhost:8081"; //env::var("BACKEND_URL").expect("BACKEND_URL is not set in .env file"); let url = format!("{}/api/list_search?search={}&page={}&userid={}",backend_url, search, page, userid); 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 backend_url : &'static str = dotenv!("BACKEND_URL"); //env::var("BACKEND_URL").expect("BACKEND_URL is not set in .env file"); let url = format!("{}/api/create", backend_url); let resp = Request::post(&url).body(serde_wasm_bindgen::to_value(&serde_json::to_string(&record).unwrap()).unwrap()).header("content-type","application/json").send().await?; Ok(resp) } async fn add_books_isbns(isbns: String) -> Result { let backend_url : &'static str = dotenv!("BACKEND_URL"); //env::var("BACKEND_URL").expect("BACKEND_URL is not set in .env file"); let url = format!("{}/api/create_by_isbn?isbns={}", backend_url, isbns); let resp = Request::get(&url).send().await?; println!("Adding multiple books\n"); Ok(resp) } async fn update_book(record: BookUI) -> Result { let backend_url : &'static str = dotenv!("BACKEND_URL"); //env::var("BACKEND_URL").expect("BACKEND_URL is not set in .env file"); let url = format!("{}/api/update", backend_url); let resp = Request::post(&url).body(serde_wasm_bindgen::to_value(&serde_json::to_string(&record).unwrap()).unwrap()).header("content-type","application/json").send().await?; Ok(resp) } async fn delete_book(id: i32) -> Result { let backend_url : &'static str = dotenv!("BACKEND_URL"); //env::var("BACKEND_URL").expect("BACKEND_URL is not set in .env file"); let url = format!("{}/api/delete/{}", backend_url, id); let resp = Request::get(&url).send().await?; Ok(resp) } async fn list_books(page: u32, userid: i32, sort: String) -> Result { let backend_url : &'static str = dotenv!("BACKEND_URL"); //env::var("BACKEND_URL").expect("BACKEND_URL is not set in .env file"); let url = format!("{}/api/list?page={}&userid={}&sort={}", backend_url, page, userid, sort); let resp = Request::get(&url).send().await?; println!("Fetching books\n"); info!("BACKEND{}",backend_url); 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 value2 = create_signal(cx, String::new()); let input_ref2 = create_node_ref(cx); let value3 = create_signal(cx, String::new()); let input_ref3 = create_node_ref(cx); let editchecked = create_signal(cx, false); 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.clone()); app_state.openlibrary.set(true); app_state.internalsearch.set(false); info!("Fetching search {}\n", task.clone()); app_state.books.set(Vec::new()); value.set("".to_string()); input_ref .get::() .unchecked_into::() .set_value(""); } } }; let handle_submit_seachall = |event: Event| { let event: KeyboardEvent = event.unchecked_into(); if event.key() == "Enter" { let mut task = value2.get().as_ref().clone(); task = task.trim().to_string(); if !task.is_empty() { app_state.search.set(task); app_state.openlibrary.set(false); app_state.internalsearch.set(true); app_state.refreshing.set(true); info!("Fetching search 2\n"); } } }; let click_listall = |_| { app_state.openlibrary.set(false); app_state.internalsearch.set(false); app_state.refreshing.set(true); }; let click_addbook = |_| { app_state.adding.set(true); app_state.updating.set(false); app_state.addingbook.set(BookUI::default()); }; let handle_submit_addbulk = |event: Event| { let event: KeyboardEvent = event.unchecked_into(); if event.key() == "Enter" { let mut task = value3.get().as_ref().clone(); task = task.trim().to_string(); if !task.is_empty() { spawn_local(async move { let _temp = add_books_isbns(task).await.unwrap(); }); } } }; let click_logout = |_| { spawn_local(async move { logout_user().await.unwrap(); }); app_state.loggedin.set(false); app_state.editmode.set(false); }; let click_userscreen = |_| { app_state.userscreen.set(true); }; let toggle_editmode = |_| { app_state.editmode.set(*editchecked.get()); app_state.userid.set(*app_state.useridloggedin.get()); }; let dropdown_userselect = |_| { let app_state = app_state.clone(); spawn_local(async move { app_state.userlist.set(list_users() .await.expect("Couldn't list user")); }); app_state.dropdownselect.set(true); }; // let users = create_memo(cx, || app_state.userlist.get().keys().cloned().map(|x| StringProp{value: x}).collect::>()); let users = create_memo(cx, || app_state.userlist.get().keys().cloned().collect::>()); view! { cx, header(class="header") { div(class="header-button"){ (if *app_state.loggedin.get() == false { view!{ cx, button(on:click=click_userscreen) { "Register/Login" } } } else { view!{cx, button(on:click=click_logout) { "Log Out" } input( class="toggle", type="checkbox", on:input=toggle_editmode, bind:checked=editchecked ) } }) } (if *app_state.editmode.get() == true { view!{ cx, div(class="header-column"){ input(ref=input_ref, class="new-todo", placeholder="Search internet (openlibrary)", bind:value=value, on:keyup=handle_submit, ) } } } else { view!{cx, } }) div(class="header-column"){ (if *app_state.editmode.get() == false { view!{ cx, button(on:click=dropdown_userselect) { "Select User" } (if *app_state.dropdownselect.get() == true { view!{ cx, div(class="dropdown-content") { Keyed( iterable=users, view=move |cx, x| view! { cx, DropDownUser(value=x) }, key =|x| x.clone() ) } } } else { view!{cx, ""} }) }} else { view!{cx, ""} }) button(on:click=click_listall) { "All books" } input(ref=input_ref2, class="new-todo", placeholder="Search your library", bind:value=value2, on:keyup=handle_submit_seachall, ) } (if *app_state.editmode.get() == true { view!{ cx, div(class="header-column"){ button(on:click=click_addbook) { "+ Add New" } input(ref=input_ref3, class="new-todo", placeholder="Add bulk ISBNs", bind:value=value3, on:keyup=handle_submit_addbulk, ) } } } else { view!{cx, ""} }) div(class="header-page-column"){ PageBar{} } } } } #[component(inline_props)] pub fn DropDownUser(cx: Scope, value: StringProp) -> View { let app_state = use_context::(cx); let buttontext = value.clone().value.clone(); let valueclosure = value.clone(); let handle_select_user = move |_| { app_state.userid.set( *(*app_state.userlist.get()).get(&(valueclosure.value).clone()).unwrap_or(&0)); app_state.dropdownselect.set(false); }; view! { cx, button(class="delete", on:click=handle_select_user){ (buttontext) } } } #[component] async fn ListDB(cx: Scope<'_>) -> View { let app_state = use_context::(cx); create_effect(cx, || { let app_state = app_state.clone(); app_state.search.track(); app_state.pagenum.track(); app_state.openlibrary.track(); app_state.internalsearch.track(); app_state.refreshing.track(); //let tempb = app_state.books.get(); //spawn_local( async move { info!( // "The state changed. Old value: {:?}", // app_state.books.get() //)}); if *app_state.openlibrary.get() == false { if *app_state.refreshing.get() == true { app_state.refreshing.set(false); app_state.books.set(Vec::new()); //TimeoutFuture::new(1000).await; //info!("Refresh triggered"); if *app_state.internalsearch.get() == false { info!("DB triggered"); spawn_local(async move { let res = list_books(*app_state.pagenum.get(), *app_state.userid.get(), "desc".to_string()) .await.unwrap(); app_state.books.set( res.books ); app_state.maxpage.set( res.num_pages ) }); } else { info!("IntSearch triggered"); spawn_local(async move { let res = search_books(app_state.search.get().to_string(), *app_state.pagenum.get(), *app_state.userid.get()) .await.unwrap(); app_state.books.set( res.books ); app_state.maxpage.set( res.num_pages ) }); } } } else { if *app_state.refreshing.get() == false { spawn_local(async move { if *app_state.search.get() != "" { if *app_state.openlibrary.get() == true { app_state.books.set( fetch_books(app_state.search.get().to_string() ) .await .unwrap(), ) } } }); } } info!("List Refreshing Done"); }); let docs = create_memo(cx, || app_state.books.get().iter().cloned().collect::>()); view! {cx, p { (if *app_state.openlibrary.get() == false && *app_state.editmode.get() == false { view!{ cx, ul { Keyed( iterable=docs, view=|cx, x| view! { cx, BookDB_View(bookitem=x) }, key =|x| x.id ) } } } else if *app_state.openlibrary.get() == false && *app_state.editmode.get() == true { view!{ cx, ul { Keyed( iterable=docs, view=|cx, x| view! { cx, BookDB(bookitem=x) }, key =|x| x.id ) } } } else { view!{cx, ""} } ) } } } #[component(inline_props)] pub fn BookDB(cx: Scope, bookitem: BookUIProp) -> View { let app_state = use_context::(cx); let bookdelete = bookitem.bookitem.clone(); let bookupdate = bookitem.bookitem.clone(); let bookdisplay = bookitem.bookitem.clone(); let loctitle = bookitem.bookitem.clone().title.clone(); let locauthors = bookitem.bookitem.clone().author_name.clone().unwrap_or(vec!["".to_string()]).join(", "); let locdesc = bookitem.bookitem.clone().description.unwrap_or("".to_string()); let locloc = bookitem.bookitem.clone().location.unwrap_or("".to_string()); //let locref = create_rc_signal(false); let coverurl = bookdisplay.clone().cover.clone().unwrap_or("None".to_string()); let handle_delete = move |_| { app_state.deleteid.set(bookdelete.id); app_state.deleterequest.set(true); }; create_effect(cx, || { let app_state = app_state.clone(); if *app_state.deleterequest.get() == true { spawn_local(async move { let temp = delete_book(*app_state.deleteid.get()).await.unwrap(); println!("{}",temp.status()); app_state.refreshing.set(true); }); } app_state.deleterequest.set(false); }); let handle_update = move |_| { app_state.adding.set(false); app_state.updating.set(true); app_state.addingbook.set(bookupdate.clone()); }; let handle_display = move |_| { app_state.displaying.set(true); app_state.updating.set(false); //info!("Setting displaying book {:?}",bookdisplay); app_state.displayingbook.set(bookdisplay.clone()); }; view! { cx, div(class="column"){ div(class="card"){ div(class="card-buttons"){ button(class="delete", on:click=handle_delete){ "DEL-" } button(class="update", on:click=handle_update){ "EDIT=" } button(class="info", on:click=handle_display){ "INFO+" } } div(class="card-main"){ img(src=coverurl,width="100") div(class="card-title"){ (format!("{}",loctitle)) } div(class="card-authors"){ (format!("{}",locauthors)) } //div(class="card-image"){ //} } div(class="card-desc"){ (format!("{}",locloc)) br{}br{} (format!("{}",locdesc)) } } } } } #[component(inline_props)] pub fn BookDB_View(cx: Scope, bookitem: BookUIProp) -> View { let app_state = use_context::(cx); let bookdisplay = bookitem.bookitem.clone(); let loctitle = bookitem.bookitem.clone().title.clone(); let locauthors = bookitem.bookitem.clone().author_name.clone().unwrap_or(vec!["".to_string()]).join(", "); let locdesc = bookitem.bookitem.clone().description.unwrap_or("".to_string()); let locloc = bookitem.bookitem.clone().location.unwrap_or("".to_string()); let coverurl = bookdisplay.clone().cover.clone().unwrap_or("None".to_string()); let handle_display = move |_| { app_state.displaying.set(true); app_state.updating.set(false); //info!("Setting displaying book {:?}",bookdisplay); app_state.displayingbook.set(bookdisplay.clone()); }; view! { cx, div(class="column"){ div(class="card"){ div(class="card-buttons"){ button(class="info", on:click=handle_display){ "INFO+" } } div(class="card-main"){ img(src=coverurl,width="100") div(class="card-title"){ (format!("{}",loctitle)) } div(class="card-authors"){ (format!("{}",locauthors)) } //div(class="card-image"){ //} } div(class="card-desc"){ (format!("{}",locloc)) br{}br{} (format!("{}",locdesc)) } } } } } #[component] async fn ListOL(cx: Scope<'_>) -> View { let app_state = use_context::(cx); /* create_effect(cx, || { app_state.search.track(); info!( "The state changed B. New value: {}", app_state.search.get(), ); }); */ let docs = create_memo(cx, || app_state.books.get().iter().cloned().collect::>()); view! {cx, p { (if *app_state.openlibrary.get() == true { view!{ cx, div(class="row") { Keyed( iterable=docs, view=move |cx, x| view! { cx, BookOL(bookitem=x) }, key =|x| x.id ) } } } else { view!{cx,""} } ) } } } #[component(inline_props)] pub fn BookOL(cx: Scope, bookitem: BookUIProp) -> View { let book = bookitem.bookitem.clone(); let bookdisp=book.clone(); let loctitle = bookitem.bookitem.clone().title.clone(); let locauthors = bookitem.bookitem.clone().author_name.clone().unwrap_or(vec!["".to_string()]).join(", "); let coverurl = bookdisp.cover.clone().unwrap_or("NONE".to_string()); let app_state = use_context::(cx); let handle_add = move |_| { app_state.adding.set(true); app_state.addingbook.set(book.clone()); }; view! { cx, div(class="column"){ div(class="card-openlibrary"){ button(class="add", on:click=handle_add){ "ADD+" } div(class="card-main"){ img(src=coverurl,width="100") div(class="card-title"){ (format!("{}",loctitle)) } div(class="card-authors"){ (format!("{}",locauthors)) } //div(class="card-image"){ //} } } } } } #[component] async fn AddingUI(cx: Scope<'_>) -> View { let app_state = use_context::(cx); let node_ref = create_node_ref(cx); let inp_title = create_signal(cx, (*app_state.addingbook.get()).clone().title); let inp_olkey = create_signal(cx, (*app_state.addingbook.get()).clone().open_library_key.unwrap_or("".to_string())); let inp_editioncount = create_signal(cx, (*app_state.addingbook.get()).clone().edition_count.unwrap_or(0).to_string()); let inp_publishyear = create_signal(cx, (*app_state.addingbook.get()).clone().first_publish_year.unwrap_or(0).to_string()); let inp_medianpage = create_signal(cx, (*app_state.addingbook.get()).clone().median_page_count.unwrap_or(0).to_string()); let inp_goodread = create_signal(cx, (*app_state.addingbook.get()).clone().goodread_id.unwrap_or("".to_string())); let inp_desc = create_signal(cx, (*app_state.addingbook.get()).clone().description.unwrap_or("".to_string())); let inp_cover = create_signal(cx, (*app_state.addingbook.get()).clone().cover.unwrap_or("".to_string())); let inp_location = create_signal(cx, (*app_state.addingbook.get()).clone().location.unwrap_or("".to_string())); let inp_rating = create_signal(cx, (*app_state.addingbook.get()).clone().location.unwrap_or("".to_string())); let inp_comments = create_signal(cx, (*app_state.addingbook.get()).clone().comments.unwrap_or("".to_string())); let inp_author = create_signal(cx, (*app_state.addingbook.get()).clone().author_name.unwrap_or(vec!["".to_string()]).join(", ")); let inp_person = create_signal(cx, (*app_state.addingbook.get()).clone().person.unwrap_or(vec!["".to_string()]).join(", ")); let inp_place = create_signal(cx, (*app_state.addingbook.get()).clone().place.unwrap_or(vec!["".to_string()]).join(", ")); let inp_subject = create_signal(cx, (*app_state.addingbook.get()).clone().subject.unwrap_or(vec!["".to_string()]).join(", ")); let inp_time = create_signal(cx, (*app_state.addingbook.get()).clone().time.unwrap_or(vec!["".to_string()]).join(", ")); let inp_isbn = create_signal(cx, (*app_state.addingbook.get()).clone().isbn.unwrap_or(vec!["".to_string()]).join(", ")); create_effect(cx, || { // info!("{:?}",*app_state.addingbook.get()); inp_title.set((*app_state.addingbook.get()).clone().title); inp_olkey.set((*app_state.addingbook.get()).clone().open_library_key.unwrap_or("".to_string())); inp_editioncount.set((*app_state.addingbook.get()).clone().edition_count.unwrap_or(0).to_string()); inp_publishyear.set((*app_state.addingbook.get()).clone().first_publish_year.unwrap_or(0).to_string()); inp_medianpage.set((*app_state.addingbook.get()).clone().median_page_count.unwrap_or(0).to_string()); inp_goodread.set((*app_state.addingbook.get()).clone().goodread_id.unwrap_or("".to_string())); inp_desc.set((*app_state.addingbook.get()).clone().description.unwrap_or("".to_string())); inp_cover.set((*app_state.addingbook.get()).clone().cover.unwrap_or("".to_string())); inp_location.set((*app_state.addingbook.get()).clone().location.unwrap_or("".to_string())); inp_rating.set((*app_state.addingbook.get()).clone().location.unwrap_or("".to_string())); inp_comments.set((*app_state.addingbook.get()).clone().comments.unwrap_or("".to_string())); inp_author.set((*app_state.addingbook.get()).clone().author_name.unwrap_or(vec!["".to_string()]).join(", ")); inp_person.set((*app_state.addingbook.get()).clone().person.unwrap_or(vec!["".to_string()]).join(", ")); inp_place.set((*app_state.addingbook.get()).clone().place.unwrap_or(vec!["".to_string()]).join(", ")); inp_subject.set((*app_state.addingbook.get()).clone().subject.unwrap_or(vec!["".to_string()]).join(", ")); inp_time.set((*app_state.addingbook.get()).clone().time.unwrap_or(vec!["".to_string()]).join(", ")); inp_isbn.set((*app_state.addingbook.get()).clone().isbn.unwrap_or(vec!["".to_string()]).join(", ")); }); let handle_cancel = |_| { app_state.updating.set(false); app_state.adding.set(false); let dom_node = node_ref.try_get::(); if dom_node.is_some() { dom_node.unwrap().set_attribute("popup-display","false"); } }; let handle_add = |_| { info!("Adding book"); let authors: Vec = (*inp_author.get()).clone().split(",").map(str::to_string).collect::>(); let persons: Vec = (*inp_person.get()).clone().split(",").map(str::to_string).collect::>(); let places: Vec = (*inp_place.get()).clone().split(",").map(|x| x.to_string()).collect::>(); let subjects: Vec = (*inp_subject.get()).clone().split(",").map(|x| x.to_string()).collect::>(); let times: Vec = (*inp_time.get()).clone().split(",").map(|x| x.to_string()).collect::>(); let isbns: Vec = (*inp_isbn.get()).clone().split(",").map(|x| x.to_string()).collect::>(); let record : BookUI = BookUI{ id: app_state.addingbook.get().id.clone(), title: (*inp_title.get()).clone(), open_library_key: Some((*inp_olkey.get()).clone()), edition_count: Some(inp_editioncount.get().parse::().unwrap_or(0)), first_publish_year: Some(inp_publishyear.get().parse::().unwrap_or(0)), median_page_count: Some(inp_medianpage.get().parse::().unwrap_or(0)), goodread_id: Some((*inp_goodread.get()).clone()), description: Some((*inp_desc.get()).clone()), cover: Some((*inp_cover.get()).clone()), location: Some((*inp_location.get()).clone()), time_added: Some("NA".to_string()), rating: Some(inp_rating.get().parse::().unwrap_or(0)), comments: Some((*inp_comments.get()).clone()), author_name: Some(authors), person: Some(persons), place: Some(places), subject: Some(subjects), time: Some(times), isbn: Some(isbns), }; app_state.apibook.set(record); app_state.addingbook.set(BookUI::default()); if *app_state.updating.get()==true { app_state.updatingrequest.set(true); } if *app_state.adding.get()==true { app_state.addingrequest.set(true); } app_state.updating.set(false); app_state.adding.set(false); //app_state.books.set(vec![BookUI::default()]); }; create_effect(cx, || { app_state.apibook.track(); let app_state = app_state.clone(); let record = (*app_state.apibook.get()).clone(); spawn_local(async move { info!("Adding effect startedDone"); if *app_state.addingrequest.get() == true { let temp = add_book(record).await.unwrap(); info!("Adding Done{}",temp.status()); app_state.refreshing.set(true); app_state.addingrequest.set(false); } else if *app_state.updatingrequest.get() == true { let temp = update_book(record).await.unwrap(); info!("Updating Done{}",temp.status()); app_state.refreshing.set(true); app_state.updatingrequest.set(false); } }); }); create_effect(cx, || { if *app_state.updating.get() == true || *app_state.adding.get() == true { let dom_node = node_ref.try_get::(); if dom_node.is_some() { dom_node.unwrap().set_attribute("popup-display","true"); } } else { let dom_node = node_ref.try_get::(); if dom_node.is_some() { dom_node.unwrap().set_attribute("popup-display","false"); } } }); view! {cx, div(class="modal-box",ref=node_ref){ (if *app_state.adding.get() == true || *app_state.updating.get() == true { view!{ cx, div(class="modal-content"){ div(class="input-buttons"){ button(class="add", on:click=handle_add){ "Add/Update book to DB" } button(class="cancel", on:click=handle_cancel){ "Cancel" } } p { div(class="input-field"){ label{"Title"} textarea(bind:value=inp_title, placeholder="Title" ) } div(class="input-field"){ label{"OpenLib Key"} textarea(bind:value=inp_olkey, placeholder="OpenLibrary Key" ) } div(class="input-field"){ label{"Editions"} textarea(bind:value=inp_editioncount, placeholder="Number of editions" ) } div(class="input-field"){ label{"Publish year"} textarea(bind:value=inp_publishyear, placeholder="First publish year" ) } div(class="input-field"){ label{"Pages"} textarea(bind:value=inp_medianpage, placeholder="Page count" ) } div(class="input-field"){ label{"GoodreadID"} textarea(bind:value=inp_goodread, placeholder="GoodRead ID" ) } div(class="input-field"){ label{"Description"} textarea(bind:value=inp_desc, placeholder="Description" ) } div(class="input-field"){ label{"Cover"} textarea(bind:value=inp_cover, placeholder="Cover URL" ) } div(class="input-field"){ label{"Location"} textarea(bind:value=inp_location, placeholder="Location" ) } div(class="input-field"){ label{"Rating"} textarea(bind:value=inp_rating, placeholder="Rating (/10)" ) } div(class="input-field"){ label{"Comments"} textarea(bind:value=inp_comments, placeholder="Comments" ) } div(class="input-field"){ label{"Authors"} textarea(bind:value=inp_author, placeholder="Authors") } div(class="input-field"){ label{"Persons"} textarea(bind:value=inp_person, placeholder="Persons" ) } div(class="input-field"){ label{"Places"} textarea(bind:value=inp_place, placeholder="Places" ) } div(class="input-field"){ label{"Subjects"} textarea(bind:value=inp_subject, placeholder="Subjects" ) } div(class="input-field"){ label{"Times"} textarea(bind:value=inp_time, placeholder="Times" ) } div(class="input-field"){ label{"ISBNs"} textarea(bind:value=inp_isbn, placeholder="ISBNs" ) } } } } } else { view!{cx,""} }) } } } #[component] async fn SelectedUI(cx: Scope<'_>) -> View { let app_state = use_context::(cx); let displaybook = create_memo(cx, || app_state.displayingbook.get()); let coverurl = create_memo(cx, || app_state.displayingbook.get().clone().cover.clone().unwrap_or("NONE".to_string()).to_string().clone()); let node_ref = create_node_ref(cx); let dtitle = create_memo(cx, || app_state.displayingbook.get().title.clone()); let dollink = create_memo(cx, || { let olkey = app_state.displayingbook.get().open_library_key.clone().unwrap_or("".to_string()); format!("https://openlibrary.org{}",olkey) } ); let deditions = create_memo(cx, || app_state.displayingbook.get().edition_count.unwrap_or(0) ); let dpubyr = create_memo(cx, || app_state.displayingbook.get().first_publish_year.unwrap_or(0) ); let dpages = create_memo(cx, || app_state.displayingbook.get().median_page_count.unwrap_or(0) ); let dgoodread = create_memo(cx, || { let goodreadid = app_state.displayingbook.get().goodread_id.clone().unwrap_or("".to_string()); format!("https://goodreads.com/en/book/show/{}",goodreadid) } ); let ddesc = create_memo(cx, || app_state.displayingbook.get().description.clone().unwrap_or("".to_string()) ); let dlocation = create_memo(cx, || app_state.displayingbook.get().location.clone().unwrap_or("".to_string()) ); let dcomments = create_memo(cx, || app_state.displayingbook.get().comments.clone().unwrap_or("".to_string()) ); let dtags = create_memo(cx, || { let persons = app_state.displayingbook.get().person.clone().unwrap_or(vec!["".to_string()]).iter().filter(|word| !word.is_empty()).map(|s| format!("#{}",s.clone())).collect::>().join(", "); let places = app_state.displayingbook.get().place.clone().unwrap_or(vec!["".to_string()]).iter().filter(|word| !word.is_empty()).map(|s| format!("#{}",s.clone())).collect::>().join(", "); let subjects = app_state.displayingbook.get().subject.clone().unwrap_or(vec!["".to_string()]).iter().filter(|word| !word.is_empty()).map(|s| format!("#{}",s.clone())).collect::>().join(", "); let times = app_state.displayingbook.get().time.clone().unwrap_or(vec!["".to_string()]).iter().filter(|word| !word.is_empty()).map(|s| format!("#{}",s.clone())).collect::>().join(", "); let alltags = vec![persons,places,subjects,times].iter().filter(|word| !word.is_empty()).join(", "); format!("{}",alltags) } ); let disbn = create_memo(cx, || { app_state.displayingbook.get().isbn.clone().unwrap_or(vec!["".to_string()])[0].clone() }); let dauthors = create_memo(cx, || { app_state.displayingbook.get().author_name.clone().unwrap_or(vec!["".to_string()]).join(", ") }); let handle_close = move |_| { app_state.displaying.set(false); let dom_node = node_ref.try_get::(); if dom_node.is_some() { dom_node.unwrap().set_attribute("popup-display","false"); } }; let handle_edit = move |_| { app_state.displaying.set(false); let dom_node = node_ref.try_get::(); if dom_node.is_some() { dom_node.unwrap().set_attribute("popup-display","false"); } app_state.addingbook.set((*(*displaybook.get())).clone()); app_state.updating.set(true); }; create_effect(cx, || { if *app_state.displaying.get() == true { let dom_node = node_ref.try_get::(); if dom_node.is_some() { dom_node.unwrap().set_attribute("popup-display","true"); } } else { let dom_node = node_ref.try_get::(); if dom_node.is_some() { dom_node.unwrap().set_attribute("popup-display","false"); } } }); view! {cx, div(class="modal-box", ref=node_ref){ (if *app_state.displaying.get() == true { view!{ cx, div(class="modal-content"){ div(class="more-info"){ div(class="more-info-buttons"){ button(class="close", on:click=handle_close){ "CLOSE" } button(class="close", on:click=handle_edit){ "EDIT" } } img(src=coverurl.get(),width="200") label{"Title: "} (format!("{}",dtitle.get())) br{} label{"Authors: "} (format!("{}",dauthors.get())) br{} label{"Location: "} (format!("{}",dlocation.get())) br{} label{"Editions: "} (format!("{}",deditions.get())) br{} label{"Publish Year: "} (format!("{}",dpubyr.get())) br{} label{"Page Count: "} (format!("{}",dpages.get())) br{} label{"Openlibrary Link: "} a(href=dollink.get().clone()){(format!("{}",dollink.get()))} br{} label{"GoodReads Link: "} a(href=dgoodread.get().clone()){(format!("{}",dgoodread.get()))} br{} label{"ISBN: "} (format!("{}",disbn.get())) br{} label{"Description: "} (format!("{}",ddesc.get())) br{} label{"Comments: "} (format!("{}",dcomments.get())) br{} label{"Tags: "} (format!("{}",dtags.get())) br{} } } }} else { view!{cx,""} }) } } } #[component] async fn PageBar(cx: Scope<'_>) -> View { let app_state = use_context::(cx); let currpg = create_signal(cx, (*app_state.pagenum.get()).to_string()); let input_ref = create_node_ref(cx); let handle_add = move |_| { app_state.pagenum.set(*app_state.pagenum.get()+1); app_state.refreshing.set(true); }; let handle_sub = move |_| { if *app_state.pagenum.get()>1 { app_state.pagenum.set(*app_state.pagenum.get()-1); app_state.refreshing.set(true); } }; let handle_submit = |event: Event| { let event: KeyboardEvent = event.unchecked_into(); if event.key() == "Enter" { let pg = currpg.get().as_ref().clone().parse::().unwrap_or(1); if pg>0 && pg<*app_state.maxpage.get() { app_state.pagenum.set(pg); app_state.refreshing.set(true); } else { currpg.set((*app_state.pagenum.get()).to_string()); } } }; view! {cx, div { (if *app_state.openlibrary.get() == false || *app_state.internalsearch.get() == true { view!{ cx, label{"PAGE "} input(ref=input_ref,bind:value=currpg,on:keyup=handle_submit,class="page-input") label{(format!(" / {}",*app_state.maxpage.get()))} (if *app_state.pagenum.get()>1 { view!{cx, button(class="page", on:click=handle_sub){ "-" }} } else { view!{cx,""} }) (if *app_state.pagenum.get()<*app_state.maxpage.get() { view!{cx, button(class="page", on:click=handle_add){ "+" }} } else { view!{cx,""} }) } }else { view!{cx,""} }) } } } #[component] async fn LoginScreenUI(cx: Scope<'_>) -> View { let app_state = use_context::(cx); let userval = create_signal(cx, String::new()); let input_refU = create_node_ref(cx); let passval = create_signal(cx, String::new()); let input_refP = create_node_ref(cx); let handle_register = move |_| { let app_state = app_state.clone(); let user = AxumUser{ id: 0, name: (*userval.get()).clone(), password_hash: (*passval.get()).clone(), }; spawn_local(async move { register_user(user) .await.expect("Couldn't register user"); }); spawn_local(async move { app_state.userlist.set(list_users() .await.expect("Couldn't list user")); }); app_state.userscreen.set(false); }; let handle_login = move |_| { let user = AxumUser{ id: 0, name: (*userval.get()).clone(), password_hash: (*passval.get()).clone(), }; spawn_local(async move { login_user(user) .await.expect("Couldn't login user"); }); app_state.loggedin.set(true); app_state.useridloggedin.set( *(*app_state.userlist.get()).get(&(*userval.get()).clone()).unwrap_or(&0)); app_state.userid.set( *(*app_state.userlist.get()).get(&(*userval.get()).clone()).unwrap_or(&0)); app_state.userscreen.set(false); }; view! {cx, div { (if *app_state.userscreen.get() == true { view!{cx, input(ref=input_refU,bind:value=userval) input(ref=input_refP,bind:value=passval) button(on:click=handle_login){ "LOGIN" } button(on:click=handle_register){ "REGISTER" } } }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()), internalsearch: create_rc_signal(bool::default()), pagedisplay: create_rc_signal(1), pagenum: create_rc_signal(1), maxpage: create_rc_signal(1), adding: create_rc_signal(bool::default()), updating: create_rc_signal(bool::default()), addingbook: create_rc_signal(BookUI::default()), displaying: create_rc_signal(bool::default()), displayingbook: create_rc_signal(BookUI::default()), refreshing: create_rc_signal(true), apibook: create_rc_signal(BookUI::default()), updatingrequest: create_rc_signal(bool::default()), addingrequest: create_rc_signal(bool::default()), deleteid: create_rc_signal(-1), deleterequest: create_rc_signal(bool::default()), userlist: create_rc_signal(HashMap::new()), userid: create_rc_signal(0), useridloggedin: create_rc_signal(0), userscreen: create_rc_signal(bool::default()), loggedin: create_rc_signal(bool::default()), editmode: create_rc_signal(bool::default()), dropdownselect: create_rc_signal(bool::default()), }; provide_context(cx, app_state); view! { cx, div { Header {} div(class="main"){ LoginScreenUI{} AddingUI{} SelectedUI{} ListOL {} ListDB{} } //PageBar{} } } } fn main() { console_error_panic_hook::set_once(); console_log::init_with_level(log::Level::Debug).unwrap(); sycamore::render(|cx| view! { cx, App {} }); }