From 9f7731ef42d82d2f7db312368c457fc08187615d Mon Sep 17 00:00:00 2001 From: Vinod J M Date: Sat, 3 Dec 2022 22:43:49 +0530 Subject: [PATCH] v0.15-WORKINGV1 --- Dockerfile | 35 +- backend/Cargo.lock | 1 + backend/api/Cargo.toml | 1 + backend/api/src/lib.rs | 13 +- .../src/m20220101_000001_create_table.rs | 26 +- backend/search/src/lib.rs | 4 +- frontend/Cargo.lock | 53 ++- frontend/Cargo.toml | 4 +- frontend/Trunk.toml | 1 + frontend/css/index.css | 135 ++++++- frontend/index.html | 6 +- frontend/src/main.rs | 372 ++++++++++++++---- 12 files changed, 504 insertions(+), 147 deletions(-) diff --git a/Dockerfile b/Dockerfile index 15b5377..07b761b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -8,41 +8,20 @@ WORKDIR /usr/src/app RUN rustup target add wasm32-unknown-unknown #RUN export CARGO_BUILD_JOBS = 1 ENV CARGO_BUILD_JOBS=1 - RUN cargo install trunk -RUN mkdir frontend -RUN mkdir frontend/src -RUN mkdir backend -RUN mkdir backend/src -RUN mkdir backend/api/ -RUN mkdir backend/entity/ -RUN mkdir backend/migration/ -RUN mkdir backend/orm/ -RUN mkdir backend/search/ -RUN mkdir backend/api/src -RUN mkdir backend/entity/src -RUN mkdir backend/migration/src -RUN mkdir backend/orm/src -RUN mkdir backend/search/src +RUN mkdir frontend && mkdir frontend/src && mkdir backend && mkdir backend/src && mkdir backend/api/ && mkdir backend/entity/ && mkdir backend/migration/ && mkdir backend/orm/ && mkdir backend/search/ && mkdir backend/api/src && mkdir backend/entity/src && mkdir backend/migration/src && mkdir backend/orm/src && mkdir backend/search/src && mkdir frontend/css + +COPY ./dummy.rs ./dummy.rs +RUN cp dummy.rs /usr/src/app/frontend/src/main.rs && cp dummy.rs /usr/src/app/backend/src/main.rs && cp dummy.rs /usr/src/app/backend/api/src/main.rs && cp dummy.rs /usr/src/app/backend/entity/src/lib.rs && cp dummy.rs /usr/src/app/backend/migration/src/lib.rs && cp dummy.rs /usr/src/app/backend/orm/src/lib.rs && cp dummy.rs /usr/src/app/backend/search/src/lib.rs COPY ./frontend/Cargo.toml /usr/src/app/frontend -COPY ./dummy.rs /usr/src/app/frontend/src/main.rs - COPY ./backend/Cargo.toml /usr/src/app/backend -COPY ./dummy.rs /usr/src/app/backend/src/main.rs COPY ./backend/api/Cargo.toml /usr/src/app/backend/api/Cargo.toml -COPY ./dummy.rs /usr/src/app/backend/api/src/lib.rs COPY ./backend/entity/Cargo.toml /usr/src/app/backend/entity/Cargo.toml -COPY ./dummy.rs /usr/src/app/backend/entity/src/lib.rs COPY ./backend/migration/Cargo.toml /usr/src/app/backend/migration/Cargo.toml -COPY ./dummy.rs /usr/src/app/backend/migration/src/lib.rs COPY ./backend/orm/Cargo.toml /usr/src/app/backend/orm/Cargo.toml -COPY ./dummy.rs /usr/src/app/backend/orm/src/lib.rs COPY ./backend/search/Cargo.toml /usr/src/app/backend/search/Cargo.toml -COPY ./dummy.rs /usr/src/app/backend/search/src/lib.rs - -RUN mkdir frontend/css COPY ./frontend/index.html /usr/src/app/frontend/ COPY ./frontend/css/index.css /usr/src/app/frontend/css COPY ./frontend/Trunk.toml /usr/src/app/frontend/ @@ -52,15 +31,15 @@ RUN cd backend && cargo build --release COPY . /usr/src/app RUN cp .prod .env -#RUN cd frontend && trunk build --release +RUN cd frontend && trunk build --release RUN cd backend && cargo build --release FROM alpine:latest WORKDIR /usr/src/app COPY --from=builder /usr/src/app/backend/target/release/backend /usr/src/app/booksman -COPY ./dist /usr/src/dist -#COPY --from=builder /usr/src/app/dist /usr/src/dist +#COPY ./dist /usr/src/dist +COPY --from=builder /usr/src/app/dist /usr/src/dist COPY --from=builder /usr/src/app/.env /usr/src/app/.env RUN chmod +x /usr/src/app/booksman diff --git a/backend/Cargo.lock b/backend/Cargo.lock index 75a9446..4091b14 100644 --- a/backend/Cargo.lock +++ b/backend/Cargo.lock @@ -433,6 +433,7 @@ dependencies = [ "axum-extra", "booksman-orm", "booksman-search", + "chrono", "clap", "dotenvy", "entity", diff --git a/backend/api/Cargo.toml b/backend/api/Cargo.toml index 6584165..44622cb 100644 --- a/backend/api/Cargo.toml +++ b/backend/api/Cargo.toml @@ -26,6 +26,7 @@ tower-http = { version = "^0.3", features = ["full"] } tracing = "^0.1" tracing-subscriber = "^0.3" itertools = "0.10" +chrono = "0.4" [dependencies.sea-orm] version = "^0.9.2" # sea-orm version diff --git a/backend/api/src/lib.rs b/backend/api/src/lib.rs index cd64af1..8e90009 100644 --- a/backend/api/src/lib.rs +++ b/backend/api/src/lib.rs @@ -30,6 +30,7 @@ use meilisearch_sdk::client::Client; use ::entity::entities::{book,book_author,book_person,book_place,book_subject,book_time,book_isbn}; use std::env; use migration::{Migrator, MigratorTrait}; +use chrono::Local; #[derive(Deserialize, Serialize, Debug, Clone)] struct BookUI { @@ -605,7 +606,7 @@ async fn list_search_book( let mut resbooks: Vec = Vec::with_capacity(24); - for bookmeili in books.into_iter() { + for bookmeili in books.0.into_iter() { let mut cover = bookmeili.clone().cover; if cover!="".to_string() { cover = format!("{}/images/{}",backend_url,cover); @@ -637,7 +638,7 @@ let mut resbooks: Vec = Vec::with_capacity(24); } let res = PaginatedBookUIList{ - num_pages: 10, + num_pages: (books.1/24) as u32, books: resbooks }; return Json(res); @@ -668,7 +669,7 @@ async fn create_book( let img_bytes = reqwest::get(cover.unwrap()).await.unwrap().bytes().await.unwrap(); let image = image::load_from_memory(&img_bytes).unwrap(); let temp_cover = doc_sent.cover.clone().unwrap(); - let img_id = temp_cover.split("/").last().unwrap(); + let img_id = format!("{}{}",temp_cover.split("/").last().unwrap(),Local::now().format("%Y-%m-%d-%H-%M-%S")); image.save(format!("{}/{}",images_dir,img_id)).expect("Failed to save image"); cover = Some(img_id.to_string()); } @@ -766,7 +767,7 @@ async fn create_book( median_page_count: doc_sent.median_page_count.unwrap_or(0), goodread_id: doc_sent.goodread_id.unwrap_or("".to_string()), description: doc_sent.description.unwrap_or("".to_string()), - cover: doc_sent.cover.unwrap_or("".to_string()), + cover: cover.unwrap_or("".to_string()), location: doc_sent.location.unwrap_or("".to_string()), time_added: doc_sent.time_added.unwrap_or("".to_string()), rating: doc_sent.rating.unwrap_or(0), @@ -807,7 +808,7 @@ if !doc_sent.cover.is_none() { //let img_bytes = img_resp.unwrap().bytes(); let image = image::load_from_memory(&img_bytes).unwrap(); let temp_cover = doc_sent.cover.clone().unwrap(); - let img_id = temp_cover.split("/").last().unwrap(); + let img_id = format!("{}{}",temp_cover.split("/").last().unwrap(),Local::now().format("%Y-%m-%d-%H-%M-%S")); image.save(format!("{}/{}",images_dir,img_id)).expect("Failed to save image"); cover = Some(img_id.to_string()); } @@ -929,7 +930,7 @@ let book: book::Model = book::Model{ median_page_count: doc_sent.median_page_count.unwrap_or(0), goodread_id: doc_sent.goodread_id.unwrap_or("".to_string()), description: doc_sent.description.unwrap_or("".to_string()), - cover: doc_sent.cover.unwrap_or("".to_string()), + cover: cover.unwrap_or("".to_string()), location: doc_sent.location.unwrap_or("".to_string()), time_added: doc_sent.time_added.unwrap_or("".to_string()), rating: doc_sent.rating.unwrap_or(0), diff --git a/backend/migration/src/m20220101_000001_create_table.rs b/backend/migration/src/m20220101_000001_create_table.rs index e775702..a4f2af8 100644 --- a/backend/migration/src/m20220101_000001_create_table.rs +++ b/backend/migration/src/m20220101_000001_create_table.rs @@ -1,4 +1,4 @@ -use chrono::DateTime; +//use chrono::DateTime; use sea_orm_migration::prelude::*; #[derive(DeriveMigrationName)] @@ -36,7 +36,7 @@ impl MigrationTrait for Migration { .col(ColumnDef::new(Book::Comments).string()) .to_owned(), ) - .await; + .await.expect("Migration failed"); manager .create_table( @@ -62,7 +62,7 @@ impl MigrationTrait for Migration { ) .to_owned(), ) - .await; + .await.expect("Migration failed"); manager .create_table( @@ -88,7 +88,7 @@ impl MigrationTrait for Migration { ) .to_owned(), ) - .await; + .await.expect("Migration failed"); manager .create_table( @@ -114,7 +114,7 @@ impl MigrationTrait for Migration { ) .to_owned(), ) - .await; + .await.expect("Migration failed"); manager .create_table( @@ -140,7 +140,7 @@ impl MigrationTrait for Migration { ) .to_owned(), ) - .await; + .await.expect("Migration failed"); manager .create_table( @@ -166,7 +166,7 @@ impl MigrationTrait for Migration { ) .to_owned(), ) - .await; + .await.expect("Migration failed"); manager .create_table( @@ -201,22 +201,22 @@ impl MigrationTrait for Migration { manager .drop_table(Table::drop().table(Book::Table).to_owned()) - .await; + .await.expect("Drop failed"); manager .drop_table(Table::drop().table(BookAuthor::Table).to_owned()) - .await; + .await.expect("Drop failed"); manager .drop_table(Table::drop().table(BookPerson::Table).to_owned()) - .await; + .await.expect("Drop failed"); manager .drop_table(Table::drop().table(BookPlace::Table).to_owned()) - .await; + .await.expect("Drop failed"); manager .drop_table(Table::drop().table(BookSubject::Table).to_owned()) - .await; + .await.expect("Drop failed"); manager .drop_table(Table::drop().table(BookTime::Table).to_owned()) - .await; + .await.expect("Drop failed"); manager .drop_table(Table::drop().table(BookISBN::Table).to_owned()) .await diff --git a/backend/search/src/lib.rs b/backend/search/src/lib.rs index dbe1e1e..53964c4 100644 --- a/backend/search/src/lib.rs +++ b/backend/search/src/lib.rs @@ -42,7 +42,7 @@ pub async fn delete_book(bookid: i32, client: &Client) { } -pub async fn search_book(search: &str, page: usize, client: &Client) -> Result, meilisearch_sdk::errors::Error> { +pub async fn search_book(search: &str, page: usize, client: &Client) -> Result<(Vec, usize), meilisearch_sdk::errors::Error> { // An index is where the documents are stored. let books = client.index("books"); let results : SearchResults = books.search().with_query(search).with_offset((page-1)*24) @@ -50,7 +50,7 @@ pub async fn search_book(search: &str, page: usize, client: &Client) -> Result = (results.hits).iter().map(|r| r.result.clone()).collect(); //.iter()s.map(|r| r.formatted_result.unwrap()).collect(); - return Ok(formatted_results); + return Ok((formatted_results, results.estimated_total_hits)); } diff --git a/frontend/Cargo.lock b/frontend/Cargo.lock index 34b66d8..67ef473 100644 --- a/frontend/Cargo.lock +++ b/frontend/Cargo.lock @@ -73,10 +73,39 @@ dependencies = [ ] [[package]] -name = "dotenvy" -version = "0.15.6" +name = "dotenv" +version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03d8c417d7a8cb362e0c37e5d815f5eb7c37f79ff93707329d5a194e42e54ca0" +checksum = "77c90badedccf4105eca100756a0b1289e191f6fcbdadd3cee1d2f614f97da8f" + +[[package]] +name = "dotenv_codegen" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56966279c10e4f8ee8c22123a15ed74e7c8150b658b26c619c53f4a56eb4a8aa" +dependencies = [ + "dotenv_codegen_implementation", + "proc-macro-hack", +] + +[[package]] +name = "dotenv_codegen_implementation" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53e737a3522cd45f6adc19b644ce43ef53e1e9045f2d2de425c1f468abd4cf33" +dependencies = [ + "dotenv", + "proc-macro-hack", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "either" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797" [[package]] name = "env_logger" @@ -97,9 +126,10 @@ version = "0.1.0" dependencies = [ "console_error_panic_hook", "console_log", - "dotenvy", + "dotenv_codegen", "env_logger", "gloo-net 0.2.4", + "itertools", "log", "reqwasm", "serde", @@ -306,6 +336,15 @@ dependencies = [ "hashbrown", ] +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.3" @@ -402,6 +441,12 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "proc-macro-hack" +version = "0.5.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5" + [[package]] name = "proc-macro2" version = "1.0.43" diff --git a/frontend/Cargo.toml b/frontend/Cargo.toml index b4b26e7..c0b997f 100644 --- a/frontend/Cargo.toml +++ b/frontend/Cargo.toml @@ -7,11 +7,13 @@ edition = "2021" [dependencies] console_error_panic_hook = "0.1.7" -dotenvy = "0.15.0" +#dotenvy = "0.15.0" +dotenv_codegen = "0.15.0" env_logger = "0.9.0" gloo-net = "^0.2" #gloo-utils = {version = "0.1.5", features =["serde"]} log = "0.4" +itertools = "0.10" console_log = { version = "0.2", features = ["color"] } #reqwest = {version = "0.11.11", features = ["blocking", "json"]} reqwasm = {version = "0.5.0", features = ["json"]} diff --git a/frontend/Trunk.toml b/frontend/Trunk.toml index 02bb8e1..5919adf 100644 --- a/frontend/Trunk.toml +++ b/frontend/Trunk.toml @@ -1,6 +1,7 @@ [build] target = "index.html" dist = "../dist" +public_url = "/assets/" [[proxy]] backend = "http://[::1]:8081/api/" diff --git a/frontend/css/index.css b/frontend/css/index.css index 97208d8..3a28349 100644 --- a/frontend/css/index.css +++ b/frontend/css/index.css @@ -28,19 +28,41 @@ body { } /* Modal Content/Box */ .modal-content { - margin: 10% auto; /* 15% from the top and centered */ + margin: 5% auto; /* 15% from the top and centered */ padding: 20px; border: 1px solid #888; width: 80%; /* Could be more or less, depending on screen size */ - height: 80%; + height: 90%; overflow: auto; /* Enable scroll if needed */ background-color: rgb(0,0,0); /* Fallback color */ } +/* Float three header columns side by side */ +.header { + position: fixed; /* Stay in place */ + overflow: hidden; + z-index: 1; /* Sit on top */ + width: 100%; + top: 0; /* Position the navbar at the top of the page */ + padding: 0 10px; + background-color: #f1f1f1; +} + +.main { + margin-top: 50px; /* Add a top margin to avoid content overlay */ +} + /* Float three header columns side by side */ .header-column { float: left; - width: 30%; + width: 28%; + padding: 0 10px; +} +/* Float three header columns side by side */ +.header-page-column { + float: left; + margin-top: 0px; + width: 15%; padding: 0 10px; } @@ -49,6 +71,8 @@ body { float: left; width: 25%; padding: 0 10px; + margin-top: 10px; + margin-bottom: 10px; } /* Remove extra left and right margins, due to padding in columns */ @@ -70,6 +94,105 @@ body { overflow: hidden; text-overflow: ellipsis; background-color: #f1f1f1; + #content img { + position: absolute; + top: 0px; + right: 0px; + } +} + +/* Style the counter cards */ +.card-openlibrary { + box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2); /* this adds the "card" effect */ + padding: 16px; + text-align: center; + height: 200px; + overflow: hidden; + text-overflow: ellipsis; + background-color: #f1f1f1; + #content img { + position: absolute; + top: 0px; + right: 0px; + } +} +/* +.card-image { + padding: 16px; + position: relative; + top: 0px; + right: 0px; + width: 40%; +} +*/ +.card-title { + padding: 16px; + float: left; + text-align: left; + width: 60%; + font-weight: bold; + font-size: large; +} + +.card-authors { + padding: 16px; + float: left; + text-align: left; + width: 60%; + font-size: large; +} + + +.input-field { + padding: 4px; + float: left; + text-align: left; + width: 50%; + font-size: large; +} +.input-field label { + display: block; + vertical-align: middle; + min-width: 20%; + max-width: 20%; + width: 20%; + padding: 4px; +} +.input-field textarea { + vertical-align: middle; + width: 80%; +} +.input-buttons { + padding: 20px; + float: left; + text-align: left; + width: 100%; +} + +.more-info { + width: 90%; +} +.more-info img { + float: right; + width: 200px; + padding: 0 20px 20px 0; +} + +.more-info label { + display:inline-block; + text-align: left; + width: 20%; + font-weight: bold; + font-size: large; +} + +.more-info-buttons { + padding: 20px; + text-align: left; +} + +.page-input { + width: 40px; } /* Responsive columns - one column layout (vertical) on small screens */ @@ -80,3 +203,9 @@ body { margin-bottom: 20px; } } + +@media screen and (max-width: 800px) { + .main { + margin-top: 80px; /* Add a top margin to avoid content overlay */ + } +} diff --git a/frontend/index.html b/frontend/index.html index a9212bd..0f259e1 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -2,10 +2,10 @@ - - + + + Book Manager - diff --git a/frontend/src/main.rs b/frontend/src/main.rs index ffaa12c..27e5d79 100644 --- a/frontend/src/main.rs +++ b/frontend/src/main.rs @@ -1,4 +1,5 @@ use log::info; +//use std::env; //use log::Level; use reqwasm::http::Request; use serde::{Deserialize, Serialize}; @@ -9,6 +10,10 @@ use sycamore::prelude::*; use sycamore_router::Route; use wasm_bindgen::JsCast; use web_sys::{Event, HtmlInputElement, KeyboardEvent}; // 0.3.5 +use dotenv_codegen::dotenv; +use itertools::Itertools; +//#[macro_use] +//extern crate dotenv_codegen; #[derive(Deserialize, Serialize, Debug, Default, Clone, PartialEq, Hash, Eq)] pub struct BookUI { @@ -38,7 +43,7 @@ pub struct BookUIProp { bookitem: BookUI, } -#[derive(Deserialize, Serialize, Debug)] +#[derive(Deserialize, Serialize, Debug, Clone)] struct PaginatedBookUIList { num_pages: u32, books: Vec, @@ -51,11 +56,13 @@ pub struct AppState { pub openlibrary: RcSignal, pub internalsearch: 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, } #[derive(Route)] @@ -69,8 +76,8 @@ enum AppRoutes { } async fn fetch_books(search: String) -> Result, reqwasm::Error> { - dotenvy::dotenv().ok(); - let backend_url = "http://localhost:8081"; + //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?; @@ -80,8 +87,8 @@ async fn fetch_books(search: String) -> Result, reqwasm::Error> { } async fn search_books(search: String, page: u32) -> Result { - dotenvy::dotenv().ok(); - let backend_url = "http://localhost:8081"; + 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={}",backend_url, search, page); let resp = Request::get(&url).send().await?; @@ -91,8 +98,7 @@ async fn search_books(search: String, page: u32) -> Result Result { - dotenvy::dotenv().ok(); - let backend_url = "http://localhost:8081"; + 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?; @@ -100,8 +106,7 @@ async fn add_book(record: BookUI) -> Result Result { - dotenvy::dotenv().ok(); - let backend_url = "http://localhost:8081"; + 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?; @@ -111,8 +116,7 @@ async fn add_books_isbns(isbns: String) -> Result Result { - dotenvy::dotenv().ok(); - let backend_url = "http://localhost:8081"; + 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?; @@ -120,8 +124,7 @@ async fn update_book(record: BookUI) -> Result Result { - dotenvy::dotenv().ok(); - let backend_url = "http://localhost:8081"; + 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?; @@ -129,8 +132,7 @@ async fn delete_book(id: i32) -> Result } async fn list_books(page: u32) -> Result { - dotenvy::dotenv().ok(); - let backend_url = "http://localhost:8081"; + 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={}", backend_url, page); let resp = Request::get(&url).send().await?; @@ -290,6 +292,9 @@ pub fn Header(cx: Scope) -> View { ) button(on:click=click_addbulk) { "Add bulk ISBNs" } } + div(class="header-page-column"){ + PageBar{} + } } } } @@ -304,6 +309,8 @@ async fn ListDB(cx: Scope<'_>) -> View { app_state.pagenum.track(); app_state.openlibrary.track(); app_state.internalsearch.track(); + app_state.refreshing.track(); + app_state.pagenum.track(); info!( "The state changed. New value: {}", @@ -311,34 +318,45 @@ async fn ListDB(cx: Scope<'_>) -> View { ); if *app_state.openlibrary.get() == false { + app_state.refreshing.set(false); + if *app_state.internalsearch.get() == false { spawn_local(async move { + let res = list_books(*app_state.pagenum.get()) + .await.unwrap(); app_state.books.set( - list_books(*app_state.pagenum.get()) - .await - .unwrap().books + res.books + ); + app_state.maxpage.set( + res.num_pages ) }); } else { spawn_local(async move { + let res = search_books(app_state.search.get().to_string(), *app_state.pagenum.get()) + .await.unwrap(); app_state.books.set( - search_books(app_state.search.get().to_string(), *app_state.pagenum.get()) - .await - .unwrap().books) - }) + res.books + ); + app_state.maxpage.set( + res.num_pages + ) + }); } } else { - 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(), - ) + 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(), + ) + } } - } - }); + }); + } } }); @@ -366,7 +384,7 @@ async fn ListDB(cx: Scope<'_>) -> View { #[component(inline_props)] pub fn BookDB(cx: Scope, bookitem: BookUIProp) -> View { - let app_state = use_context::(cx); + let app_state = use_context::(cx); let bookdelete = bookitem.bookitem.clone(); let bookupdate = bookitem.bookitem.clone(); let bookdisplay = bookitem.bookitem.clone(); @@ -378,15 +396,18 @@ pub fn BookDB(cx: Scope, bookitem: BookUIProp) -> View { let coverurl = bookdisplay.clone().cover.clone().unwrap_or("None".to_string()); let handle_delete = move |_| { spawn_local(async move { - let temp = delete_book(bookdelete.id).await.unwrap(); + let temp = delete_book(bookdelete.id).await.unwrap(); println!("{}",temp.status()); }); + app_state.refreshing.set(true); + }; let handle_update = move |_| { app_state.adding.set(false); app_state.updating.set(true); app_state.addingbook.set(bookupdate.clone()); + app_state.refreshing.set(true); }; let handle_display = move |_| { @@ -398,19 +419,29 @@ pub fn BookDB(cx: Scope, bookitem: BookUIProp) -> View { 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"){ + + div(class="card-title"){ + (format!("{}",loctitle)) + } + div(class="card-authors"){ + (format!("{}",locauthors)) + } + //div(class="card-image"){ img(src=coverurl,width="100") - - (format!("{:?}",loctitle)) - br{} - (format!("{:?}",locauthors)) - br{} - (format!("{:?}",locdesc)) - - + //} + } + div(class="card-desc"){ + (format!("{}",locdesc)) + } } } @@ -460,7 +491,6 @@ pub fn BookOL(cx: Scope, bookitem: BookUIProp) -> View { 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 locdesc = bookitem.bookitem.clone().description.unwrap_or("".to_string()); let coverurl = bookdisp.cover.clone().unwrap_or("NONE".to_string()); let app_state = use_context::(cx); let handle_add = move |_| { @@ -470,15 +500,20 @@ pub fn BookOL(cx: Scope, bookitem: BookUIProp) -> View { view! { cx, div(class="column"){ - div(class="card"){ - img(src=coverurl,width="100") + div(class="card-openlibrary"){ + button(class="add", on:click=handle_add){ "ADD+" } + div(class="card-main"){ - (format!("{:?}",loctitle)) - br{} - (format!("{:?}",locauthors)) - br{} - (format!("{:?}",locdesc)) - button(class="add", on:click=handle_add){ "+" } + div(class="card-title"){ + (format!("{}",loctitle)) + } + div(class="card-authors"){ + (format!("{}",locauthors)) + } + //div(class="card-image"){ + img(src=coverurl,width="100") + //} + } } } } @@ -587,6 +622,7 @@ info!("Adding book"); app_state.addingbook.set(BookUI::default()); app_state.updating.set(false); app_state.adding.set(false); + app_state.refreshing.set(true); }; @@ -613,27 +649,80 @@ info!("Adding book"); (if *app_state.adding.get() == true || *app_state.updating.get() == true { view!{ cx, div(class="modal-content"){ - - p { - input(bind:value=inp_title, placeholder="Title" ) - input(bind:value=inp_olkey, placeholder="OpenLibrary Key" ) - input(bind:value=inp_editioncount, placeholder="Number of editions" ) - input(bind:value=inp_publishyear, placeholder="First publish year" ) - input(bind:value=inp_medianpage, placeholder="Page count" ) - input(bind:value=inp_goodread, placeholder="GoodRead ID" ) - input(bind:value=inp_desc, placeholder="Description" ) - input(bind:value=inp_cover, placeholder="Cover URL" ) - input(bind:value=inp_location, placeholder="Location" ) - input(bind:value=inp_rating, placeholder="Rating (/10)" ) - input(bind:value=inp_comments, placeholder="Comments" ) - input(bind:value=inp_author, placeholder="Authors") - input(bind:value=inp_person, placeholder="Persons" ) - input(bind:value=inp_place, placeholder="Places" ) - input(bind:value=inp_subject, placeholder="Subjects" ) - input(bind:value=inp_time, placeholder="Times" ) - input(bind:value=inp_isbn, placeholder="ISBNs" ) - button(class="add", on:click=handle_add){ "Add/Update book to DB" } + 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" ) + } + } } @@ -653,12 +742,43 @@ info!("Adding book"); #[component] async fn SelectedUI(cx: Scope<'_>) -> View { let app_state = use_context::(cx); - let displ_book = create_memo(cx, || app_state.displayingbook.get()); + 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); - //on_mount(cx, || { - // let dom_node = node_ref.get::(); - //}); + + 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); @@ -669,6 +789,17 @@ async fn SelectedUI(cx: Scope<'_>) -> View { } }; + 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 { @@ -692,13 +823,50 @@ async fn SelectedUI(cx: Scope<'_>) -> View { view!{ cx, div(class="modal-content"){ - p{ - div(class="select-book"){ + + div(class="more-info"){ + div(class="more-info-buttons"){ button(class="close", on:click=handle_close){ "CLOSE" } - img(src=coverurl.get(),width="200") - (format!("{:?}",displ_book.get())) + 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: "} + (format!("{}",dollink.get())) + br{} + label{"ISBN: "} + (format!("{}",disbn.get())) + br{} + label{"GoodReads Link: "} + (format!("{}",dgoodread.get())) + br{} + label{"Description: "} + (format!("{}",ddesc.get())) + br{} + label{"Comments: "} + (format!("{}",dcomments.get())) + br{} + label{"Tags: "} + (format!("{}",dtags.get())) + br{} + } } }} else { @@ -713,22 +881,48 @@ async fn SelectedUI(cx: Scope<'_>) -> View { #[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); }; let handle_sub = move |_| { - app_state.pagenum.set(*app_state.pagenum.get()-1); + if *app_state.pagenum.get()>1 { + app_state.pagenum.set(*app_state.pagenum.get()-1); + } + }; + + 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); + } else { + currpg.set((*app_state.pagenum.get()).to_string()); + } + + } }; view! {cx, - p { + div { (if *app_state.openlibrary.get() == false || *app_state.internalsearch.get() == true { view!{ cx, - - button(class="page", on:click=handle_sub){ "Page -" } - (format!("{:?}",app_state.pagenum.get())) - button(class="page", on:click=handle_add){ "Page +" } + label{"PAGE "} + input(ref=input_ref,bind:value=currpg,on:keyup=handle_submit,class="page-input") + (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,""} @@ -747,22 +941,26 @@ fn App(cx: Scope) -> View { openlibrary: create_rc_signal(bool::default()), internalsearch: create_rc_signal(bool::default()), 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(bool::default()), }; provide_context(cx, app_state); view! { cx, div { Header {} + div(class="main"){ AddingUI{} SelectedUI{} ListOL {} ListDB{} - PageBar{} + } + //PageBar{} } } }