v0.15-WORKINGV1
This commit is contained in:
35
Dockerfile
35
Dockerfile
@@ -8,41 +8,20 @@ WORKDIR /usr/src/app
|
|||||||
RUN rustup target add wasm32-unknown-unknown
|
RUN rustup target add wasm32-unknown-unknown
|
||||||
#RUN export CARGO_BUILD_JOBS = 1
|
#RUN export CARGO_BUILD_JOBS = 1
|
||||||
ENV CARGO_BUILD_JOBS=1
|
ENV CARGO_BUILD_JOBS=1
|
||||||
|
|
||||||
RUN cargo install trunk
|
RUN cargo install trunk
|
||||||
|
|
||||||
RUN mkdir frontend
|
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
|
||||||
RUN mkdir frontend/src
|
|
||||||
RUN mkdir backend
|
COPY ./dummy.rs ./dummy.rs
|
||||||
RUN mkdir backend/src
|
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
|
||||||
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
|
|
||||||
|
|
||||||
COPY ./frontend/Cargo.toml /usr/src/app/frontend
|
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 ./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 ./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 ./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 ./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 ./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 ./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/index.html /usr/src/app/frontend/
|
||||||
COPY ./frontend/css/index.css /usr/src/app/frontend/css
|
COPY ./frontend/css/index.css /usr/src/app/frontend/css
|
||||||
COPY ./frontend/Trunk.toml /usr/src/app/frontend/
|
COPY ./frontend/Trunk.toml /usr/src/app/frontend/
|
||||||
@@ -52,15 +31,15 @@ RUN cd backend && cargo build --release
|
|||||||
|
|
||||||
COPY . /usr/src/app
|
COPY . /usr/src/app
|
||||||
RUN cp .prod .env
|
RUN cp .prod .env
|
||||||
#RUN cd frontend && trunk build --release
|
RUN cd frontend && trunk build --release
|
||||||
RUN cd backend && cargo build --release
|
RUN cd backend && cargo build --release
|
||||||
|
|
||||||
FROM alpine:latest
|
FROM alpine:latest
|
||||||
|
|
||||||
WORKDIR /usr/src/app
|
WORKDIR /usr/src/app
|
||||||
COPY --from=builder /usr/src/app/backend/target/release/backend /usr/src/app/booksman
|
COPY --from=builder /usr/src/app/backend/target/release/backend /usr/src/app/booksman
|
||||||
COPY ./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/dist /usr/src/dist
|
||||||
COPY --from=builder /usr/src/app/.env /usr/src/app/.env
|
COPY --from=builder /usr/src/app/.env /usr/src/app/.env
|
||||||
RUN chmod +x /usr/src/app/booksman
|
RUN chmod +x /usr/src/app/booksman
|
||||||
|
|
||||||
|
|||||||
1
backend/Cargo.lock
generated
1
backend/Cargo.lock
generated
@@ -433,6 +433,7 @@ dependencies = [
|
|||||||
"axum-extra",
|
"axum-extra",
|
||||||
"booksman-orm",
|
"booksman-orm",
|
||||||
"booksman-search",
|
"booksman-search",
|
||||||
|
"chrono",
|
||||||
"clap",
|
"clap",
|
||||||
"dotenvy",
|
"dotenvy",
|
||||||
"entity",
|
"entity",
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ tower-http = { version = "^0.3", features = ["full"] }
|
|||||||
tracing = "^0.1"
|
tracing = "^0.1"
|
||||||
tracing-subscriber = "^0.3"
|
tracing-subscriber = "^0.3"
|
||||||
itertools = "0.10"
|
itertools = "0.10"
|
||||||
|
chrono = "0.4"
|
||||||
|
|
||||||
[dependencies.sea-orm]
|
[dependencies.sea-orm]
|
||||||
version = "^0.9.2" # sea-orm version
|
version = "^0.9.2" # sea-orm version
|
||||||
|
|||||||
@@ -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 ::entity::entities::{book,book_author,book_person,book_place,book_subject,book_time,book_isbn};
|
||||||
use std::env;
|
use std::env;
|
||||||
use migration::{Migrator, MigratorTrait};
|
use migration::{Migrator, MigratorTrait};
|
||||||
|
use chrono::Local;
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize, Debug, Clone)]
|
#[derive(Deserialize, Serialize, Debug, Clone)]
|
||||||
struct BookUI {
|
struct BookUI {
|
||||||
@@ -605,7 +606,7 @@ async fn list_search_book(
|
|||||||
|
|
||||||
let mut resbooks: Vec<BookUI> = Vec::with_capacity(24);
|
let mut resbooks: Vec<BookUI> = Vec::with_capacity(24);
|
||||||
|
|
||||||
for bookmeili in books.into_iter() {
|
for bookmeili in books.0.into_iter() {
|
||||||
let mut cover = bookmeili.clone().cover;
|
let mut cover = bookmeili.clone().cover;
|
||||||
if cover!="".to_string() {
|
if cover!="".to_string() {
|
||||||
cover = format!("{}/images/{}",backend_url,cover);
|
cover = format!("{}/images/{}",backend_url,cover);
|
||||||
@@ -637,7 +638,7 @@ let mut resbooks: Vec<BookUI> = Vec::with_capacity(24);
|
|||||||
}
|
}
|
||||||
|
|
||||||
let res = PaginatedBookUIList{
|
let res = PaginatedBookUIList{
|
||||||
num_pages: 10,
|
num_pages: (books.1/24) as u32,
|
||||||
books: resbooks
|
books: resbooks
|
||||||
};
|
};
|
||||||
return Json(res);
|
return Json(res);
|
||||||
@@ -668,7 +669,7 @@ async fn create_book(
|
|||||||
let img_bytes = reqwest::get(cover.unwrap()).await.unwrap().bytes().await.unwrap();
|
let img_bytes = reqwest::get(cover.unwrap()).await.unwrap().bytes().await.unwrap();
|
||||||
let image = image::load_from_memory(&img_bytes).unwrap();
|
let image = image::load_from_memory(&img_bytes).unwrap();
|
||||||
let temp_cover = doc_sent.cover.clone().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");
|
image.save(format!("{}/{}",images_dir,img_id)).expect("Failed to save image");
|
||||||
cover = Some(img_id.to_string());
|
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),
|
median_page_count: doc_sent.median_page_count.unwrap_or(0),
|
||||||
goodread_id: doc_sent.goodread_id.unwrap_or("".to_string()),
|
goodread_id: doc_sent.goodread_id.unwrap_or("".to_string()),
|
||||||
description: doc_sent.description.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()),
|
location: doc_sent.location.unwrap_or("".to_string()),
|
||||||
time_added: doc_sent.time_added.unwrap_or("".to_string()),
|
time_added: doc_sent.time_added.unwrap_or("".to_string()),
|
||||||
rating: doc_sent.rating.unwrap_or(0),
|
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 img_bytes = img_resp.unwrap().bytes();
|
||||||
let image = image::load_from_memory(&img_bytes).unwrap();
|
let image = image::load_from_memory(&img_bytes).unwrap();
|
||||||
let temp_cover = doc_sent.cover.clone().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");
|
image.save(format!("{}/{}",images_dir,img_id)).expect("Failed to save image");
|
||||||
cover = Some(img_id.to_string());
|
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),
|
median_page_count: doc_sent.median_page_count.unwrap_or(0),
|
||||||
goodread_id: doc_sent.goodread_id.unwrap_or("".to_string()),
|
goodread_id: doc_sent.goodread_id.unwrap_or("".to_string()),
|
||||||
description: doc_sent.description.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()),
|
location: doc_sent.location.unwrap_or("".to_string()),
|
||||||
time_added: doc_sent.time_added.unwrap_or("".to_string()),
|
time_added: doc_sent.time_added.unwrap_or("".to_string()),
|
||||||
rating: doc_sent.rating.unwrap_or(0),
|
rating: doc_sent.rating.unwrap_or(0),
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
use chrono::DateTime;
|
//use chrono::DateTime;
|
||||||
use sea_orm_migration::prelude::*;
|
use sea_orm_migration::prelude::*;
|
||||||
|
|
||||||
#[derive(DeriveMigrationName)]
|
#[derive(DeriveMigrationName)]
|
||||||
@@ -36,7 +36,7 @@ impl MigrationTrait for Migration {
|
|||||||
.col(ColumnDef::new(Book::Comments).string())
|
.col(ColumnDef::new(Book::Comments).string())
|
||||||
.to_owned(),
|
.to_owned(),
|
||||||
)
|
)
|
||||||
.await;
|
.await.expect("Migration failed");
|
||||||
|
|
||||||
manager
|
manager
|
||||||
.create_table(
|
.create_table(
|
||||||
@@ -62,7 +62,7 @@ impl MigrationTrait for Migration {
|
|||||||
)
|
)
|
||||||
.to_owned(),
|
.to_owned(),
|
||||||
)
|
)
|
||||||
.await;
|
.await.expect("Migration failed");
|
||||||
|
|
||||||
manager
|
manager
|
||||||
.create_table(
|
.create_table(
|
||||||
@@ -88,7 +88,7 @@ impl MigrationTrait for Migration {
|
|||||||
)
|
)
|
||||||
.to_owned(),
|
.to_owned(),
|
||||||
)
|
)
|
||||||
.await;
|
.await.expect("Migration failed");
|
||||||
|
|
||||||
manager
|
manager
|
||||||
.create_table(
|
.create_table(
|
||||||
@@ -114,7 +114,7 @@ impl MigrationTrait for Migration {
|
|||||||
)
|
)
|
||||||
.to_owned(),
|
.to_owned(),
|
||||||
)
|
)
|
||||||
.await;
|
.await.expect("Migration failed");
|
||||||
|
|
||||||
manager
|
manager
|
||||||
.create_table(
|
.create_table(
|
||||||
@@ -140,7 +140,7 @@ impl MigrationTrait for Migration {
|
|||||||
)
|
)
|
||||||
.to_owned(),
|
.to_owned(),
|
||||||
)
|
)
|
||||||
.await;
|
.await.expect("Migration failed");
|
||||||
|
|
||||||
manager
|
manager
|
||||||
.create_table(
|
.create_table(
|
||||||
@@ -166,7 +166,7 @@ impl MigrationTrait for Migration {
|
|||||||
)
|
)
|
||||||
.to_owned(),
|
.to_owned(),
|
||||||
)
|
)
|
||||||
.await;
|
.await.expect("Migration failed");
|
||||||
|
|
||||||
manager
|
manager
|
||||||
.create_table(
|
.create_table(
|
||||||
@@ -201,22 +201,22 @@ impl MigrationTrait for Migration {
|
|||||||
|
|
||||||
manager
|
manager
|
||||||
.drop_table(Table::drop().table(Book::Table).to_owned())
|
.drop_table(Table::drop().table(Book::Table).to_owned())
|
||||||
.await;
|
.await.expect("Drop failed");
|
||||||
manager
|
manager
|
||||||
.drop_table(Table::drop().table(BookAuthor::Table).to_owned())
|
.drop_table(Table::drop().table(BookAuthor::Table).to_owned())
|
||||||
.await;
|
.await.expect("Drop failed");
|
||||||
manager
|
manager
|
||||||
.drop_table(Table::drop().table(BookPerson::Table).to_owned())
|
.drop_table(Table::drop().table(BookPerson::Table).to_owned())
|
||||||
.await;
|
.await.expect("Drop failed");
|
||||||
manager
|
manager
|
||||||
.drop_table(Table::drop().table(BookPlace::Table).to_owned())
|
.drop_table(Table::drop().table(BookPlace::Table).to_owned())
|
||||||
.await;
|
.await.expect("Drop failed");
|
||||||
manager
|
manager
|
||||||
.drop_table(Table::drop().table(BookSubject::Table).to_owned())
|
.drop_table(Table::drop().table(BookSubject::Table).to_owned())
|
||||||
.await;
|
.await.expect("Drop failed");
|
||||||
manager
|
manager
|
||||||
.drop_table(Table::drop().table(BookTime::Table).to_owned())
|
.drop_table(Table::drop().table(BookTime::Table).to_owned())
|
||||||
.await;
|
.await.expect("Drop failed");
|
||||||
manager
|
manager
|
||||||
.drop_table(Table::drop().table(BookISBN::Table).to_owned())
|
.drop_table(Table::drop().table(BookISBN::Table).to_owned())
|
||||||
.await
|
.await
|
||||||
|
|||||||
@@ -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<Vec<BookMeili>, meilisearch_sdk::errors::Error> {
|
pub async fn search_book(search: &str, page: usize, client: &Client) -> Result<(Vec<BookMeili>, usize), meilisearch_sdk::errors::Error> {
|
||||||
// An index is where the documents are stored.
|
// An index is where the documents are stored.
|
||||||
let books = client.index("books");
|
let books = client.index("books");
|
||||||
let results : SearchResults<BookMeili> = books.search().with_query(search).with_offset((page-1)*24)
|
let results : SearchResults<BookMeili> = 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<V
|
|||||||
|
|
||||||
let formatted_results : Vec<BookMeili> = (results.hits).iter().map(|r| r.result.clone()).collect();
|
let formatted_results : Vec<BookMeili> = (results.hits).iter().map(|r| r.result.clone()).collect();
|
||||||
//.iter()s.map(|r| r.formatted_result.unwrap()).collect();
|
//.iter()s.map(|r| r.formatted_result.unwrap()).collect();
|
||||||
return Ok(formatted_results);
|
return Ok((formatted_results, results.estimated_total_hits));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
53
frontend/Cargo.lock
generated
53
frontend/Cargo.lock
generated
@@ -73,10 +73,39 @@ dependencies = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "dotenvy"
|
name = "dotenv"
|
||||||
version = "0.15.6"
|
version = "0.15.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
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]]
|
[[package]]
|
||||||
name = "env_logger"
|
name = "env_logger"
|
||||||
@@ -97,9 +126,10 @@ version = "0.1.0"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"console_error_panic_hook",
|
"console_error_panic_hook",
|
||||||
"console_log",
|
"console_log",
|
||||||
"dotenvy",
|
"dotenv_codegen",
|
||||||
"env_logger",
|
"env_logger",
|
||||||
"gloo-net 0.2.4",
|
"gloo-net 0.2.4",
|
||||||
|
"itertools",
|
||||||
"log",
|
"log",
|
||||||
"reqwasm",
|
"reqwasm",
|
||||||
"serde",
|
"serde",
|
||||||
@@ -306,6 +336,15 @@ dependencies = [
|
|||||||
"hashbrown",
|
"hashbrown",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "itertools"
|
||||||
|
version = "0.10.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473"
|
||||||
|
dependencies = [
|
||||||
|
"either",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "itoa"
|
name = "itoa"
|
||||||
version = "1.0.3"
|
version = "1.0.3"
|
||||||
@@ -402,6 +441,12 @@ version = "0.1.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
|
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "proc-macro-hack"
|
||||||
|
version = "0.5.19"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "proc-macro2"
|
name = "proc-macro2"
|
||||||
version = "1.0.43"
|
version = "1.0.43"
|
||||||
|
|||||||
@@ -7,11 +7,13 @@ edition = "2021"
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
console_error_panic_hook = "0.1.7"
|
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"
|
env_logger = "0.9.0"
|
||||||
gloo-net = "^0.2"
|
gloo-net = "^0.2"
|
||||||
#gloo-utils = {version = "0.1.5", features =["serde"]}
|
#gloo-utils = {version = "0.1.5", features =["serde"]}
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
|
itertools = "0.10"
|
||||||
console_log = { version = "0.2", features = ["color"] }
|
console_log = { version = "0.2", features = ["color"] }
|
||||||
#reqwest = {version = "0.11.11", features = ["blocking", "json"]}
|
#reqwest = {version = "0.11.11", features = ["blocking", "json"]}
|
||||||
reqwasm = {version = "0.5.0", features = ["json"]}
|
reqwasm = {version = "0.5.0", features = ["json"]}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
[build]
|
[build]
|
||||||
target = "index.html"
|
target = "index.html"
|
||||||
dist = "../dist"
|
dist = "../dist"
|
||||||
|
public_url = "/assets/"
|
||||||
|
|
||||||
[[proxy]]
|
[[proxy]]
|
||||||
backend = "http://[::1]:8081/api/"
|
backend = "http://[::1]:8081/api/"
|
||||||
|
|||||||
@@ -28,19 +28,41 @@ body {
|
|||||||
}
|
}
|
||||||
/* Modal Content/Box */
|
/* Modal Content/Box */
|
||||||
.modal-content {
|
.modal-content {
|
||||||
margin: 10% auto; /* 15% from the top and centered */
|
margin: 5% auto; /* 15% from the top and centered */
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
border: 1px solid #888;
|
border: 1px solid #888;
|
||||||
width: 80%; /* Could be more or less, depending on screen size */
|
width: 80%; /* Could be more or less, depending on screen size */
|
||||||
height: 80%;
|
height: 90%;
|
||||||
overflow: auto; /* Enable scroll if needed */
|
overflow: auto; /* Enable scroll if needed */
|
||||||
background-color: rgb(0,0,0); /* Fallback color */
|
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 */
|
/* Float three header columns side by side */
|
||||||
.header-column {
|
.header-column {
|
||||||
float: left;
|
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;
|
padding: 0 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -49,6 +71,8 @@ body {
|
|||||||
float: left;
|
float: left;
|
||||||
width: 25%;
|
width: 25%;
|
||||||
padding: 0 10px;
|
padding: 0 10px;
|
||||||
|
margin-top: 10px;
|
||||||
|
margin-bottom: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Remove extra left and right margins, due to padding in columns */
|
/* Remove extra left and right margins, due to padding in columns */
|
||||||
@@ -70,6 +94,105 @@ body {
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
background-color: #f1f1f1;
|
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 */
|
/* Responsive columns - one column layout (vertical) on small screens */
|
||||||
@@ -80,3 +203,9 @@ body {
|
|||||||
margin-bottom: 20px;
|
margin-bottom: 20px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: 800px) {
|
||||||
|
.main {
|
||||||
|
margin-top: 80px; /* Add a top margin to avoid content overlay */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -3,9 +3,9 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<link rel="shortcut icon" type="image/x-icon" href="data:image/x-icon;,">
|
<link rel="shortcut icon" type="image/x-icon" href="data:image/x-icon;,">
|
||||||
<link data-trunk rel="css" href="css/index.css"/>
|
<link data-trunk rel="inline" href="css/index.css" />
|
||||||
|
<base data-trunk-public-url/>
|
||||||
<title>Book Manager</title>
|
<title>Book Manager</title>
|
||||||
<base href="/"/>
|
|
||||||
</head>
|
</head>
|
||||||
<body></body>
|
<body></body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
use log::info;
|
use log::info;
|
||||||
|
//use std::env;
|
||||||
//use log::Level;
|
//use log::Level;
|
||||||
use reqwasm::http::Request;
|
use reqwasm::http::Request;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
@@ -9,6 +10,10 @@ use sycamore::prelude::*;
|
|||||||
use sycamore_router::Route;
|
use sycamore_router::Route;
|
||||||
use wasm_bindgen::JsCast;
|
use wasm_bindgen::JsCast;
|
||||||
use web_sys::{Event, HtmlInputElement, KeyboardEvent}; // 0.3.5
|
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)]
|
#[derive(Deserialize, Serialize, Debug, Default, Clone, PartialEq, Hash, Eq)]
|
||||||
pub struct BookUI {
|
pub struct BookUI {
|
||||||
@@ -38,7 +43,7 @@ pub struct BookUIProp {
|
|||||||
bookitem: BookUI,
|
bookitem: BookUI,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize, Debug)]
|
#[derive(Deserialize, Serialize, Debug, Clone)]
|
||||||
struct PaginatedBookUIList {
|
struct PaginatedBookUIList {
|
||||||
num_pages: u32,
|
num_pages: u32,
|
||||||
books: Vec<BookUI>,
|
books: Vec<BookUI>,
|
||||||
@@ -51,11 +56,13 @@ pub struct AppState {
|
|||||||
pub openlibrary: RcSignal<bool>,
|
pub openlibrary: RcSignal<bool>,
|
||||||
pub internalsearch: RcSignal<bool>,
|
pub internalsearch: RcSignal<bool>,
|
||||||
pub pagenum: RcSignal<u32>,
|
pub pagenum: RcSignal<u32>,
|
||||||
|
pub maxpage: RcSignal<u32>,
|
||||||
pub adding: RcSignal<bool>,
|
pub adding: RcSignal<bool>,
|
||||||
pub updating: RcSignal<bool>,
|
pub updating: RcSignal<bool>,
|
||||||
pub displaying: RcSignal<bool>,
|
pub displaying: RcSignal<bool>,
|
||||||
pub addingbook: RcSignal<BookUI>,
|
pub addingbook: RcSignal<BookUI>,
|
||||||
pub displayingbook: RcSignal<BookUI>,
|
pub displayingbook: RcSignal<BookUI>,
|
||||||
|
pub refreshing: RcSignal<bool>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Route)]
|
#[derive(Route)]
|
||||||
@@ -69,8 +76,8 @@ enum AppRoutes {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn fetch_books(search: String) -> Result<Vec<BookUI>, reqwasm::Error> {
|
async fn fetch_books(search: String) -> Result<Vec<BookUI>, reqwasm::Error> {
|
||||||
dotenvy::dotenv().ok();
|
//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");
|
//env::var("BACKEND_URL").expect("BACKEND_URL is not set in .env file");
|
||||||
let url = format!("{}/api/search_openlibrary?search={}", backend_url, search);
|
let url = format!("{}/api/search_openlibrary?search={}", backend_url, search);
|
||||||
let resp = Request::get(&url).send().await?;
|
let resp = Request::get(&url).send().await?;
|
||||||
@@ -80,8 +87,8 @@ async fn fetch_books(search: String) -> Result<Vec<BookUI>, reqwasm::Error> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn search_books(search: String, page: u32) -> Result<PaginatedBookUIList, reqwasm::Error> {
|
async fn search_books(search: String, page: u32) -> Result<PaginatedBookUIList, reqwasm::Error> {
|
||||||
dotenvy::dotenv().ok();
|
let backend_url : &'static str = dotenv!("BACKEND_URL");
|
||||||
let backend_url = "http://localhost:8081";
|
//"http://localhost:8081";
|
||||||
//env::var("BACKEND_URL").expect("BACKEND_URL is not set in .env file");
|
//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 url = format!("{}/api/list_search?search={}&page={}",backend_url, search, page);
|
||||||
let resp = Request::get(&url).send().await?;
|
let resp = Request::get(&url).send().await?;
|
||||||
@@ -91,8 +98,7 @@ async fn search_books(search: String, page: u32) -> Result<PaginatedBookUIList,
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn add_book(record: BookUI) -> Result<reqwasm::http::Response, reqwasm::Error> {
|
async fn add_book(record: BookUI) -> Result<reqwasm::http::Response, reqwasm::Error> {
|
||||||
dotenvy::dotenv().ok();
|
let backend_url : &'static str = dotenv!("BACKEND_URL");
|
||||||
let backend_url = "http://localhost:8081";
|
|
||||||
//env::var("BACKEND_URL").expect("BACKEND_URL is not set in .env file");
|
//env::var("BACKEND_URL").expect("BACKEND_URL is not set in .env file");
|
||||||
let url = format!("{}/api/create", backend_url);
|
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?;
|
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<reqwasm::http::Response, reqwasm::Er
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn add_books_isbns(isbns: String) -> Result<reqwasm::http::Response, reqwasm::Error> {
|
async fn add_books_isbns(isbns: String) -> Result<reqwasm::http::Response, reqwasm::Error> {
|
||||||
dotenvy::dotenv().ok();
|
let backend_url : &'static str = dotenv!("BACKEND_URL");
|
||||||
let backend_url = "http://localhost:8081";
|
|
||||||
//env::var("BACKEND_URL").expect("BACKEND_URL is not set in .env file");
|
//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 url = format!("{}/api/create_by_isbn?isbns={}", backend_url, isbns);
|
||||||
let resp = Request::get(&url).send().await?;
|
let resp = Request::get(&url).send().await?;
|
||||||
@@ -111,8 +116,7 @@ async fn add_books_isbns(isbns: String) -> Result<reqwasm::http::Response, reqw
|
|||||||
|
|
||||||
|
|
||||||
async fn update_book(record: BookUI) -> Result<reqwasm::http::Response, reqwasm::Error> {
|
async fn update_book(record: BookUI) -> Result<reqwasm::http::Response, reqwasm::Error> {
|
||||||
dotenvy::dotenv().ok();
|
let backend_url : &'static str = dotenv!("BACKEND_URL");
|
||||||
let backend_url = "http://localhost:8081";
|
|
||||||
//env::var("BACKEND_URL").expect("BACKEND_URL is not set in .env file");
|
//env::var("BACKEND_URL").expect("BACKEND_URL is not set in .env file");
|
||||||
let url = format!("{}/api/update", backend_url);
|
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?;
|
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<reqwasm::http::Response, reqwasm:
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn delete_book(id: i32) -> Result<reqwasm::http::Response, reqwasm::Error> {
|
async fn delete_book(id: i32) -> Result<reqwasm::http::Response, reqwasm::Error> {
|
||||||
dotenvy::dotenv().ok();
|
let backend_url : &'static str = dotenv!("BACKEND_URL");
|
||||||
let backend_url = "http://localhost:8081";
|
|
||||||
//env::var("BACKEND_URL").expect("BACKEND_URL is not set in .env file");
|
//env::var("BACKEND_URL").expect("BACKEND_URL is not set in .env file");
|
||||||
let url = format!("{}/api/delete/{}", backend_url, id);
|
let url = format!("{}/api/delete/{}", backend_url, id);
|
||||||
let resp = Request::get(&url).send().await?;
|
let resp = Request::get(&url).send().await?;
|
||||||
@@ -129,8 +132,7 @@ async fn delete_book(id: i32) -> Result<reqwasm::http::Response, reqwasm::Error>
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn list_books(page: u32) -> Result<PaginatedBookUIList, reqwasm::Error> {
|
async fn list_books(page: u32) -> Result<PaginatedBookUIList, reqwasm::Error> {
|
||||||
dotenvy::dotenv().ok();
|
let backend_url : &'static str = dotenv!("BACKEND_URL");
|
||||||
let backend_url = "http://localhost:8081";
|
|
||||||
//env::var("BACKEND_URL").expect("BACKEND_URL is not set in .env file");
|
//env::var("BACKEND_URL").expect("BACKEND_URL is not set in .env file");
|
||||||
let url = format!("{}/api/list?page={}", backend_url, page);
|
let url = format!("{}/api/list?page={}", backend_url, page);
|
||||||
let resp = Request::get(&url).send().await?;
|
let resp = Request::get(&url).send().await?;
|
||||||
@@ -290,6 +292,9 @@ pub fn Header<G: Html>(cx: Scope) -> View<G> {
|
|||||||
)
|
)
|
||||||
button(on:click=click_addbulk) { "Add bulk ISBNs" }
|
button(on:click=click_addbulk) { "Add bulk ISBNs" }
|
||||||
}
|
}
|
||||||
|
div(class="header-page-column"){
|
||||||
|
PageBar{}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -304,6 +309,8 @@ async fn ListDB<G: Html>(cx: Scope<'_>) -> View<G> {
|
|||||||
app_state.pagenum.track();
|
app_state.pagenum.track();
|
||||||
app_state.openlibrary.track();
|
app_state.openlibrary.track();
|
||||||
app_state.internalsearch.track();
|
app_state.internalsearch.track();
|
||||||
|
app_state.refreshing.track();
|
||||||
|
app_state.pagenum.track();
|
||||||
|
|
||||||
info!(
|
info!(
|
||||||
"The state changed. New value: {}",
|
"The state changed. New value: {}",
|
||||||
@@ -311,23 +318,33 @@ async fn ListDB<G: Html>(cx: Scope<'_>) -> View<G> {
|
|||||||
);
|
);
|
||||||
|
|
||||||
if *app_state.openlibrary.get() == false {
|
if *app_state.openlibrary.get() == false {
|
||||||
|
app_state.refreshing.set(false);
|
||||||
|
|
||||||
if *app_state.internalsearch.get() == false {
|
if *app_state.internalsearch.get() == false {
|
||||||
spawn_local(async move {
|
spawn_local(async move {
|
||||||
|
let res = list_books(*app_state.pagenum.get())
|
||||||
|
.await.unwrap();
|
||||||
app_state.books.set(
|
app_state.books.set(
|
||||||
list_books(*app_state.pagenum.get())
|
res.books
|
||||||
.await
|
);
|
||||||
.unwrap().books
|
app_state.maxpage.set(
|
||||||
|
res.num_pages
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
spawn_local(async move {
|
spawn_local(async move {
|
||||||
|
let res = search_books(app_state.search.get().to_string(), *app_state.pagenum.get())
|
||||||
|
.await.unwrap();
|
||||||
app_state.books.set(
|
app_state.books.set(
|
||||||
search_books(app_state.search.get().to_string(), *app_state.pagenum.get())
|
res.books
|
||||||
.await
|
);
|
||||||
.unwrap().books)
|
app_state.maxpage.set(
|
||||||
})
|
res.num_pages
|
||||||
|
)
|
||||||
|
});
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
if *app_state.refreshing.get() == false {
|
||||||
spawn_local(async move {
|
spawn_local(async move {
|
||||||
if *app_state.search.get() != "" {
|
if *app_state.search.get() != "" {
|
||||||
if *app_state.openlibrary.get() == true {
|
if *app_state.openlibrary.get() == true {
|
||||||
@@ -340,6 +357,7 @@ async fn ListDB<G: Html>(cx: Scope<'_>) -> View<G> {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
let docs = create_memo(cx, || app_state.books.get().iter().cloned().collect::<Vec<_>>());
|
let docs = create_memo(cx, || app_state.books.get().iter().cloned().collect::<Vec<_>>());
|
||||||
@@ -381,12 +399,15 @@ pub fn BookDB<G: Html>(cx: Scope, bookitem: BookUIProp) -> View<G> {
|
|||||||
let temp = delete_book(bookdelete.id).await.unwrap();
|
let temp = delete_book(bookdelete.id).await.unwrap();
|
||||||
println!("{}",temp.status());
|
println!("{}",temp.status());
|
||||||
});
|
});
|
||||||
|
app_state.refreshing.set(true);
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let handle_update = move |_| {
|
let handle_update = move |_| {
|
||||||
app_state.adding.set(false);
|
app_state.adding.set(false);
|
||||||
app_state.updating.set(true);
|
app_state.updating.set(true);
|
||||||
app_state.addingbook.set(bookupdate.clone());
|
app_state.addingbook.set(bookupdate.clone());
|
||||||
|
app_state.refreshing.set(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
let handle_display = move |_| {
|
let handle_display = move |_| {
|
||||||
@@ -398,19 +419,29 @@ pub fn BookDB<G: Html>(cx: Scope, bookitem: BookUIProp) -> View<G> {
|
|||||||
view! { cx,
|
view! { cx,
|
||||||
div(class="column"){
|
div(class="column"){
|
||||||
div(class="card"){
|
div(class="card"){
|
||||||
|
|
||||||
|
div(class="card-buttons"){
|
||||||
|
|
||||||
button(class="delete", on:click=handle_delete){ "DEL-" }
|
button(class="delete", on:click=handle_delete){ "DEL-" }
|
||||||
button(class="update", on:click=handle_update){ "EDIT=" }
|
button(class="update", on:click=handle_update){ "EDIT=" }
|
||||||
button(class="info", on:click=handle_display){ "INFO+" }
|
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")
|
img(src=coverurl,width="100")
|
||||||
|
//}
|
||||||
|
}
|
||||||
|
|
||||||
(format!("{:?}",loctitle))
|
div(class="card-desc"){
|
||||||
br{}
|
(format!("{}",locdesc))
|
||||||
(format!("{:?}",locauthors))
|
}
|
||||||
br{}
|
|
||||||
(format!("{:?}",locdesc))
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -460,7 +491,6 @@ pub fn BookOL<G: Html>(cx: Scope, bookitem: BookUIProp) -> View<G> {
|
|||||||
let bookdisp=book.clone();
|
let bookdisp=book.clone();
|
||||||
let loctitle = bookitem.bookitem.clone().title.clone();
|
let loctitle = bookitem.bookitem.clone().title.clone();
|
||||||
let locauthors = bookitem.bookitem.clone().author_name.clone().unwrap_or(vec!["".to_string()]).join(", ");
|
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 coverurl = bookdisp.cover.clone().unwrap_or("NONE".to_string());
|
||||||
let app_state = use_context::<AppState>(cx);
|
let app_state = use_context::<AppState>(cx);
|
||||||
let handle_add = move |_| {
|
let handle_add = move |_| {
|
||||||
@@ -470,15 +500,20 @@ pub fn BookOL<G: Html>(cx: Scope, bookitem: BookUIProp) -> View<G> {
|
|||||||
|
|
||||||
view! { cx,
|
view! { cx,
|
||||||
div(class="column"){
|
div(class="column"){
|
||||||
div(class="card"){
|
div(class="card-openlibrary"){
|
||||||
img(src=coverurl,width="100")
|
button(class="add", on:click=handle_add){ "ADD+" }
|
||||||
|
div(class="card-main"){
|
||||||
|
|
||||||
(format!("{:?}",loctitle))
|
div(class="card-title"){
|
||||||
br{}
|
(format!("{}",loctitle))
|
||||||
(format!("{:?}",locauthors))
|
}
|
||||||
br{}
|
div(class="card-authors"){
|
||||||
(format!("{:?}",locdesc))
|
(format!("{}",locauthors))
|
||||||
button(class="add", on:click=handle_add){ "+" }
|
}
|
||||||
|
//div(class="card-image"){
|
||||||
|
img(src=coverurl,width="100")
|
||||||
|
//}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -587,6 +622,7 @@ info!("Adding book");
|
|||||||
app_state.addingbook.set(BookUI::default());
|
app_state.addingbook.set(BookUI::default());
|
||||||
app_state.updating.set(false);
|
app_state.updating.set(false);
|
||||||
app_state.adding.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 {
|
(if *app_state.adding.get() == true || *app_state.updating.get() == true {
|
||||||
view!{ cx,
|
view!{ cx,
|
||||||
div(class="modal-content"){
|
div(class="modal-content"){
|
||||||
|
div(class="input-buttons"){
|
||||||
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" }
|
button(class="add", on:click=handle_add){ "Add/Update book to DB" }
|
||||||
button(class="cancel", on:click=handle_cancel){ "Cancel" }
|
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]
|
#[component]
|
||||||
async fn SelectedUI<G: Html>(cx: Scope<'_>) -> View<G> {
|
async fn SelectedUI<G: Html>(cx: Scope<'_>) -> View<G> {
|
||||||
let app_state = use_context::<AppState>(cx);
|
let app_state = use_context::<AppState>(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 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 node_ref = create_node_ref(cx);
|
||||||
//on_mount(cx, || {
|
|
||||||
// let dom_node = node_ref.get::<DomNode>();
|
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::<Vec<String>>().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::<Vec<String>>().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::<Vec<String>>().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::<Vec<String>>().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 |_| {
|
let handle_close = move |_| {
|
||||||
app_state.displaying.set(false);
|
app_state.displaying.set(false);
|
||||||
@@ -669,6 +789,17 @@ async fn SelectedUI<G: Html>(cx: Scope<'_>) -> View<G> {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let handle_edit = move |_| {
|
||||||
|
app_state.displaying.set(false);
|
||||||
|
let dom_node = node_ref.try_get::<DomNode>();
|
||||||
|
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, || {
|
create_effect(cx, || {
|
||||||
if *app_state.displaying.get() == true {
|
if *app_state.displaying.get() == true {
|
||||||
@@ -692,12 +823,49 @@ async fn SelectedUI<G: Html>(cx: Scope<'_>) -> View<G> {
|
|||||||
view!{ cx,
|
view!{ cx,
|
||||||
|
|
||||||
div(class="modal-content"){
|
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" }
|
button(class="close", on:click=handle_close){ "CLOSE" }
|
||||||
img(src=coverurl.get(),width="200")
|
button(class="close", on:click=handle_edit){ "EDIT" }
|
||||||
(format!("{:?}",displ_book.get()))
|
|
||||||
}
|
}
|
||||||
|
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{}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
@@ -713,22 +881,48 @@ async fn SelectedUI<G: Html>(cx: Scope<'_>) -> View<G> {
|
|||||||
#[component]
|
#[component]
|
||||||
async fn PageBar<G: Html>(cx: Scope<'_>) -> View<G> {
|
async fn PageBar<G: Html>(cx: Scope<'_>) -> View<G> {
|
||||||
let app_state = use_context::<AppState>(cx);
|
let app_state = use_context::<AppState>(cx);
|
||||||
|
let currpg = create_signal(cx, (*app_state.pagenum.get()).to_string());
|
||||||
|
let input_ref = create_node_ref(cx);
|
||||||
|
|
||||||
let handle_add = move |_| {
|
let handle_add = move |_| {
|
||||||
app_state.pagenum.set(*app_state.pagenum.get()+1);
|
app_state.pagenum.set(*app_state.pagenum.get()+1);
|
||||||
};
|
};
|
||||||
|
|
||||||
let handle_sub = move |_| {
|
let handle_sub = move |_| {
|
||||||
|
if *app_state.pagenum.get()>1 {
|
||||||
app_state.pagenum.set(*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::<u32>().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,
|
view! {cx,
|
||||||
p {
|
div {
|
||||||
(if *app_state.openlibrary.get() == false || *app_state.internalsearch.get() == true {
|
(if *app_state.openlibrary.get() == false || *app_state.internalsearch.get() == true {
|
||||||
view!{ cx,
|
view!{ cx,
|
||||||
|
label{"PAGE "}
|
||||||
button(class="page", on:click=handle_sub){ "Page -" }
|
input(ref=input_ref,bind:value=currpg,on:keyup=handle_submit,class="page-input")
|
||||||
(format!("{:?}",app_state.pagenum.get()))
|
(if *app_state.pagenum.get()>1 {
|
||||||
button(class="page", on:click=handle_add){ "Page +" }
|
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 {
|
}else {
|
||||||
view!{cx,""}
|
view!{cx,""}
|
||||||
@@ -747,22 +941,26 @@ fn App<G: Html>(cx: Scope) -> View<G> {
|
|||||||
openlibrary: create_rc_signal(bool::default()),
|
openlibrary: create_rc_signal(bool::default()),
|
||||||
internalsearch: create_rc_signal(bool::default()),
|
internalsearch: create_rc_signal(bool::default()),
|
||||||
pagenum: create_rc_signal(1),
|
pagenum: create_rc_signal(1),
|
||||||
|
maxpage: create_rc_signal(1),
|
||||||
adding: create_rc_signal(bool::default()),
|
adding: create_rc_signal(bool::default()),
|
||||||
updating: create_rc_signal(bool::default()),
|
updating: create_rc_signal(bool::default()),
|
||||||
addingbook: create_rc_signal(BookUI::default()),
|
addingbook: create_rc_signal(BookUI::default()),
|
||||||
displaying: create_rc_signal(bool::default()),
|
displaying: create_rc_signal(bool::default()),
|
||||||
displayingbook: create_rc_signal(BookUI::default()),
|
displayingbook: create_rc_signal(BookUI::default()),
|
||||||
|
refreshing: create_rc_signal(bool::default()),
|
||||||
};
|
};
|
||||||
provide_context(cx, app_state);
|
provide_context(cx, app_state);
|
||||||
view! {
|
view! {
|
||||||
cx,
|
cx,
|
||||||
div {
|
div {
|
||||||
Header {}
|
Header {}
|
||||||
|
div(class="main"){
|
||||||
AddingUI{}
|
AddingUI{}
|
||||||
SelectedUI{}
|
SelectedUI{}
|
||||||
ListOL {}
|
ListOL {}
|
||||||
ListDB{}
|
ListDB{}
|
||||||
PageBar{}
|
}
|
||||||
|
//PageBar{}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user