diff --git a/Cargo.lock b/Cargo.lock index 7b3b057..6b389be 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -730,6 +730,7 @@ dependencies = [ "leptos_router", "nostr-sdk", "serde", + "serde_json", "tokio", "uuid", "wasm-bindgen", diff --git a/Cargo.toml b/Cargo.toml index ebc0942..b809cf5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,6 +24,7 @@ tokio = "1" gloo-net = "0.5" futures = "0.3" wasm-bindgen-futures = "0.4" +serde_json="1.0.133" [features] csr = ["leptos/csr", "leptos_meta/csr", "leptos_router/csr"] diff --git a/src/app.rs b/src/app.rs index 210d358..56c4c2a 100644 --- a/src/app.rs +++ b/src/app.rs @@ -1,7 +1,7 @@ use leptos::*; use leptos_meta::*; -use crate::components::{item_form::ItemForm, items_list::ItemsList}; -use crate::models::item::{Item, ReviewWithRating}; +use crate::components::items_list::ItemsList; +use crate::models::item::Item; use crate::nostr::NostrClient; use tokio::sync::mpsc; use uuid::Uuid; @@ -28,36 +28,28 @@ pub fn App() -> impl IntoView { } }); - // Add a new item and review using the unified form - let add_item = move |name: String, description: String, tags: Vec<(String, String)>, review: String, rating: u8| { + // Function to add a new item from the grid + let add_item_from_grid = move || { let new_id = Uuid::new_v4().to_string(); set_items.update(|items| { let item = Item { id: new_id.clone(), - name, - description, - tags, - reviews: vec![ReviewWithRating { content: review.clone(), rating }], + name: String::new(), + description: String::new(), + tags: vec![], + reviews: vec![], wikidata_id: None, }; items.push(item); }); - - spawn_local(async move { - let nostr_client = NostrClient::new("wss://relay.example.com").await.unwrap(); - nostr_client.publish_item("New item added!".to_string(), "".to_string(), vec![]).await.unwrap(); - }); }; view! {

{ "CompareWare" }

