diff --git a/neveko-core/src/monero.rs b/neveko-core/src/monero.rs index c1d5422..12f2658 100644 --- a/neveko-core/src/monero.rs +++ b/neveko-core/src/monero.rs @@ -401,7 +401,7 @@ pub async fn create_wallet(filename: &String, password: &String) -> bool { fn update_wallet_lock(filename: &String, closing: bool) -> bool { let is_busy: bool = match IS_WALLET_BUSY.lock() { Ok(m) => *m, - Err(_) => false, + Err(_) => true, }; if is_busy && !closing { debug!("wallet {} is busy", filename); diff --git a/neveko-core/src/utils.rs b/neveko-core/src/utils.rs index 87c6a77..9cedecc 100644 --- a/neveko-core/src/utils.rs +++ b/neveko-core/src/utils.rs @@ -229,6 +229,20 @@ pub fn message_to_json(m: &models::Message) -> Json { Json(r_message) } +/// convert product to json so only core module does the work +pub fn product_to_json(m: &models::Product) -> Json { + let r_message: models::Product = models::Product { + pid: String::from(&m.pid), + description: String::from(&m.description), + image: m.image.iter().cloned().collect(), + in_stock: m.in_stock, + name: String::from(&m.name), + price: m.price, + qty: m.qty, + }; + Json(r_message) +} + /// Instead of putting `String::from("")` pub fn empty_string() -> String { String::from("") @@ -248,7 +262,7 @@ pub const fn message_limit() -> usize { } pub const fn image_limit() -> usize { - 2048 + 9999 } /// Generate application gpg keys at startup if none exist @@ -660,16 +674,18 @@ pub async fn can_transfer(invoice: u128) -> bool { } /// Gui toggle for vendor mode -pub fn toggle_vendor_enabled() { +pub fn toggle_vendor_enabled() -> bool { let off: &str = "0"; let on: &str = "1"; let vendor_env = std::env::var(contact::NEVEKO_VENDOR_ENABLED).unwrap_or(String::from(off)); if vendor_env == off { info!("neveko vendor mode enabled"); std::env::set_var(contact::NEVEKO_VENDOR_ENABLED, on); + return true; } else { info!("neveko vendor mode disabled"); std::env::set_var(contact::NEVEKO_VENDOR_ENABLED, off); + return false; } } diff --git a/neveko-gui/src/apps/market.rs b/neveko-gui/src/apps/market.rs index d5b3aef..c4465ad 100644 --- a/neveko-gui/src/apps/market.rs +++ b/neveko-gui/src/apps/market.rs @@ -5,10 +5,17 @@ use std::sync::mpsc::{ }; pub struct MarketApp { + is_product_image_set: bool, is_showing_products: bool, + is_showing_product_image: bool, + is_showing_product_update: bool, is_showing_orders: bool, + is_vendor_enabled: bool, orders: Vec, + product_image: egui_extras::RetainedImage, products: Vec, + product_update_pid: String, + new_product_image: String, new_product_name: String, new_product_desc: String, new_product_price: String, @@ -21,11 +28,19 @@ impl Default for MarketApp { fn default() -> Self { let (_refresh_on_delete_product_tx, _refresh_on_delete_product_rx) = std::sync::mpsc::channel(); + let read_product_image = std::fs::read("./assets/qr.png").unwrap_or(Vec::new()); MarketApp { + is_product_image_set: false, is_showing_orders: false, is_showing_products: false, + is_showing_product_image: false, + is_showing_product_update: false, + is_vendor_enabled: false, orders: Vec::new(), + product_image: egui_extras::RetainedImage::from_image_bytes("qr.png", &read_product_image).unwrap(), products: Vec::new(), + product_update_pid: utils::empty_string(), + new_product_image: utils::empty_string(), new_product_name: utils::empty_string(), new_product_desc: utils::empty_string(), new_product_price: utils::empty_string(), @@ -41,6 +56,100 @@ impl eframe::App for MarketApp { // Hook into async channel threads //----------------------------------------------------------------------------------- + // Product image window + //----------------------------------------------------------------------------------- + let mut is_showing_product_image = self.is_showing_product_image; + egui::Window::new("") + .open(&mut is_showing_product_image) + .vscroll(true) + .show(ctx, |ui| { + self.product_image.show(ui); + if ui.button("Exit").clicked() { + self.is_showing_product_image = false; + self.is_product_image_set = false; + let read_product_image = std::fs::read("./assets/qr.png").unwrap_or(Vec::new()); + self.product_image = egui_extras::RetainedImage::from_image_bytes("qr.png", &read_product_image).unwrap(); + } + }); + + // Update Product window + //----------------------------------------------------------------------------------- + let mut is_showing_product_update = self.is_showing_product_update; + egui::Window::new(format!("Update Product - {}", self.new_product_name)) + .open(&mut is_showing_product_update) + .vscroll(true) + .show(ctx, |ui| { + ui.label( + "____________________________________________________________________________\n", + ); + // TODO(c2m): file picker for images + ui.horizontal(|ui| { + let product_name = ui.label("image: \t\t\t"); + ui.text_edit_singleline(&mut self.new_product_image) + .labelled_by(product_name.id); + ui.label("\t/path/to/image.png"); + }); + ui.horizontal(|ui| { + let product_name = ui.label("name: \t\t\t"); + ui.text_edit_singleline(&mut self.new_product_name) + .labelled_by(product_name.id); + }); + ui.horizontal(|ui| { + let product_desc = ui.label("description: \t"); + ui.text_edit_singleline(&mut self.new_product_desc) + .labelled_by(product_desc.id); + }); + ui.horizontal(|ui| { + let product_price = ui.label("price: \t\t\t"); + ui.text_edit_singleline(&mut self.new_product_price) + .labelled_by(product_price.id); + ui.label("\t (piconeros)") + }); + ui.horizontal(|ui| { + let product_qty = ui.label("quantity: \t\t"); + ui.text_edit_singleline(&mut self.new_product_qty) + .labelled_by(product_qty.id); + }); + ui.label("\n"); + if ui.button("Update Product").clicked() { + let image: Vec = std::fs::read(self.new_product_image.clone()).unwrap_or_default(); + let price = match self.new_product_price.parse::() { + Ok(p) => p, + Err(_) => 0, + }; + let qty = match self.new_product_qty.parse::() { + Ok(q) => q, + Err(_) => 0, + }; + let product: models::Product = models::Product { + pid: self.product_update_pid.clone(), + description: self.new_product_desc.clone(), + image, + in_stock: qty > 0, + name: self.new_product_name.clone(), + price, + qty, + }; + let j_product = utils::product_to_json(&product); + product::modify(j_product); + self.new_product_desc = utils::empty_string(); + self.new_product_name = utils::empty_string(); + self.new_product_price = utils::empty_string(); + self.new_product_qty = utils::empty_string(); + self.new_product_image = utils::empty_string(); + self.is_showing_product_update = false; + self.products = product::find_all(); + } + if ui.button("Exit").clicked() { + self.new_product_desc = utils::empty_string(); + self.new_product_name = utils::empty_string(); + self.new_product_price = utils::empty_string(); + self.new_product_qty = utils::empty_string(); + self.new_product_image = utils::empty_string(); + self.is_showing_product_update = false; + } + }); + // Products window //----------------------------------------------------------------------------------- let mut is_showing_products = self.is_showing_products; @@ -59,9 +168,10 @@ impl eframe::App for MarketApp { .resizable(true) .cell_layout(egui::Layout::left_to_right(egui::Align::Center)) .column(Column::auto()) - .column(Column::initial(100.0).at_least(40.0).clip(true)) - .column(Column::initial(100.0).at_least(40.0).clip(true)) - .column(Column::initial(100.0).at_least(40.0).clip(true)) + .column(Column::auto()) + .column(Column::auto()) + .column(Column::auto()) + .column(Column::auto()) .column(Column::remainder()) .min_scrolled_height(0.0); @@ -79,13 +189,16 @@ impl eframe::App for MarketApp { header.col(|ui| { ui.strong("Quantity"); }); + header.col(|ui| { + ui.strong("Image"); + }); header.col(|ui| { ui.strong(""); }); }) .body(|mut body| { for p in &self.products { - let row_height = 200.0; + let row_height = 20.0; body.row(row_height, |mut row| { row.col(|ui| { ui.label(format!("{}", p.name)); @@ -99,17 +212,55 @@ impl eframe::App for MarketApp { row.col(|ui| { ui.label(format!("{}", p.qty)); }); + row.col(|ui| { + if ui.button("View").clicked() { + if !self.is_product_image_set { + self.is_showing_product_image = true; + let file_path = format!( + "/home/{}/.neveko/{}.jpeg", + std::env::var("USER").unwrap_or(String::from("user")), + p.pid + ); + // For the sake of brevity product list doesn't have image bytes, get them + let i_product = product::find(&p.pid); + match std::fs::write(&file_path, &i_product.image) { + Ok(w) => w, + Err(_) => log::error!("failed to write product image") + }; + self.is_product_image_set = true; + let contents = std::fs::read(&file_path).unwrap_or(Vec::new()); + if !i_product.image.is_empty() { + self.product_image = + egui_extras::RetainedImage::from_image_bytes(file_path, &contents).unwrap(); + } + self.is_product_image_set = true; + ctx.request_repaint(); + } + } + }); row.col(|ui| { ui.style_mut().wrap = Some(false); - ui.horizontal(|_ui| { - // update button + ui.horizontal(|ui| { + if ui.button("Update").clicked() { + self.product_update_pid = p.pid.clone(); + self.new_product_desc = p.description.clone(); + self.new_product_name = p.name.clone(); + self.new_product_price = format!("{}", p.price); + self.new_product_qty = format!("{}", p.qty); + self.is_showing_product_update = true; + } }); }); }); } }); + if ui.button("Exit").clicked() { + self.is_showing_products = false; + } }); + // TODO(c2m): Orders window + //----------------------------------------------------------------------------------- egui::Window::new("Orders") .open(&mut is_showing_orders) .vscroll(true) @@ -133,16 +284,16 @@ impl eframe::App for MarketApp { table .header(20.0, |mut header| { header.col(|ui| { - ui.strong("Name"); + ui.strong(""); }); header.col(|ui| { - ui.strong("Description"); + ui.strong(""); }); header.col(|ui| { - ui.strong("Price"); + ui.strong(""); }); header.col(|ui| { - ui.strong("Quantity"); + ui.strong(""); }); header.col(|ui| { ui.strong(""); @@ -150,7 +301,7 @@ impl eframe::App for MarketApp { }) .body(|mut body| { for o in &self.orders { - let row_height = 200.0; + let row_height = 20.0; body.row(row_height, |mut row| { row.col(|ui| { ui.label(format!("{}", o.cid)); @@ -178,38 +329,93 @@ impl eframe::App for MarketApp { egui::CentralPanel::default().show(ctx, |ui| { if ui.button("Refresh").clicked() { self.products = product::find_all(); + self.orders = order::find_all(); } ui.horizontal(|ui| { - ui.label("vendor mode: \t"); + let vendor_mode: &str = if self.is_vendor_enabled { + "enabled" + } else { + "disabled" + }; + ui.label(format!("vendor mode: {} \t", vendor_mode)); if ui.button("toggle").clicked() { - utils::toggle_vendor_enabled(); + self.is_vendor_enabled = utils::toggle_vendor_enabled(); } }); + if ui.button("View Vendors").clicked() { + + } ui.label("\n"); - ui.heading("Add Product"); - ui.label( - "____________________________________________________________________________\n", - ); - ui.horizontal(|ui| { - let product_name = ui.label("name: \t\t\t"); - ui.text_edit_singleline(&mut self.new_product_name) - .labelled_by(product_name.id); - }); - ui.horizontal(|ui| { - let product_desc = ui.label("description: \t"); - ui.text_edit_singleline(&mut self.new_product_desc) - .labelled_by(product_desc.id); - }); - ui.horizontal(|ui| { - let product_price = ui.label("price: \t\t\t"); - ui.text_edit_singleline(&mut self.new_product_price) - .labelled_by(product_price.id); - }); - ui.horizontal(|ui| { - let product_qty = ui.label("quantity: \t\t"); - ui.text_edit_singleline(&mut self.new_product_qty) - .labelled_by(product_qty.id); - }); + if ui.button("View Orders").clicked() { + + } + if self.is_vendor_enabled { + ui.label("\n"); + ui.heading("Add Product"); + ui.label( + "____________________________________________________________________________\n", + ); + // TODO(c2m): file picker for images + ui.horizontal(|ui| { + let product_name = ui.label("image: \t\t\t"); + ui.text_edit_singleline(&mut self.new_product_image) + .labelled_by(product_name.id); + ui.label("\t/path/to/image.png"); + }); + ui.horizontal(|ui| { + let product_name = ui.label("name: \t\t\t"); + ui.text_edit_singleline(&mut self.new_product_name) + .labelled_by(product_name.id); + }); + ui.horizontal(|ui| { + let product_desc = ui.label("description: \t"); + ui.text_edit_singleline(&mut self.new_product_desc) + .labelled_by(product_desc.id); + }); + ui.horizontal(|ui| { + let product_price = ui.label("price: \t\t\t"); + ui.text_edit_singleline(&mut self.new_product_price) + .labelled_by(product_price.id); + ui.label("\t (piconeros)") + }); + ui.horizontal(|ui| { + let product_qty = ui.label("quantity: \t\t"); + ui.text_edit_singleline(&mut self.new_product_qty) + .labelled_by(product_qty.id); + }); + if ui.button("Add Product").clicked() { + let image: Vec = std::fs::read(self.new_product_image.clone()).unwrap_or_default(); + let price = match self.new_product_price.parse::() { + Ok(p) => p, + Err(_) => 0, + }; + let qty = match self.new_product_qty.parse::() { + Ok(q) => q, + Err(_) => 0, + }; + let product: models::Product = models::Product { + pid: utils::empty_string(), + description: self.new_product_desc.clone(), + image, + in_stock: qty > 0, + name: self.new_product_name.clone(), + price, + qty, + }; + let j_product = utils::product_to_json(&product); + product::create(j_product); + self.new_product_desc = utils::empty_string(); + self.new_product_name = utils::empty_string(); + self.new_product_price = utils::empty_string(); + self.new_product_qty = utils::empty_string(); + self.new_product_image = utils::empty_string(); + } + ui.label("\n"); + if ui.button("View Products").clicked() { + self.products = product::find_all(); + self.is_showing_products = true; + } + } }); } }