diff --git a/Cargo.lock b/Cargo.lock index a97b032..79d70fc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -629,6 +629,7 @@ dependencies = [ "actix-web-httpauth", "actix-web-opentelemetry", "async-trait", + "bytes", "cart_manager", "chrono", "config", @@ -1133,6 +1134,7 @@ dependencies = [ "actix 0.13.0", "actix-rt", "actix-web", + "bytes", "config", "database_manager", "dotenv", @@ -1396,6 +1398,8 @@ version = "0.1.0" dependencies = [ "actix 0.13.0", "actix-rt", + "actix-web", + "bytes", "chrono", "config", "log", diff --git a/actors/fs_manager/Cargo.toml b/actors/fs_manager/Cargo.toml index accce94..0c4fbc2 100644 --- a/actors/fs_manager/Cargo.toml +++ b/actors/fs_manager/Cargo.toml @@ -7,7 +7,10 @@ edition = "2021" model = { path = "../../shared/model" } config = { path = "../../shared/config" } +bytes = { version = "1.1.0" } + actix = { version = "0.13", features = [] } +actix-web = { version = "4.0.1" } actix-rt = { version = "2.7", features = [] } thiserror = { version = "1.0.31" } diff --git a/actors/fs_manager/src/lib.rs b/actors/fs_manager/src/lib.rs index 3ec9d08..74e05fa 100644 --- a/actors/fs_manager/src/lib.rs +++ b/actors/fs_manager/src/lib.rs @@ -157,7 +157,7 @@ pub struct WriteResult { #[rtype(result = "Result")] pub struct WriteFile { pub file_name: String, - pub stream: tokio::sync::mpsc::UnboundedReceiver, + pub stream: tokio::sync::mpsc::UnboundedReceiver, } fs_async_handler!(WriteFile, write_file, WriteResult); @@ -210,7 +210,7 @@ pub(crate) async fn write_file(msg: WriteFile, config: SharedAppConfig) -> Resul if counter % 100_000 == 0 { log::debug!("Wrote {} for {:?}", counter, file_name); } - match file.write(&[b]) { + match file.write(&b) { Ok(_) => {} Err(e) if e.kind() == std::io::ErrorKind::StorageFull => return Err(Error::NoSpace), Err(e) => return Err(Error::CantWrite(e)), @@ -221,6 +221,6 @@ pub(crate) async fn write_file(msg: WriteFile, config: SharedAppConfig) -> Resul Ok(WriteResult { file_name: FileName::new(file_name), unique_name: UniqueName::new(unique_name), - local_path: LocalPath::from(path.to_str().unwrap_or_default().to_string()), + local_path: LocalPath::new(path.to_str().unwrap_or_default()), }) } diff --git a/api/Cargo.toml b/api/Cargo.toml index 930f172..8a14866 100644 --- a/api/Cargo.toml +++ b/api/Cargo.toml @@ -17,6 +17,8 @@ fs_manager = { path = "../actors/fs_manager" } human-panic = { version = "1.0.3" } +bytes = { version = "1.1.0" } + actix = { version = "0.13", features = [] } actix-rt = { version = "2.7", features = [] } actix-web = { version = "4.0", features = [] } diff --git a/api/assets/svg/plate.svg b/api/assets/svg/plate.svg new file mode 100644 index 0000000..9f26867 --- /dev/null +++ b/api/assets/svg/plate.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/api/src/routes/admin/api_v1/uploads.rs b/api/src/routes/admin/api_v1/uploads.rs index d747ca3..f388aad 100644 --- a/api/src/routes/admin/api_v1/uploads.rs +++ b/api/src/routes/admin/api_v1/uploads.rs @@ -51,11 +51,9 @@ async fn upload_product_image( }; let read = async { while let Some(Ok(data)) = field.next().await { - for b in data { - if let Err(e) = tx.send(b) { - log::error!("{e:?}"); - return Err(UploadError::InvalidName); - } + if let Err(e) = tx.send(data) { + log::error!("{e:?}"); + return Err(UploadError::InvalidName); } } Ok(()) diff --git a/api/src/routes/public.rs b/api/src/routes/public.rs index c2ade6e..80980c0 100644 --- a/api/src/routes/public.rs +++ b/api/src/routes/public.rs @@ -84,6 +84,7 @@ async fn svg(path: Path) -> HttpResponse { "sweets" => serve_svg!("sweets"), "vegetables" => serve_svg!("vegetables"), "memory" => serve_svg!("memory"), + "plates" => serve_svg!("plate"), _ => HttpResponse::NotFound().finish(), } } diff --git a/assets/examples/backup-images/pexels-Venus-HD-Make-up-and-perfume-2587370.webp b/assets/examples/backup-images/pexels-Venus-HD-Make-up-and-perfume-2587370.webp new file mode 100644 index 0000000..b02cc94 Binary files /dev/null and b/assets/examples/backup-images/pexels-Venus-HD-Make-up-and-perfume-2587370.webp differ diff --git a/assets/examples/backup-images/pexels-agnese-lunecka-10322857.webp b/assets/examples/backup-images/pexels-agnese-lunecka-10322857.webp new file mode 100644 index 0000000..48e7a9c Binary files /dev/null and b/assets/examples/backup-images/pexels-agnese-lunecka-10322857.webp differ diff --git a/assets/examples/backup-images/pexels-agnese-lunecka-11179383.webp b/assets/examples/backup-images/pexels-agnese-lunecka-11179383.webp new file mode 100644 index 0000000..ccb4887 Binary files /dev/null and b/assets/examples/backup-images/pexels-agnese-lunecka-11179383.webp differ diff --git a/assets/examples/backup-images/pexels-agnese-lunecka-11328773.webp b/assets/examples/backup-images/pexels-agnese-lunecka-11328773.webp new file mode 100644 index 0000000..d9267e1 Binary files /dev/null and b/assets/examples/backup-images/pexels-agnese-lunecka-11328773.webp differ diff --git a/assets/examples/backup-images/pexels-alex-azabache-3907507.webp b/assets/examples/backup-images/pexels-alex-azabache-3907507.webp new file mode 100644 index 0000000..b534d57 Binary files /dev/null and b/assets/examples/backup-images/pexels-alex-azabache-3907507.webp differ diff --git a/assets/examples/backup-images/pexels-binoid-cbd-3612182.webp b/assets/examples/backup-images/pexels-binoid-cbd-3612182.webp new file mode 100644 index 0000000..a218d9f Binary files /dev/null and b/assets/examples/backup-images/pexels-binoid-cbd-3612182.webp differ diff --git a/assets/examples/backup-images/pexels-caio-1279107.webp b/assets/examples/backup-images/pexels-caio-1279107.webp new file mode 100644 index 0000000..058e02f Binary files /dev/null and b/assets/examples/backup-images/pexels-caio-1279107.webp differ diff --git a/assets/examples/backup-images/pexels-eprism-studio-335257.webp b/assets/examples/backup-images/pexels-eprism-studio-335257.webp new file mode 100644 index 0000000..d925c69 Binary files /dev/null and b/assets/examples/backup-images/pexels-eprism-studio-335257.webp differ diff --git a/assets/examples/backup-images/pexels-gabriel-freytez-341523.webp b/assets/examples/backup-images/pexels-gabriel-freytez-341523.webp new file mode 100644 index 0000000..962795f Binary files /dev/null and b/assets/examples/backup-images/pexels-gabriel-freytez-341523.webp differ diff --git a/assets/examples/backup-images/pexels-jess-bailey-designs-913135.webp b/assets/examples/backup-images/pexels-jess-bailey-designs-913135.webp new file mode 100644 index 0000000..7ce0dce Binary files /dev/null and b/assets/examples/backup-images/pexels-jess-bailey-designs-913135.webp differ diff --git a/assets/examples/backup-images/pexels-luis-quintero-1738641.webp b/assets/examples/backup-images/pexels-luis-quintero-1738641.webp new file mode 100644 index 0000000..59f5e8b Binary files /dev/null and b/assets/examples/backup-images/pexels-luis-quintero-1738641.webp differ diff --git a/assets/examples/backup-images/pexels-math-90946.webp b/assets/examples/backup-images/pexels-math-90946.webp new file mode 100644 index 0000000..e59e44b Binary files /dev/null and b/assets/examples/backup-images/pexels-math-90946.webp differ diff --git a/assets/examples/backup-images/pexels-mike-380954.webp b/assets/examples/backup-images/pexels-mike-380954.webp new file mode 100644 index 0000000..d71c5de Binary files /dev/null and b/assets/examples/backup-images/pexels-mike-380954.webp differ diff --git a/assets/examples/backup-images/pexels-pixabay-279906.webp b/assets/examples/backup-images/pexels-pixabay-279906.webp new file mode 100644 index 0000000..e230265 Binary files /dev/null and b/assets/examples/backup-images/pexels-pixabay-279906.webp differ diff --git a/assets/examples/images/pexels-Venus-HD-Make-up-and-perfume-2587370.webp b/assets/examples/images/pexels-Venus-HD-Make-up-and-perfume-2587370.webp index b02cc94..6e635cf 100644 Binary files a/assets/examples/images/pexels-Venus-HD-Make-up-and-perfume-2587370.webp and b/assets/examples/images/pexels-Venus-HD-Make-up-and-perfume-2587370.webp differ diff --git a/assets/examples/images/pexels-agnese-lunecka-10322857.webp b/assets/examples/images/pexels-agnese-lunecka-10322857.webp new file mode 100644 index 0000000..f80020a Binary files /dev/null and b/assets/examples/images/pexels-agnese-lunecka-10322857.webp differ diff --git a/assets/examples/images/pexels-agnese-lunecka-11179383.webp b/assets/examples/images/pexels-agnese-lunecka-11179383.webp new file mode 100644 index 0000000..35c854b Binary files /dev/null and b/assets/examples/images/pexels-agnese-lunecka-11179383.webp differ diff --git a/assets/examples/images/pexels-agnese-lunecka-11328773.webp b/assets/examples/images/pexels-agnese-lunecka-11328773.webp new file mode 100644 index 0000000..434ce53 Binary files /dev/null and b/assets/examples/images/pexels-agnese-lunecka-11328773.webp differ diff --git a/assets/examples/images/pexels-alex-azabache-3907507.webp b/assets/examples/images/pexels-alex-azabache-3907507.webp index b534d57..c74914a 100644 Binary files a/assets/examples/images/pexels-alex-azabache-3907507.webp and b/assets/examples/images/pexels-alex-azabache-3907507.webp differ diff --git a/assets/examples/images/pexels-binoid-cbd-3612182.webp b/assets/examples/images/pexels-binoid-cbd-3612182.webp index a218d9f..3e732bb 100644 Binary files a/assets/examples/images/pexels-binoid-cbd-3612182.webp and b/assets/examples/images/pexels-binoid-cbd-3612182.webp differ diff --git a/assets/examples/images/pexels-caio-1279107.webp b/assets/examples/images/pexels-caio-1279107.webp index 058e02f..341d87b 100644 Binary files a/assets/examples/images/pexels-caio-1279107.webp and b/assets/examples/images/pexels-caio-1279107.webp differ diff --git a/assets/examples/images/pexels-eprism-studio-335257.webp b/assets/examples/images/pexels-eprism-studio-335257.webp index d925c69..1154dbf 100644 Binary files a/assets/examples/images/pexels-eprism-studio-335257.webp and b/assets/examples/images/pexels-eprism-studio-335257.webp differ diff --git a/assets/examples/images/pexels-gabriel-freytez-341523.webp b/assets/examples/images/pexels-gabriel-freytez-341523.webp index 962795f..8043a40 100644 Binary files a/assets/examples/images/pexels-gabriel-freytez-341523.webp and b/assets/examples/images/pexels-gabriel-freytez-341523.webp differ diff --git a/assets/examples/images/pexels-jess-bailey-designs-913135.webp b/assets/examples/images/pexels-jess-bailey-designs-913135.webp index 7ce0dce..dc86761 100644 Binary files a/assets/examples/images/pexels-jess-bailey-designs-913135.webp and b/assets/examples/images/pexels-jess-bailey-designs-913135.webp differ diff --git a/assets/examples/images/pexels-luis-quintero-1738641.webp b/assets/examples/images/pexels-luis-quintero-1738641.webp index 59f5e8b..a780618 100644 Binary files a/assets/examples/images/pexels-luis-quintero-1738641.webp and b/assets/examples/images/pexels-luis-quintero-1738641.webp differ diff --git a/assets/examples/images/pexels-math-90946.webp b/assets/examples/images/pexels-math-90946.webp index e59e44b..8556194 100644 Binary files a/assets/examples/images/pexels-math-90946.webp and b/assets/examples/images/pexels-math-90946.webp differ diff --git a/assets/examples/images/pexels-mike-380954.webp b/assets/examples/images/pexels-mike-380954.webp index d71c5de..e2a8a4a 100644 Binary files a/assets/examples/images/pexels-mike-380954.webp and b/assets/examples/images/pexels-mike-380954.webp differ diff --git a/assets/examples/images/pexels-pixabay-279906.webp b/assets/examples/images/pexels-pixabay-279906.webp index e230265..613ee03 100644 Binary files a/assets/examples/images/pexels-pixabay-279906.webp and b/assets/examples/images/pexels-pixabay-279906.webp differ diff --git a/db-seed/Cargo.toml b/db-seed/Cargo.toml index 7224e32..aa2fc13 100644 --- a/db-seed/Cargo.toml +++ b/db-seed/Cargo.toml @@ -9,6 +9,8 @@ config = { path = "../shared/config" } database_manager = { path = "../actors/database_manager", features = ["dummy"] } fs_manager = { path = "../actors/fs_manager", features = [] } +bytes = { version = "1.1.0" } + actix = { version = "0.13", features = [] } actix-rt = { version = "2.7", features = [] } actix-web = { version = "4.0", features = [] } diff --git a/db-seed/src/photos.rs b/db-seed/src/photos.rs index 70dae09..7f307ee 100644 --- a/db-seed/src/photos.rs +++ b/db-seed/src/photos.rs @@ -1,4 +1,5 @@ use actix::{Actor, Addr}; +use actix_web::web::BytesMut; use config::SharedAppConfig; use database_manager::{query_db, Database}; use fs_manager::query_fs; @@ -18,15 +19,21 @@ async fn create_photo( tokio::fs::File::open(std::path::Path::new("./assets/examples/images").join(file)) .await .unwrap(); - while let Ok(b) = file.read_u8().await { - tx.send(b).unwrap(); + let mut buffer = BytesMut::with_capacity(1024); + while let Ok(len) = file.read_buf(&mut buffer).await { + if len == 0 { + break; + } + tx.send(actix_web::web::Bytes::from(buffer)).unwrap(); + buffer = BytesMut::with_capacity(1024); } }; let write = async { let fs_manager::WriteResult { - unique_name: _, + unique_name, local_path, + file_name, } = query_fs!( fs, fs_manager::WriteFile { @@ -39,7 +46,8 @@ async fn create_photo( db, database_manager::CreatePhoto { local_path, - file_name: model::FileName::new(file) + file_name, + unique_name }, crate::Error::WritePhoto(file.into()) ); @@ -135,7 +143,25 @@ pub(crate) async fn create_photos( seed.clone(), fs.clone(), "pexels-Venus-HD-Make-up-and-perfume-2587370.webp" - ) + ), + create_photo( + db.clone(), + seed.clone(), + fs.clone(), + "pexels-agnese-lunecka-10322857.webp" + ), + create_photo( + db.clone(), + seed.clone(), + fs.clone(), + "pexels-agnese-lunecka-11179383.webp" + ), + create_photo( + db.clone(), + seed.clone(), + fs.clone(), + "pexels-agnese-lunecka-11328773.webp" + ), ); results.0.unwrap(); results.1.unwrap(); @@ -148,5 +174,6 @@ pub(crate) async fn create_photos( results.8.unwrap(); results.9.unwrap(); results.10.unwrap(); + results.11.unwrap(); Ok(()) } diff --git a/db-seed/src/product_photos.rs b/db-seed/src/product_photos.rs index b380c62..0dd109c 100644 --- a/db-seed/src/product_photos.rs +++ b/db-seed/src/product_photos.rs @@ -122,7 +122,25 @@ pub(crate) async fn create_product_photos( seed.clone(), "Venus HD Professional", "pexels-Venus-HD-Make-up-and-perfume-2587370.webp" - ) + ), + create_product_photo( + db.clone(), + seed.clone(), + "Fancy Plate", + "pexels-agnese-lunecka-10322857.webp" + ), + create_product_photo( + db.clone(), + seed.clone(), + "Fancy Plate", + "pexels-agnese-lunecka-11179383.webp" + ), + create_product_photo( + db.clone(), + seed.clone(), + "Fancy Plate", + "pexels-agnese-lunecka-11328773.webp" + ), ); results.0.unwrap(); results.1.unwrap(); diff --git a/db-seed/src/products.rs b/db-seed/src/products.rs index c33e5a9..90f03bd 100644 --- a/db-seed/src/products.rs +++ b/db-seed/src/products.rs @@ -54,21 +54,77 @@ pub(crate) async fn create_products( } let results = tokio::join!( - create_product(db.clone(), seed.clone(), "Nikon", "Cameras",), - create_product(db.clone(), seed.clone(), "Bonoid CBD", "Drugstore",), - create_product(db.clone(), seed.clone(), "Casio Speaker", "Speakers",), - create_product(db.clone(), seed.clone(), "Eprism Studio", "Drugstore",), - create_product(db.clone(), seed.clone(), "Best Phones 2022", "Phones",), - create_product(db.clone(), seed.clone(), "Sweet cake", "Sweets",), - create_product(db.clone(), seed.clone(), "Lexal 128G", "Memory",), - create_product(db.clone(), seed.clone(), "Fujifilm X-T10", "Cameras",), - create_product(db.clone(), seed.clone(), "Sweet Tower", "Sweets",), - create_product(db.clone(), seed.clone(), "Nikon Lenses", "Cameras",), + create_product( + db.clone(), + seed.clone(), + "Nikon", + model::Category::CAMERAS_NAME, + ), + create_product( + db.clone(), + seed.clone(), + "Bonoid CBD", + model::Category::DRUGSTORE_NAME, + ), + create_product( + db.clone(), + seed.clone(), + "Casio Speaker", + model::Category::SPEAKERS_NAME, + ), + create_product( + db.clone(), + seed.clone(), + "Eprism Studio", + model::Category::DRUGSTORE_NAME, + ), + create_product( + db.clone(), + seed.clone(), + "Best Phones 2022", + model::Category::PHONES_NAME, + ), + create_product( + db.clone(), + seed.clone(), + "Sweet cake", + model::Category::SWEETS_NAME, + ), + create_product( + db.clone(), + seed.clone(), + "Lexal 128G", + model::Category::MEMORY_NAME, + ), + create_product( + db.clone(), + seed.clone(), + "Fujifilm X-T10", + model::Category::CAMERAS_NAME, + ), + create_product( + db.clone(), + seed.clone(), + "Sweet Tower", + model::Category::SWEETS_NAME, + ), + create_product( + db.clone(), + seed.clone(), + "Nikon Lenses", + model::Category::CAMERAS_NAME, + ), create_product( db.clone(), seed.clone(), "Venus HD Professional", - "Drugstore", + model::Category::DRUGSTORE_NAME, + ), + create_product( + db.clone(), + seed.clone(), + "Fancy Plate", + model::Category::PLATES_NAME, ) ); results.0.unwrap(); diff --git a/shared/model/src/lib.rs b/shared/model/src/lib.rs index a93283a..a6a8485 100644 --- a/shared/model/src/lib.rs +++ b/shared/model/src/lib.rs @@ -39,46 +39,86 @@ pub struct Category { pub svg: &'static str, } -pub const CATEGORIES: [Category; 8] = [ +impl Category { + pub const CAMERAS_NAME: &'static str = "Cameras"; + pub const CAMERAS_KEY: &'static str = "cameras"; + + pub const DRUGSTORE_NAME: &'static str = "Drugstore"; + pub const DRUGSTORE_KEY: &'static str = "drugstore"; + + pub const SPEAKERS_NAME: &'static str = "Speakers"; + pub const SPEAKERS_KEY: &'static str = "speakers"; + + pub const PHONES_NAME: &'static str = "Phones"; + pub const PHONES_KEY: &'static str = "phones"; + + pub const SWEETS_NAME: &'static str = "Sweets"; + pub const SWEETS_KEY: &'static str = "sweets"; + + pub const MEMORY_NAME: &'static str = "Memory"; + pub const MEMORY_KEY: &'static str = "memory"; + + pub const PANTS_NAME: &'static str = "Pants"; + pub const PANTS_KEY: &'static str = "pants"; + + pub const CLOTHES_NAME: &'static str = "Clothes"; + pub const CLOTHES_KEY: &'static str = "clothes"; + + pub const PLATES_NAME: &'static str = "Plates"; + pub const PLATES_KEY: &'static str = "plates"; +} + +macro_rules! category_svg { + ($name: expr) => { + concat!("/svg/", $name, ".svg") + }; +} + +pub const CATEGORIES: [Category; 9] = [ Category { - name: "Cameras", - key: "cameras", - svg: "/svg/cameras.svg", + name: Category::CAMERAS_NAME, + key: Category::CAMERAS_KEY, + svg: category_svg!("cameras"), }, Category { - name: "Drugstore", - key: "drugstore", - svg: "/svg/drugstore.svg", + name: Category::DRUGSTORE_NAME, + key: Category::DRUGSTORE_KEY, + svg: category_svg!("drugstore"), }, Category { - name: "Speakers", - key: "speakers", - svg: "/svg/speakers.svg", + name: Category::SPEAKERS_NAME, + key: Category::SPEAKERS_KEY, + svg: category_svg!("speakers"), }, Category { - name: "Phones", - key: "phones", - svg: "/svg/phones.svg", + name: Category::PHONES_NAME, + key: Category::PHONES_KEY, + svg: category_svg!("phones"), }, Category { - name: "Sweets", - key: "sweets", - svg: "/svg/sweets.svg", + name: Category::SWEETS_NAME, + key: Category::SWEETS_KEY, + svg: category_svg!("sweets"), }, Category { - name: "Memory", - key: "memory", - svg: "/svg/memory.svg", + name: Category::MEMORY_NAME, + key: Category::MEMORY_KEY, + svg: category_svg!("memory"), }, Category { - name: "Pants", - key: "pants", - svg: "/svg/pants.svg", + name: Category::PANTS_NAME, + key: Category::PANTS_KEY, + svg: category_svg!("pants"), }, Category { - name: "Clothes", - key: "clothes", - svg: "/svg/clothes.svg", + name: Category::CLOTHES_NAME, + key: Category::CLOTHES_KEY, + svg: category_svg!("clothes"), + }, + Category { + name: Category::PLATES_NAME, + key: Category::PLATES_KEY, + svg: category_svg!("plates"), }, ]; @@ -386,6 +426,31 @@ pub enum Day { Sunday = 1 << 6, } +impl Day { + pub fn name(&self) -> &str { + match self { + Self::Monday => "Monday", + Self::Tuesday => "Tuesday", + Self::Wednesday => "Wednesday", + Self::Thursday => "Thursday", + Self::Friday => "Friday", + Self::Saturday => "Saturday", + Self::Sunday => "Sunday", + } + } + pub fn short_name(&self) -> &'static str { + match self { + Self::Monday => "Mon", + Self::Tuesday => "Tue", + Self::Wednesday => "Wed", + Self::Thursday => "Thu", + Self::Friday => "Fri", + Self::Saturday => "Sat", + Self::Sunday => "Sun", + } + } +} + impl TryFrom for Day { type Error = TransformError; @@ -411,10 +476,18 @@ impl TryFrom for Day { } #[cfg_attr(feature = "dummy", derive(fake::Dummy))] -#[derive(Serialize, Deserialize, Hash, Debug, Deref)] +#[derive(Serialize, Deserialize, Hash, Debug)] #[serde(transparent)] pub struct Days(Vec); +impl std::ops::Deref for Days { + type Target = Vec; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + #[cfg(feature = "db")] impl<'q> ::sqlx::encode::Encode<'q, sqlx::Postgres> for Days where diff --git a/web/src/i18n.rs b/web/src/i18n.rs index 0a69355..347502d 100644 --- a/web/src/i18n.rs +++ b/web/src/i18n.rs @@ -1,16 +1,69 @@ -pub struct I18n {} +mod pl; + +use std::collections::HashMap; + +pub struct I18n { + store: HashMap>, + lang: String, +} impl I18n { pub fn load() -> Self { - // let languages: js_sys::Array = seed::window().navigator().languages(); - // for lang in languages { - // let l: wasm_bindgen::JsValue = lang; - // if let Some(s) = l.as_string() { - // if let Ok(local) = pure_rust_locales::Locale::try_from(&s) { - // // - // } - // } - // } - Self {} + let mut i18n = Self { + store: HashMap::with_capacity(1_000), + lang: "".into(), + }; + pl::define(&mut i18n); + let languages: js_sys::Array = seed::window().navigator().languages(); + languages.find(&mut |lang, _idx, _array| { + let l: wasm_bindgen::JsValue = lang; + if let Some(s) = l.as_string() { + if i18n.store.contains_key(&s) { + i18n.lang = s; + true + } else { + false + } + } else { + false + } + }); + i18n + } + + fn scope(&mut self, lang: &'static str) -> Scope { + Scope { store: self, lang } + } + + pub fn t(&self, key: &'static str) -> &'static str { + self.store + .get(self.lang.as_str()) + .and_then(|s| s.get(key)) + .copied() + .unwrap_or(key) + } + + pub fn l(&self, key: &String) -> String { + self.store + .get(self.lang.as_str()) + .and_then(|s| s.get(key)) + .copied() + .map(Into::into) + .unwrap_or_else(|| key.clone()) + } +} + +pub struct Scope<'store, 'lang> { + lang: &'lang str, + store: &'store mut I18n, +} +impl<'store, 'lang> Scope<'store, 'lang> { + fn define(&mut self, key: &str, value: &'static str) -> &mut Self { + self.store + .store + .entry(self.lang.into()) + .or_insert_with(|| HashMap::with_capacity(1_000)) + .insert(key.into(), value); + self } } diff --git a/web/src/i18n/pl.rs b/web/src/i18n/pl.rs new file mode 100644 index 0000000..a0d108d --- /dev/null +++ b/web/src/i18n/pl.rs @@ -0,0 +1,24 @@ +use crate::i18n::I18n; + +pub fn define(i18n: &mut I18n) { + i18n.scope("pl") + .define("Mon", "Pon") + .define("Tue", "Wt") + .define("Wed", "Śr") + .define("Thu", "Czw") + .define("Fri", "Pt") + .define("Sat", "Sob") + .define("Sun", "Ndz") + .define("Add to Cart", "Dodaj") + .define("Qty", "Ilość") + .define("Delivery every", "Dostawa w każdy") + .define("Cameras", "Kamery") + .define("Drugstore", "Drogeria") + .define("Speakers", "Głośniki") + .define("Phones", "Telefony") + .define("Sweets", "Słodycze") + .define("Memory", "Pamięć") + .define("Pants", "Spodnie") + .define("Clothes", "Ubrania") + .define("Plates", "Talerze"); +} diff --git a/web/src/lib.rs b/web/src/lib.rs index 63d0dae..6633a74 100644 --- a/web/src/lib.rs +++ b/web/src/lib.rs @@ -7,6 +7,7 @@ pub mod shared; use seed::empty; use seed::prelude::*; +use crate::i18n::I18n; use crate::model::Model; use crate::pages::{Msg, Page, PublicPage}; @@ -75,6 +76,7 @@ fn init(url: Url, orders: &mut impl Orders) -> Model { .flatten() .and_then(|el: web_sys::Element| el.get_attribute("href")), shared: shared::Model::default(), + i18n: I18n::load(), } } diff --git a/web/src/model.rs b/web/src/model.rs index 9ca6fc3..5562cc9 100644 --- a/web/src/model.rs +++ b/web/src/model.rs @@ -1,6 +1,6 @@ use seed::Url; -use crate::Page; +use crate::{I18n, Page}; pub struct Model { pub url: Url, @@ -8,4 +8,5 @@ pub struct Model { pub page: Page, pub logo: Option, pub shared: crate::shared::Model, + pub i18n: I18n, } diff --git a/web/src/pages/public.rs b/web/src/pages/public.rs index e932700..afb2d57 100644 --- a/web/src/pages/public.rs +++ b/web/src/pages/public.rs @@ -12,13 +12,13 @@ pub mod layout { use seed::*; pub fn view( - url: Url, + model: &crate::Model, content: Node, categories: Option<&[model::api::Category]>, ) -> Node { let sidebar = match categories { Some(categories) => { - let sidebar = super::sidebar::view(url, categories); + let sidebar = super::sidebar::view(model, categories); div![ C!["flex flex-col w-64 h-screen px-4 py-8 overflow-y-auto border-r"], sidebar @@ -40,10 +40,8 @@ pub mod sidebar { use crate::pages::Urls; - pub fn view(url: Url, categories: &[model::api::Category]) -> Node { - let categories = categories - .iter() - .map(|category| item(url.clone(), category)); + pub fn view(model: &crate::Model, categories: &[model::api::Category]) -> Node { + let categories = categories.iter().map(|category| item(model, category)); div![ C!["flex flex-col justify-between mt-6"], @@ -51,8 +49,8 @@ pub mod sidebar { ] } - fn item(url: Url, category: &model::api::Category) -> Node { - let url = Urls::new(url) + fn item(model: &crate::Model, category: &model::api::Category) -> Node { + let url = Urls::new(model.url.clone()) .listing() .add_path_part(category.key.as_str()); li![ @@ -64,7 +62,7 @@ pub mod sidebar { C!["w-6 h-6"], attrs!["src" => category.svg.as_str(), "style" => ""] ], - span![C!["mx-4 font-medium"], category.name.as_str()] + span![C!["mx-4 font-medium"], model.i18n.l(&category.name)] ] ] } diff --git a/web/src/pages/public/listing.rs b/web/src/pages/public/listing.rs index 2bd4ed5..b16463d 100644 --- a/web/src/pages/public/listing.rs +++ b/web/src/pages/public/listing.rs @@ -125,7 +125,7 @@ pub fn view(model: &crate::Model, page: &ListingPage) -> Node { div![ crate::shared::view::public_navbar(model), - super::layout::view(model.url.clone(), content, Some(&page.categories)) + super::layout::view(&model, content, Some(&page.categories)) ] } @@ -173,7 +173,10 @@ fn product(model: &crate::Model, product: &model::api::Product) -> Node { ], div![ C!["flex items-center justify-between"], - a![C!["px-6 py-2 text-sm text-white bg-indigo-500 rounded-lg outline-none hover:bg-indigo-600 ring-indigo-300"], "Add to Cart"], + a![ + C!["px-6 py-2 text-sm text-white bg-indigo-500 rounded-lg outline-none hover:bg-indigo-600 ring-indigo-300"], + model.i18n.t("Add to Cart") + ], div![C!["mt-1 text-xl font-semibold"], price], ] ] diff --git a/web/src/pages/public/product.rs b/web/src/pages/public/product.rs index bd8eb03..4863df8 100644 --- a/web/src/pages/public/product.rs +++ b/web/src/pages/public/product.rs @@ -4,12 +4,14 @@ use seed::*; #[derive(Debug)] pub enum Msg { ProductFetched(fetch::Result), + SelectImage(usize), } #[derive(Debug, Default)] pub struct ProductPage { pub product_id: Option, pub product: Option, + pub selected_image: usize, } pub fn init(mut url: Url, orders: &mut impl Orders) -> ProductPage { @@ -23,6 +25,7 @@ pub fn init(mut url: Url, orders: &mut impl Orders) -> ProductPage { ProductPage { product_id: Some(product_id), product: None, + selected_image: 0, } } @@ -36,6 +39,9 @@ pub fn update(msg: Msg, model: &mut ProductPage, _orders: &mut impl Orders) Msg::ProductFetched(Err(e)) => { seed::error!(e); } + Msg::SelectImage(selected) => { + model.selected_image = selected; + } } } @@ -44,11 +50,23 @@ pub fn view(model: &crate::Model, page: &ProductPage) -> Node { None => return empty!(), Some(product) => product, }; - let images = product + let large_photo = product .photos - .iter() - .enumerate() - .map(|(idx, img)| image(idx, img)); + .get(page.selected_image) + .map(image) + .unwrap_or_else(|| empty![]); + let small_photos = { + if product.photos.len() <= 1 { + empty![] + } else { + let photos = product + .photos + .iter() + .enumerate() + .map(|(idx, img)| small_image(idx, page.selected_image, img)); + div![C!["flex -mx-2 mb-4"], photos] + } + }; let description = product .long_description @@ -56,16 +74,20 @@ pub fn view(model: &crate::Model, page: &ProductPage) -> Node { .split('\n') .map(|s| div![s]); + let delivery = delivery_available(product, model); + let content = div![ C!["max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 mt-6"], + attrs!["id" => "full-window"], div![ C!["flex flex-col md:flex-row -mx-4"], + attrs!["id" => "product-header"], div![ C!["md:flex-1 px-4"], attrs!["id" => "photos"], div![ - attrs!["x-data" => "{ image: 1}"], - div![C!["h-64 md:h-80 rounded-lg bg-gray-100 mb-4"], images] - ] + div![C!["h-64 md:h-80 rounded-lg bg-gray-100 mb-4"], large_photo] + ], + small_photos ], div![ C!["md:flex-1 px-4"], attrs!["id" => "details"], @@ -73,22 +95,82 @@ pub fn view(model: &crate::Model, page: &ProductPage) -> Node { C!["mb-2 leading-tight tracking-tight font-bold text-gray-800 text-2xl md:text-3xl"], product.name.as_str() ], - div![description] - ] + div![ + delivery + ], + div![ + C!["flex py-4 space-x-4"], + div![ + C!["relative"], + label![ + C!["text-center left-0 pt-2 right-0 absolute block text-xs uppercase text-gray-400 tracking-wide font-semibold"], + attrs!["for" => "quantity"], + model.i18n.t("Qty") + ], + input![C![""], attrs!["id" => "quantity", "type" => "number"]] + ], + button![ + C!["px-6 py-3 text-sm text-white bg-indigo-500 rounded-lg outline-none hover:bg-indigo-600 ring-indigo-300"], + model.i18n.t("Add to Cart"), + ev("click", move |ev| { + ev.prevent_default(); + ev.stop_propagation(); + None as Option + }) + ] + ] + ], + ], + div![ + C!["-mx-4"], + attrs!["id" => "product-header"], + div![description] ] ].map_msg(map_to_global); div![ crate::shared::view::public_navbar(model), - super::layout::view(model.url.clone(), content, None) + super::layout::view(model, content, None) ] } -fn image(idx: usize, img: &model::api::Photo) -> Node { +fn delivery_available(product: &model::api::Product, model: &crate::Model) -> Node { + let days = product + .deliver_days_flag + .iter() + .map(|day| div![ + C!["focus:outline-none w-14 md:w-18 rounded-lg h-14 md:h-18 bg-gray-100 flex items-center justify-center ring-indigo-300 ring-inset"], + model.i18n.t(day.short_name()) + ]); + div![ + div![C![""], model.i18n.t("Delivery every")], + div![C!["flex py-4 space-x-4"], days] + ] +} + +fn small_image(idx: usize, selected: usize, img: &model::api::Photo) -> Node { + div![ + C!["flex-1 px-2"], + button![ + C!["focus:outline-none w-full rounded-lg h-24 md:h-32 bg-gray-100 flex items-center justify-center ring-indigo-300 ring-inset"], + IF![selected == idx => C!["ring-2"]], + img![ + C!["h-24 md:h-32"], + attrs!["src" => img.url.as_str()] + ], + ev("click", move |ev| { + ev.prevent_default(); + ev.stop_propagation(); + Msg::SelectImage(idx) + }) + ] + ] +} + +fn image(img: &model::api::Photo) -> Node { div![ C!["h-64 md:h-80 rounded-lg bg-gray-100 mb-4 flex items-center justify-center"], - attrs!["x-show" => format!("image == {}", idx + 1)], - img![attrs!["src" => img.url.as_str()]] + img![C!["h-64 md:h-80"], attrs!["src" => img.url.as_str()]] ] }