- // Unified form for adding an item and its first review - - // Display all items, including reviews - +
} } diff --git a/src/components/items_list.rs b/src/components/items_list.rs index 025de27..53940a6 100644 --- a/src/components/items_list.rs +++ b/src/components/items_list.rs @@ -1,22 +1,20 @@ -/// Component to display a list of items. -/// Iterates through the items and renders their name, description, tags, and reviews. -use leptos::*; use crate::models::item::Item; -use serde::Deserialize; -use futures::future; use gloo_net::http::Request; +use leptos::logging::log; +use leptos::*; +use serde::Deserialize; +use std::collections::HashMap; use wasm_bindgen_futures::spawn_local; -use std::sync::Arc; -// Define the structure for Wikidata API response #[derive(Deserialize, Clone, Debug)] struct WikidataResponse { - entities: std::collections::HashMap, + entities: HashMap, } + #[derive(Deserialize, Clone, Debug)] struct WikidataEntity { - labels: Option>, - descriptions: Option>, + labels: Option>, + descriptions: Option>, } #[derive(Deserialize, Clone, Debug)] @@ -25,144 +23,115 @@ struct WikidataLabel { } #[component] -pub fn ItemsList(items: ReadSignal>) -> impl IntoView { - // Create a signal for selected items - let (selected_items_signal, set_selected_items) = create_signal(Vec::::new()); +pub fn ItemsList(items: ReadSignal>, set_items: WriteSignal>, on_add_item: impl Fn() + 'static, ) -> impl IntoView { let (wikidata_data, set_wikidata_data) = create_signal(Vec::>::new()); - // Fetch additional data from Wikidata for selected items - let fetch_wikidata = move || { - let selected_indices = selected_items_signal.get(); - let selected_items: Vec = selected_indices - .iter() - .map(|&i| items.get()[i].clone()) - .collect(); - - - // Wrap `selected_items` in an `Arc` so it can be cloned - let selected_items = Arc::new(selected_items); - - // Clone selected_items before the async block to avoid borrowing issues - let selected_items_clone: Arc> = Arc::clone(&selected_items); - - // For each selected item, fetch Wikidata attributes - let futures = selected_items_clone.iter().map(move |item| { - let wikidata_id = item.wikidata_id.clone(); - async move { - if let Some(id) = wikidata_id { - let url = format!("https://www.wikidata.org/wiki/Special:EntityData/{}.json", id); - match Request::get(&url).send().await { - Ok(response) => match response.json::().await { - Ok(parsed_data) => parsed_data.entities.get(&id).cloned(), - Err(_) => None, - }, - Err(_) => None, - } - } else { - None - } - } - }).collect::>(); - - + // Fetch data from Wikidata for a given item name + let fetch_wikidata = move |item_name: String| { spawn_local(async move { - let results = future::join_all(futures).await; - set_wikidata_data.set(results); + let url = format!("https://www.wikidata.org/w/api.php?action=wbsearchentities&search={}&language=en&limit=1&format=json", item_name); + match Request::get(&url).send().await { + Ok(response) => match response.json::().await { + Ok(parsed_data) => { + if let Some(entity) = parsed_data.entities.values().next() { + set_wikidata_data.update(|current_data| { + current_data.push(Some(entity.clone())); + }); + } + } + Err(_) => log!("Failed to parse response from Wikidata"), + }, + Err(_) => log!("Failed to make request to Wikidata"), + } }); }; - - // Function to toggle selection of an item - let toggle_selection = move |i: usize| { - set_selected_items.update(|items| { - if items.contains(&i) { - items.retain(|&x| x != i); - } else { - items.push(i); + + // Handle updating grid cells + let update_item = move |index: usize, column: &str, value: String| { + set_items.update(|items| { + if let Some(item) = items.get_mut(index) { + match column { + "name" => item.name = value, + "description" => item.description = value, + "tags" => item.tags.push((value.clone(), value)), // For simplicity, adding the same value as key and value. + "review" => item.reviews.push(crate::models::item::ReviewWithRating { + content: value.clone(), + rating: 5, // Assuming a default rating of 5 + }), + "rating" => { + if let Ok(rating) = value.parse::() { + item.reviews.last_mut().map(|r| r.rating = rating); + } + } + _ => (), + } } }); }; + // Trigger add item event + let add_item = move |_| { + on_add_item(); // Call the passed closure from App to add a new item + }; + view! {
-

{ "Items" }

-
    - {move || items.get().iter().enumerate().map(|(i, item)| view! { -
  • - -
    - { &item.name } - { &item.description } -
    -
      -

      { "Tags:" }

      - {item.tags.iter().map(|(key, value)| view! { -
    • { key.clone() + ": " + value }
    • - }).collect::>()} -
    -
      -

      { "Reviews:" }

      - {item.reviews.iter().map(|review| view! { -
    • { format!("Rating: {}/5 - {}", review.rating, review.content) }
    • - }).collect::>()} -
    -
  • - }).collect::>() } -
+

{ "CompareWare" }

- // Button to fetch Wikidata attributes - + - // Comparison Table -

{ "Comparison Table" }

- + - - + + {move || { - let selected_indices = selected_items_signal.get(); - selected_indices.iter().enumerate().map(|(idx, &i)| { - let item = &items.get()[i]; - let wikidata_entity = wikidata_data.get().get(idx).cloned().flatten(); + items.get().iter().enumerate().map(|(index, item)| { view! { - - - + - - + + +
{ "Item Name" }{ "Name" } { "Description" } { "Tags" }{ "Reviews" }{ "External Description (Wikidata)" }{ "Review" }{ "Rating" }
{ &item.name }{ &item.description }
- {item.tags.iter().map(|(key, value)| view! { - { key.clone() + ": " + value + " " } - }).collect::>()} - - {item.reviews.iter().map(|review| view! { - { format!("Rating: {}/5 ", review.rating) } - }).collect::>()} - - {move || { - let entity = wikidata_entity.clone(); // Clone the value - match entity { - Some(entity) => entity - .descriptions - .as_ref() - .and_then(|descriptions| descriptions.get("en")) - .map(|label| label.value.clone()) - .unwrap_or_default(), - None => String::from("No description"), + + + + + + +