From 16b04fcc1e07a4fc779e2ace3ec773d912b03295 Mon Sep 17 00:00:00 2001 From: Ryan Mwangi Date: Fri, 20 Dec 2024 18:24:20 +0300 Subject: [PATCH 1/3] feat(cells): make cells editable (debugging) --- src/app.rs | 33 ++--- src/components/editable_cell.rs | 23 +++ src/components/items_list.rs | 238 +++++++++++++++++------------- src/components/mod.rs | 4 +- src/components/tag_editor.rs | 45 ++++++ src/components/wikidata_lookup.rs | 48 ++++++ 6 files changed, 272 insertions(+), 119 deletions(-) create mode 100644 src/components/editable_cell.rs create mode 100644 src/components/tag_editor.rs create mode 100644 src/components/wikidata_lookup.rs diff --git a/src/app.rs b/src/app.rs index 56c4c2a..5ca104e 100644 --- a/src/app.rs +++ b/src/app.rs @@ -1,7 +1,7 @@ use leptos::*; use leptos_meta::*; use crate::components::items_list::ItemsList; -use crate::models::item::Item; +use crate::models::item::{Item, ReviewWithRating}; use crate::nostr::NostrClient; use tokio::sync::mpsc; use uuid::Uuid; @@ -28,28 +28,25 @@ pub fn App() -> impl IntoView { } }); - // 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: String::new(), - description: String::new(), - tags: vec![], - reviews: vec![], - wikidata_id: None, - }; - items.push(item); - }); + // Handle adding a new item + let add_item = move || { + let new_item = Item { + id: Uuid::new_v4().to_string(), + name: "New Item".to_string(), + description: String::new(), + tags: vec![], + reviews: vec![], + wikidata_id: None, + }; + set_items.update(|items| items.push(new_item)); }; - + view! {

{ "CompareWare" }

- + +
} } diff --git a/src/components/editable_cell.rs b/src/components/editable_cell.rs new file mode 100644 index 0000000..a8f43ca --- /dev/null +++ b/src/components/editable_cell.rs @@ -0,0 +1,23 @@ +use leptos::*; + +#[component] +pub fn EditableCell( + value: String, + on_input: impl Fn(String) + 'static, +) -> impl IntoView { + let (input_value, set_input_value) = create_signal(value.clone()); + + let handle_input = move |e: web_sys::Event| { + let new_value = event_target_value(&e); + set_input_value(new_value.clone()); + on_input(new_value); + }; + + view! { + + } +} diff --git a/src/components/items_list.rs b/src/components/items_list.rs index 53940a6..c2a5da2 100644 --- a/src/components/items_list.rs +++ b/src/components/items_list.rs @@ -1,144 +1,182 @@ -use crate::models::item::Item; -use gloo_net::http::Request; -use leptos::logging::log; +use crate::components::editable_cell::EditableCell; +use crate::components::tag_editor::TagEditor; use leptos::*; use serde::Deserialize; -use std::collections::HashMap; -use wasm_bindgen_futures::spawn_local; +use uuid::Uuid; #[derive(Deserialize, Clone, Debug)] -struct WikidataResponse { - entities: HashMap, +struct WikidataSuggestion { + id: String, + label: String, + description: Option, } -#[derive(Deserialize, Clone, Debug)] -struct WikidataEntity { - labels: Option>, - descriptions: Option>, -} - -#[derive(Deserialize, Clone, Debug)] -struct WikidataLabel { - value: String, +#[derive(Clone, Debug)] +struct Item { + id: String, + name: String, + description: String, + tags: Vec<(String, String)>, + wikidata_id: Option, } #[component] -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()); +pub fn ItemsList( + items: ReadSignal>, + set_items: WriteSignal>, +) -> impl IntoView { + let (wikidata_suggestions, set_wikidata_suggestions) = + create_signal(Vec::::new()); - // Fetch data from Wikidata for a given item name - let fetch_wikidata = move |item_name: String| { + // Fetch Wikidata suggestions + let fetch_wikidata_suggestions = move |query: String| { spawn_local(async move { - 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())); - }); - } + if query.is_empty() { + set_wikidata_suggestions(Vec::new()); + return; + } + + let url = format!( + "https://www.wikidata.org/w/api.php?action=wbsearchentities&search={}&language=en&limit=5&format=json&origin=*", + query + ); + + match gloo_net::http::Request::get(&url).send().await { + Ok(response) => { + if let Ok(data) = response.json::().await { + set_wikidata_suggestions(data.search); } - Err(_) => log!("Failed to parse response from Wikidata"), - }, - Err(_) => log!("Failed to make request to Wikidata"), + } + Err(_) => log!("Failed to fetch Wikidata suggestions"), } }); }; - // Handle updating grid cells - let update_item = move |index: usize, column: &str, value: String| { + // Update item fields + let update_item = move |index: usize, field: &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); - } + match field { + "name" => { + item.name = value.clone(); + fetch_wikidata_suggestions(value); } + "description" => item.description = value, _ => (), } } }); }; - // Trigger add item event - let add_item = move |_| { - on_add_item(); // Call the passed closure from App to add a new item + // Add a new tag to an item + let add_tag = move |index: usize, key: String, value: String| { + set_items.update(|items| { + if let Some(item) = items.get_mut(index) { + item.tags.push((key, value)); + } + }); + }; + + // Remove a tag from an item + let remove_tag = move |item_index: usize, tag_index: usize| { + set_items.update(|items| { + if let Some(item) = items.get_mut(item_index) { + item.tags.remove(tag_index); + } + }); + }; + + // Add a new item + let add_item = move || { + set_items.update(|items| { + items.push(Item { + id: Uuid::new_v4().to_string(), + name: String::new(), + description: String::new(), + tags: vec![], + wikidata_id: None, + }); + }); + }; + + // Remove an item + let remove_item = move |index: usize| { + set_items.update(|items| { + items.remove(index); + }); }; view! {
-

{ "CompareWare" }

- +

{ "Items List" }

- - - + - {move || { - items.get().iter().enumerate().map(|(index, item)| { - view! { - - - - -
{ "Name" } { "Description" } { "Tags" }{ "Review" }{ "Rating" }{ "Actions" }
- - - - - - -