From ebb1afd1af3055565d67ccca20558cc64ae9cee6 Mon Sep 17 00:00:00 2001 From: ryan Date: Tue, 25 Mar 2025 15:51:49 +0300 Subject: [PATCH 1/8] fix(properties): show property labels in custom property autocomplete dropdown --- src/components/items_list.rs | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/components/items_list.rs b/src/components/items_list.rs index 24e4fb1..9dced5e 100644 --- a/src/components/items_list.rs +++ b/src/components/items_list.rs @@ -410,10 +410,15 @@ pub fn ItemsList( let mut result = HashMap::new(); if let Some(bindings) = data["results"]["bindings"].as_array() { for binding in bindings { - let prop_label = binding["propLabel"]["value"].as_str().unwrap_or("").to_string(); - let prop_label = prop_label.replace("http://www.wikidata.org/prop/", ""); + let prop = binding["propLabel"]["value"].as_str().unwrap_or("").to_string(); + let prop_id = prop.replace("http://www.wikidata.org/prop/", ""); let value_label = binding["valueLabel"]["value"].as_str().unwrap_or("").to_string(); - result.insert(prop_label, value_label); + + // Fetch property labels + let prop_label_map = fetch_property_labels(vec![prop_id]).await; + if let Some(prop_label) = prop_label_map.values().next() { + result.insert(prop_label.clone(), value_label); + } log!("result: {:?}", result); } } @@ -944,7 +949,7 @@ pub fn ItemsList( property_labels.into_iter().map(|(property, label)| { let property_clone = property.clone(); view! { - + } }).collect::>() }} -- 2.45.3 From 5c3070bfc0e257126429c7aa6477e1a166e8fb0c Mon Sep 17 00:00:00 2001 From: ryan Date: Wed, 26 Mar 2025 14:48:58 +0300 Subject: [PATCH 2/8] fix(labels): update set_property_labels signal in fetch_item_properties function to avoid repetitive label fetches --- src/components/items_list.rs | 31 +++++++++++++++---------------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/src/components/items_list.rs b/src/components/items_list.rs index 9dced5e..2a6a766 100644 --- a/src/components/items_list.rs +++ b/src/components/items_list.rs @@ -381,7 +381,7 @@ pub fn ItemsList( }; //function to fetch properties - async fn fetch_item_properties(wikidata_id: &str) -> HashMap { + async fn fetch_item_properties(wikidata_id: &str, set_property_labels: WriteSignal>) -> HashMap { let sparql_query = format!( r#" SELECT ?propLabel ?value ?valueLabel WHERE {{ @@ -416,6 +416,12 @@ pub fn ItemsList( // Fetch property labels let prop_label_map = fetch_property_labels(vec![prop_id]).await; + set_property_labels.update(|labels_map| { + for (key, value) in &prop_label_map { + labels_map.insert(key.clone(), value.clone()); + } + }); + if let Some(prop_label) = prop_label_map.values().next() { result.insert(prop_label.clone(), value_label); } @@ -520,6 +526,7 @@ pub fn ItemsList( let add_property = { let current_url = Rc::clone(¤t_url); let set_items = set_items.clone(); + let set_property_labels = set_property_labels.clone(); Arc::new(move |property: String| { // Normalize the property ID let normalized_property = property.replace("http://www.wikidata.org/prop/", ""); @@ -586,18 +593,10 @@ pub fn ItemsList( } }); - // Fetch the property label - let property_id = normalized_property.clone(); - spawn_local(async move { - let labels = fetch_property_labels(vec![property_id.clone()]).await; - log!("Fetched labels: {:?}", labels); - set_property_labels.update(|labels_map| { - for (key, value) in labels { - log!("Inserting label: {} -> {}", key, value); - labels_map.insert(key, value); - } - }); - }); + // Use the property label from the property_labels signal + let property_label = property_labels.get().get(&normalized_property).cloned().unwrap_or_else(|| normalized_property.clone()); + log!("Added property with label: {}", property_label); + } }); // Fetch the relevant value for each item and populate the corresponding cells @@ -609,7 +608,7 @@ pub fn ItemsList( let set_property_labels = set_property_labels.clone(); let property_clone = property.clone(); spawn_local(async move { - let properties = fetch_item_properties(&wikidata_id).await; + let properties = fetch_item_properties(&wikidata_id, set_property_labels.clone()).await; // Update fetched properties and property labels set_fetched_properties.update(|fp| { fp.insert(wikidata_id.clone(), properties.clone()); @@ -651,7 +650,7 @@ pub fn ItemsList( if let Some(wikidata_id) = &item.wikidata_id { let wikidata_id = wikidata_id.clone(); spawn_local(async move { - let properties = fetch_item_properties(&wikidata_id).await; + let properties = fetch_item_properties(&wikidata_id, set_property_labels.clone()).await; log!("Fetched properties for index {}: {:?}", index, properties); }); } @@ -800,7 +799,7 @@ pub fn ItemsList( // Fetch additional properties from Wikidata let wikidata_id = id.clone(); spawn_local(async move { - let properties = fetch_item_properties(&wikidata_id).await; + let properties = fetch_item_properties(&wikidata_id, set_property_labels.clone()).await; // log!("Fetched properties for Wikidata ID {}: {:?}", wikidata_id, properties); // Populate the custom properties for the new item -- 2.45.3 From ef7245b716299f16c6f60bfbc50e729004c12458 Mon Sep 17 00:00:00 2001 From: ryan Date: Thu, 27 Mar 2025 15:31:58 +0300 Subject: [PATCH 3/8] fix(labels): add property cache and refactor fetch_item_properties function to use it --- src/components/items_list.rs | 96 +++++++++++++++++++++++++++++------- 1 file changed, 77 insertions(+), 19 deletions(-) diff --git a/src/components/items_list.rs b/src/components/items_list.rs index 2a6a766..76a2677 100644 --- a/src/components/items_list.rs +++ b/src/components/items_list.rs @@ -141,6 +141,8 @@ pub fn ItemsList( // Signal to store the fetched property labels let (property_labels, set_property_labels) = create_signal(HashMap::::new()); + // State to manage property cache + let (property_cache, set_property_cache) = create_signal(HashMap::>::new()); #[cfg(feature = "ssr")] fn get_current_url() -> String { use leptos::use_context; @@ -381,7 +383,19 @@ pub fn ItemsList( }; //function to fetch properties - async fn fetch_item_properties(wikidata_id: &str, set_property_labels: WriteSignal>) -> HashMap { + async fn fetch_item_properties( + wikidata_id: &str, + set_property_labels: WriteSignal>, + property_cache: ReadSignal>>, + set_property_cache: WriteSignal>>, + property_labels: ReadSignal>, + ) -> HashMap { + + // Check cache first + if let Some(cached) = property_cache.get().get(wikidata_id) { + return cached.clone(); + } + let sparql_query = format!( r#" SELECT ?propLabel ?value ?valueLabel WHERE {{ @@ -408,28 +422,56 @@ pub fn ItemsList( Ok(response) => { if let Ok(data) = response.json::().await { let mut result = HashMap::new(); + let mut prop_ids = Vec::new(); + + // First pass: collect unique property IDs if let Some(bindings) = data["results"]["bindings"].as_array() { for binding in bindings { - let prop = binding["propLabel"]["value"].as_str().unwrap_or("").to_string(); - let prop_id = prop.replace("http://www.wikidata.org/prop/", ""); - let value_label = binding["valueLabel"]["value"].as_str().unwrap_or("").to_string(); - - // Fetch property labels - let prop_label_map = fetch_property_labels(vec![prop_id]).await; - set_property_labels.update(|labels_map| { - for (key, value) in &prop_label_map { - labels_map.insert(key.clone(), value.clone()); + if let Some(prop) = binding["propLabel"]["value"].as_str() { + let prop_id = prop.replace("http://www.wikidata.org/prop/", ""); + if !prop_ids.contains(&prop_id) { + prop_ids.push(prop_id.clone()); } - }); - - if let Some(prop_label) = prop_label_map.values().next() { - result.insert(prop_label.clone(), value_label); } - log!("result: {:?}", result); } } + + // Batch fetch missing labels + let existing_labels = property_labels.get(); + let missing_ids: Vec = prop_ids + .iter() + .filter(|id| !existing_labels.contains_key(*id)) + .cloned() + .collect(); + + if !missing_ids.is_empty() { + let new_labels = fetch_property_labels(missing_ids).await; + set_property_labels.update(|labels| { + labels.extend(new_labels.clone()); + }); + } + + // Second pass: build results + if let Some(bindings) = data["results"]["bindings"].as_array() { + for binding in bindings { + if let (Some(prop), Some(value_label)) = ( + binding["propLabel"]["value"].as_str(), + binding["valueLabel"]["value"].as_str() + ) { + let prop_id = prop.replace("http://www.wikidata.org/prop/", ""); + if let Some(label) = property_labels.get().get(&prop_id).cloned() { + result.insert(label.clone(), value_label.to_string()); + } + } + } + } + + // Update cache + set_property_cache.update(|cache| { + cache.insert(wikidata_id.to_string(), result.clone()); + }); + result - } else { HashMap::new() } @@ -527,11 +569,27 @@ pub fn ItemsList( let current_url = Rc::clone(¤t_url); let set_items = set_items.clone(); let set_property_labels = set_property_labels.clone(); + let property_cache = property_cache.clone(); + let set_property_cache = set_property_cache.clone(); Arc::new(move |property: String| { // Normalize the property ID let normalized_property = property.replace("http://www.wikidata.org/prop/", ""); let normalized_property_clone = normalized_property.clone(); + // Check if label already exists + if !property_labels.get().contains_key(&normalized_property) { + spawn_local({ + let normalized_property = normalized_property.clone(); + let set_property_labels = set_property_labels.clone(); + async move { + let labels = fetch_property_labels(vec![normalized_property.clone()]).await; + set_property_labels.update(|map| { + map.extend(labels); + }); + } + }); + } + // Check if property is already selected if !selected_properties.get().contains_key(&normalized_property) && !normalized_property.is_empty() { // Add property to selected properties @@ -608,7 +666,7 @@ pub fn ItemsList( let set_property_labels = set_property_labels.clone(); let property_clone = property.clone(); spawn_local(async move { - let properties = fetch_item_properties(&wikidata_id, set_property_labels.clone()).await; + let properties = fetch_item_properties(&wikidata_id, set_property_labels.clone(), property_cache.clone(), set_property_cache.clone(), property_labels.clone()).await; // Update fetched properties and property labels set_fetched_properties.update(|fp| { fp.insert(wikidata_id.clone(), properties.clone()); @@ -650,7 +708,7 @@ pub fn ItemsList( if let Some(wikidata_id) = &item.wikidata_id { let wikidata_id = wikidata_id.clone(); spawn_local(async move { - let properties = fetch_item_properties(&wikidata_id, set_property_labels.clone()).await; + let properties = fetch_item_properties(&wikidata_id, set_property_labels.clone(), property_cache.clone(), set_property_cache.clone(), property_labels.clone()).await; log!("Fetched properties for index {}: {:?}", index, properties); }); } @@ -799,7 +857,7 @@ pub fn ItemsList( // Fetch additional properties from Wikidata let wikidata_id = id.clone(); spawn_local(async move { - let properties = fetch_item_properties(&wikidata_id, set_property_labels.clone()).await; + let properties = fetch_item_properties(&wikidata_id, set_property_labels.clone(), property_cache.clone(), set_property_cache.clone(), property_labels.clone()).await; // log!("Fetched properties for Wikidata ID {}: {:?}", wikidata_id, properties); // Populate the custom properties for the new item -- 2.45.3 From 40bb35d6a81265fb1999012794edb84b37938bf0 Mon Sep 17 00:00:00 2001 From: ryan Date: Mon, 31 Mar 2025 17:36:46 +0300 Subject: [PATCH 4/8] feat(labels): add state to track when labels are being fetched(in progress) --- src/components/items_list.rs | 408 ++++++++++++++++++++--------------- 1 file changed, 230 insertions(+), 178 deletions(-) diff --git a/src/components/items_list.rs b/src/components/items_list.rs index 76a2677..615ff30 100644 --- a/src/components/items_list.rs +++ b/src/components/items_list.rs @@ -143,6 +143,10 @@ pub fn ItemsList( // State to manage property cache let (property_cache, set_property_cache) = create_signal(HashMap::>::new()); + + // State to track when labels are being fetched + let (is_fetching_labels, set_is_fetching_labels) = create_signal(false); + #[cfg(feature = "ssr")] fn get_current_url() -> String { use leptos::use_context; @@ -206,7 +210,11 @@ pub fn ItemsList( // Fetch labels for the custom properties let property_ids = custom_props_clone; - let labels = fetch_property_labels(property_ids).await; + let labels = fetch_property_labels( + property_ids, + set_property_labels.clone(), + move |value| set_is_fetching_labels.set(value), + ).await; set_property_labels.update(|labels_map| { for (key, value) in labels { labels_map.insert(key, value); @@ -324,7 +332,7 @@ pub fn ItemsList( ) .send() .await; - + match response { Ok(resp) => { if resp.status() == 200 { @@ -408,12 +416,12 @@ pub fn ItemsList( "#, wikidata_id ); - + let url = format!( "https://query.wikidata.org/sparql?query={}&format=json", urlencoding::encode(&sparql_query) ); - + match gloo_net::http::Request::get(&url) .header("Accept", "application/json") .send() @@ -423,7 +431,7 @@ pub fn ItemsList( if let Ok(data) = response.json::().await { let mut result = HashMap::new(); let mut prop_ids = Vec::new(); - + // First pass: collect unique property IDs if let Some(bindings) = data["results"]["bindings"].as_array() { for binding in bindings { @@ -435,7 +443,7 @@ pub fn ItemsList( } } } - + // Batch fetch missing labels let existing_labels = property_labels.get(); let missing_ids: Vec = prop_ids @@ -443,20 +451,24 @@ pub fn ItemsList( .filter(|id| !existing_labels.contains_key(*id)) .cloned() .collect(); - + if !missing_ids.is_empty() { - let new_labels = fetch_property_labels(missing_ids).await; + let new_labels = fetch_property_labels( + missing_ids, + set_property_labels.clone(), + move |value| { set_is_fetching_labels.set(value) }, + ).await; set_property_labels.update(|labels| { labels.extend(new_labels.clone()); }); } - + // Second pass: build results if let Some(bindings) = data["results"]["bindings"].as_array() { for binding in bindings { if let (Some(prop), Some(value_label)) = ( binding["propLabel"]["value"].as_str(), - binding["valueLabel"]["value"].as_str() + binding["valueLabel"]["value"].as_str(), ) { let prop_id = prop.replace("http://www.wikidata.org/prop/", ""); if let Some(label) = property_labels.get().get(&prop_id).cloned() { @@ -465,12 +477,12 @@ pub fn ItemsList( } } } - + // Update cache set_property_cache.update(|cache| { cache.insert(wikidata_id.to_string(), result.clone()); }); - + result } else { HashMap::new() @@ -479,16 +491,21 @@ pub fn ItemsList( Err(_) => HashMap::new(), } } - - async fn fetch_property_labels(property_ids: Vec) -> HashMap { + + async fn fetch_property_labels( + property_ids: Vec, + set_property_labels: WriteSignal>, + set_is_fetching_labels: impl Fn(bool) + 'static, + ) -> HashMap { + set_is_fetching_labels(true); log!("Fetching property labels for properties: {:?}", property_ids); - + // Remove the "http://www.wikidata.org/prop/" prefix from property IDs let property_ids: Vec = property_ids .into_iter() .map(|id| id.replace("http://www.wikidata.org/prop/", "")) .collect(); - + let property_ids_str = property_ids.join(" wd:"); let sparql_query = format!( r#" @@ -499,13 +516,13 @@ pub fn ItemsList( "#, property_ids_str ); - + let url = format!( "https://query.wikidata.org/sparql?query={}&format=json", urlencoding::encode(&sparql_query) ); log!("Sending request to URL: {}", url); - + match gloo_net::http::Request::get(&url) .header("Accept", "application/json") .send() @@ -517,7 +534,7 @@ pub fn ItemsList( log!("Error: Unexpected status code {}", response.status()); return HashMap::new(); } - + match response.text().await { Ok(text) => { log!("Response body: {}", text); @@ -532,7 +549,8 @@ pub fn ItemsList( binding["prop"]["value"].as_str(), binding["propLabel"]["value"].as_str() ) { - let prop_id = prop.split('/').last().unwrap_or("").to_string(); + let prop_id = + prop.split('/').last().unwrap_or("").to_string(); result.insert(prop_id.clone(), label.to_string()); log!("Processed binding {}: prop_id = {}, label = {}", i, prop_id, label); } else { @@ -543,6 +561,7 @@ pub fn ItemsList( log!("Warning: No bindings found in the response"); } log!("Fetched {} property labels", result.len()); + set_is_fetching_labels(false); result } Err(e) => { @@ -563,7 +582,7 @@ pub fn ItemsList( } } } - + // Add a new custom property let add_property = { let current_url = Rc::clone(¤t_url); @@ -572,189 +591,210 @@ pub fn ItemsList( let property_cache = property_cache.clone(); let set_property_cache = set_property_cache.clone(); Arc::new(move |property: String| { - // Normalize the property ID - let normalized_property = property.replace("http://www.wikidata.org/prop/", ""); - let normalized_property_clone = normalized_property.clone(); + // Normalize the property ID + let normalized_property = property.replace("http://www.wikidata.org/prop/", ""); + let normalized_property_clone = normalized_property.clone(); - // Check if label already exists - if !property_labels.get().contains_key(&normalized_property) { - spawn_local({ - let normalized_property = normalized_property.clone(); - let set_property_labels = set_property_labels.clone(); - async move { - let labels = fetch_property_labels(vec![normalized_property.clone()]).await; - set_property_labels.update(|map| { - map.extend(labels); - }); - } - }); - } - - // Check if property is already selected - if !selected_properties.get().contains_key(&normalized_property) && !normalized_property.is_empty() { - // Add property to selected properties - set_selected_properties.update(|selected| { - selected.insert(normalized_property.clone(), true); - }); - - // Save the selected property to the database - spawn_local({ - let current_url = Rc::clone(¤t_url); - let normalized_property = normalized_property_clone.clone(); - async move { - let response = gloo_net::http::Request::post( - &format!("/api/urls/{}/properties", encode(¤t_url)) - ) - .json(&normalized_property) - .unwrap() - .send() - .await; - - match response { - Ok(resp) => { - if resp.status() == 200 { - log!("Property saved successfully"); - } else { - log!("Error saving property: {}", resp.status_text()); - } - } - Err(err) => { - log!("Error saving property: {:?}", err); - } + // Check if label already exists + if !property_labels.get().contains_key(&normalized_property) { + spawn_local({ + let normalized_property = normalized_property.clone(); + let set_property_labels = set_property_labels.clone(); + async move { + let labels = fetch_property_labels( + vec![normalized_property.clone()], + set_property_labels.clone(), + move |value| set_is_fetching_labels.set(value), + ) + .await; + set_property_labels.update(|map| { + map.extend(labels); + }); } - } - }); - } + }); + } - set_custom_properties.update(|props| { - if !props.contains(&normalized_property) && !normalized_property.is_empty() { - props.push(normalized_property.clone()); - - //update the selected_properties state when a new property is added + // Check if property is already selected + if !selected_properties.get().contains_key(&normalized_property) && !normalized_property.is_empty() { + // Add property to selected properties set_selected_properties.update(|selected| { selected.insert(normalized_property.clone(), true); }); - // Ensure the grid updates reactively - set_items.update(|items| { - for item in items { - item.custom_properties.entry(normalized_property.clone()).or_insert_with(|| "".to_string()); - - // Save the updated item to the database - let item_clone = item.clone(); - spawn_local({ - let current_url = Rc::clone(¤t_url); - async move { - save_item_to_db(item_clone, selected_properties, current_url.to_string()).await; + // Save the selected property to the database + spawn_local({ + let current_url = Rc::clone(¤t_url); + let normalized_property = normalized_property_clone.clone(); + async move { + let response = gloo_net::http::Request::post( + &format!("/api/urls/{}/properties", encode(¤t_url)) + ) + .json(&normalized_property) + .unwrap() + .send() + .await; + + match response { + Ok(resp) => { + if resp.status() == 200 { + log!("Property saved successfully"); + } else { + log!("Error saving property: {}", resp.status_text()); + } } - }); + Err(err) => { + log!("Error saving property: {:?}", err); + } + } } }); - - // Use the property label from the property_labels signal - let property_label = property_labels.get().get(&normalized_property).cloned().unwrap_or_else(|| normalized_property.clone()); - log!("Added property with label: {}", property_label); - } - }); - // Fetch the relevant value for each item and populate the corresponding cells - set_items.update(|items| { - for item in items { - if let Some(wikidata_id) = &item.wikidata_id { - let wikidata_id = wikidata_id.clone(); - let set_fetched_properties = set_fetched_properties.clone(); - let set_property_labels = set_property_labels.clone(); - let property_clone = property.clone(); - spawn_local(async move { - let properties = fetch_item_properties(&wikidata_id, set_property_labels.clone(), property_cache.clone(), set_property_cache.clone(), property_labels.clone()).await; - // Update fetched properties and property labels - set_fetched_properties.update(|fp| { - fp.insert(wikidata_id.clone(), properties.clone()); - }); - set_property_labels.update(|pl| { - for (key, value) in properties.iter() { - pl.entry(key.clone()).or_insert_with(|| value.clone()); - } - }); - if let Some(value) = properties.get(&property_clone) { - set_items.update(|items| { - if let Some(item) = items.iter_mut().find(|item| item.wikidata_id.as_ref().unwrap() == &wikidata_id) { - item.custom_properties.insert(property_clone.clone(), value.clone()); + + set_custom_properties.update(|props| { + if !props.contains(&normalized_property) && !normalized_property.is_empty() { + props.push(normalized_property.clone()); + + //update the selected_properties state when a new property is added + set_selected_properties.update(|selected| { + selected.insert(normalized_property.clone(), true); + }); + + // Ensure the grid updates reactively + set_items.update(|items| { + for item in items { + item.custom_properties.entry(normalized_property.clone()).or_insert_with(|| "".to_string()); + + // Save the updated item to the database + let item_clone = item.clone(); + spawn_local({ + let current_url = Rc::clone(¤t_url); + async move { + save_item_to_db(item_clone, selected_properties, current_url.to_string()).await; } }); } }); + + // Use the property label from the property_labels signal + let property_label = property_labels.get().get(&normalized_property).cloned().unwrap_or_else(|| normalized_property.clone()); + log!("Added property with label: {}", property_label); + } - } - }); - })}; - + }); + // Fetch the relevant value for each item and populate the corresponding cells + set_items.update(|items| { + for item in items { + if let Some(wikidata_id) = &item.wikidata_id { + let wikidata_id = wikidata_id.clone(); + let set_fetched_properties = set_fetched_properties.clone(); + let set_property_labels = set_property_labels.clone(); + let property_clone = property.clone(); + spawn_local(async move { + let properties = fetch_item_properties( + &wikidata_id, + set_property_labels.clone(), + property_cache.clone(), + set_property_cache.clone(), + property_labels.clone(), + ) + .await; + // Update fetched properties and property labels + set_fetched_properties.update(|fp| { + fp.insert(wikidata_id.clone(), properties.clone()); + }); + set_property_labels.update(|pl| { + for (key, value) in properties.iter() { + pl.entry(key.clone()).or_insert_with(|| value.clone()); + } + }); + if let Some(value) = properties.get(&property_clone) { + set_items.update(|items| { + if let Some(item) = items.iter_mut().find(|item| item.wikidata_id.as_ref().unwrap() == &wikidata_id) { + item.custom_properties.insert(property_clone.clone(), value.clone()); + } + }); + } + }); + } + } + }); + }) + }; + // Update item fields let update_item = { let set_items = set_items.clone(); let current_url = Rc::clone(¤t_url); Arc::new(move |index: usize, field: &str, value: String| { - let set_items = set_items.clone(); - let current_url = Rc::clone(¤t_url); + let set_items = set_items.clone(); + let current_url = Rc::clone(¤t_url); set_items.update(move|items| { - if let Some(item) = items.get_mut(index) { - match field { - "name" => { - item.name = value.clone(); - fetch_wikidata_suggestions(format!("name-{}", index), value.clone()); + if let Some(item) = items.get_mut(index) { + match field { + "name" => { + item.name = value.clone(); + fetch_wikidata_suggestions(format!("name-{}", index), value.clone()); - // Fetch Wikidata properties if the field is "name" and the item has a valid Wikidata ID - if !value.is_empty() { - if let Some(wikidata_id) = &item.wikidata_id { - let wikidata_id = wikidata_id.clone(); - spawn_local(async move { - let properties = fetch_item_properties(&wikidata_id, set_property_labels.clone(), property_cache.clone(), set_property_cache.clone(), property_labels.clone()).await; + // Fetch Wikidata properties if the field is "name" and the item has a valid Wikidata ID + if !value.is_empty() { + if let Some(wikidata_id) = &item.wikidata_id { + let wikidata_id = wikidata_id.clone(); + spawn_local(async move { + let properties = fetch_item_properties( + &wikidata_id, + set_property_labels.clone(), + property_cache.clone(), + set_property_cache.clone(), + property_labels.clone(), + ) + .await; log!("Fetched properties for index {}: {:?}", index, properties); - }); + }); + } } } - } - "description" => { - item.description = value.clone(); - } - _ => { - // Update custom property + "description" => { + item.description = value.clone(); + } + _ => { + // Update custom property item.custom_properties.insert(field.to_string(), value.clone()); + } } - } - // Save the updated item to the database - let item_clone = item.clone(); - spawn_local({ - let current_url = Rc::clone(¤t_url); - async move { + // Save the updated item to the database + let item_clone = item.clone(); + spawn_local({ + let current_url = Rc::clone(¤t_url); + async move { save_item_to_db(item_clone, selected_properties, current_url.to_string()).await; - } - }); - } - // Automatically add a new row when editing the last row - if index == items.len() - 1 && !value.is_empty() { - let new_item = Item { - id: Uuid::new_v4().to_string(), - name: String::new(), - description: String::new(), - // reviews: vec![], - wikidata_id: None, - custom_properties: HashMap::new(), - }; - items.push(new_item.clone()); + } + }); + } + // Automatically add a new row when editing the last row + if index == items.len() - 1 && !value.is_empty() { + let new_item = Item { + id: Uuid::new_v4().to_string(), + name: String::new(), + description: String::new(), + // reviews: vec![], + wikidata_id: None, + custom_properties: HashMap::new(), + }; + items.push(new_item.clone()); - // Save the new item to the database - spawn_local({ - let current_url = Rc::clone(¤t_url); - async move { + // Save the new item to the database + spawn_local({ + let current_url = Rc::clone(¤t_url); + async move { save_item_to_db(new_item, selected_properties, current_url.to_string()).await; - } - }); - } - log!("Items updated: {:?}", items); - }); - })}; + } + }); + } + log!("Items updated: {:?}", items); + }); + }) + }; // List of properties to display as rows let properties = vec!["Name", "Description"]; @@ -774,7 +814,7 @@ pub fn ItemsList( } - }).collect::>()} + }).collect::>()} @@ -842,7 +882,7 @@ pub fn ItemsList( let label_for_display = suggestion.label.clone(); let description_for_click = suggestion.description.clone().unwrap_or_default(); let description_for_display = suggestion.description.clone().unwrap_or_default(); - let id = suggestion.id.clone(); + let id = suggestion.id.clone(); view! {
  • } - }).collect::>()} + }).collect::>()} } }).collect::>()} @@ -933,10 +973,20 @@ pub fn ItemsList( let property_label = property_labels.get().get(&normalized_property).cloned().unwrap_or_else(|| normalized_property.clone()); log!("Rendering property: {} -> {}", normalized_property, property_label); let property_clone_for_button = normalized_property.clone(); + let normalized_property_clone = normalized_property.clone(); view! { - { property_label } + {move || { + let property_label = property_labels.get().get(&normalized_property).cloned(); + let is_fetching = is_fetching_labels.get(); + + if is_fetching { + normalized_property.clone() + } else { + property_label.unwrap_or_else(|| normalized_property.clone()) + } + }} + let normalized_property = property.replace("http://www.wikidata.org/prop/", ""); + let normalized_property_clone = normalized_property.clone(); {move || { let update_item_cell = Arc::clone(&update_item_inner); - let property_clone_for_cells = normalized_property.clone(); + let property_clone_for_cells = normalized_property_clone.clone(); items.get().iter().enumerate().map(move |(index, item)| { let update_item_cell = Arc::clone(&update_item_cell); let property_clone_for_closure = property_clone_for_cells.clone(); -- 2.45.3 From 9d21d9999f1f469910d76247ed29cd0584f7d104 Mon Sep 17 00:00:00 2001 From: ryan Date: Mon, 31 Mar 2025 18:07:36 +0300 Subject: [PATCH 5/8] fix(labels): add set_is_fetching_labels signal to fetch_item_properties label to fix error --- .gitignore | 3 ++- src/components/items_list.rs | 6 +++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index a2868de..1d53e8f 100644 --- a/.gitignore +++ b/.gitignore @@ -18,4 +18,5 @@ playwright/.cache/ .idea/ # Ignore database file -compareware.db \ No newline at end of file +compareware.db +.qodo diff --git a/src/components/items_list.rs b/src/components/items_list.rs index 615ff30..2fead66 100644 --- a/src/components/items_list.rs +++ b/src/components/items_list.rs @@ -397,6 +397,7 @@ pub fn ItemsList( property_cache: ReadSignal>>, set_property_cache: WriteSignal>>, property_labels: ReadSignal>, + set_is_fetching_labels: WriteSignal, ) -> HashMap { // Check cache first @@ -453,6 +454,7 @@ pub fn ItemsList( .collect(); if !missing_ids.is_empty() { + let new_labels = fetch_property_labels( missing_ids, set_property_labels.clone(), @@ -696,6 +698,7 @@ pub fn ItemsList( property_cache.clone(), set_property_cache.clone(), property_labels.clone(), + set_is_fetching_labels.clone() ) .await; // Update fetched properties and property labels @@ -746,6 +749,7 @@ pub fn ItemsList( property_cache.clone(), set_property_cache.clone(), property_labels.clone(), + set_is_fetching_labels.clone() ) .await; log!("Fetched properties for index {}: {:?}", index, properties); @@ -897,7 +901,7 @@ pub fn ItemsList( // Fetch additional properties from Wikidata let wikidata_id = id.clone(); spawn_local(async move { - let properties = fetch_item_properties(&wikidata_id, set_property_labels.clone(), property_cache.clone(), set_property_cache.clone(), property_labels.clone()).await; + let properties = fetch_item_properties(&wikidata_id, set_property_labels.clone(), property_cache.clone(), set_property_cache.clone(), property_labels.clone(),set_is_fetching_labels.clone()).await; // log!("Fetched properties for Wikidata ID {}: {:?}", wikidata_id, properties); // Populate the custom properties for the new item -- 2.45.3 From 8c7946091f04476532c361623163c804def39dd4 Mon Sep 17 00:00:00 2001 From: ryan Date: Mon, 31 Mar 2025 23:50:35 +0300 Subject: [PATCH 6/8] fix(labels): display correct labels in dropdown and when properties are added --- src/components/items_list.rs | 487 +++++++++++++++++------------------ 1 file changed, 230 insertions(+), 257 deletions(-) diff --git a/src/components/items_list.rs b/src/components/items_list.rs index 2fead66..2fb4d10 100644 --- a/src/components/items_list.rs +++ b/src/components/items_list.rs @@ -143,10 +143,6 @@ pub fn ItemsList( // State to manage property cache let (property_cache, set_property_cache) = create_signal(HashMap::>::new()); - - // State to track when labels are being fetched - let (is_fetching_labels, set_is_fetching_labels) = create_signal(false); - #[cfg(feature = "ssr")] fn get_current_url() -> String { use leptos::use_context; @@ -210,11 +206,7 @@ pub fn ItemsList( // Fetch labels for the custom properties let property_ids = custom_props_clone; - let labels = fetch_property_labels( - property_ids, - set_property_labels.clone(), - move |value| set_is_fetching_labels.set(value), - ).await; + let labels = fetch_property_labels(property_ids).await; set_property_labels.update(|labels_map| { for (key, value) in labels { labels_map.insert(key, value); @@ -332,7 +324,7 @@ pub fn ItemsList( ) .send() .await; - + match response { Ok(resp) => { if resp.status() == 200 { @@ -397,7 +389,6 @@ pub fn ItemsList( property_cache: ReadSignal>>, set_property_cache: WriteSignal>>, property_labels: ReadSignal>, - set_is_fetching_labels: WriteSignal, ) -> HashMap { // Check cache first @@ -417,12 +408,12 @@ pub fn ItemsList( "#, wikidata_id ); - + let url = format!( "https://query.wikidata.org/sparql?query={}&format=json", urlencoding::encode(&sparql_query) ); - + match gloo_net::http::Request::get(&url) .header("Accept", "application/json") .send() @@ -432,7 +423,7 @@ pub fn ItemsList( if let Ok(data) = response.json::().await { let mut result = HashMap::new(); let mut prop_ids = Vec::new(); - + // First pass: collect unique property IDs if let Some(bindings) = data["results"]["bindings"].as_array() { for binding in bindings { @@ -444,7 +435,7 @@ pub fn ItemsList( } } } - + // Batch fetch missing labels let existing_labels = property_labels.get(); let missing_ids: Vec = prop_ids @@ -452,39 +443,48 @@ pub fn ItemsList( .filter(|id| !existing_labels.contains_key(*id)) .cloned() .collect(); - + if !missing_ids.is_empty() { - - let new_labels = fetch_property_labels( - missing_ids, - set_property_labels.clone(), - move |value| { set_is_fetching_labels.set(value) }, - ).await; + let new_labels = fetch_property_labels(missing_ids).await; set_property_labels.update(|labels| { labels.extend(new_labels.clone()); }); } - + // Second pass: build results if let Some(bindings) = data["results"]["bindings"].as_array() { for binding in bindings { - if let (Some(prop), Some(value_label)) = ( - binding["propLabel"]["value"].as_str(), - binding["valueLabel"]["value"].as_str(), + if let (Some(prop_uri), Some(value_label)) = ( + binding["prop"]["value"].as_str(), + binding["valueLabel"]["value"].as_str() ) { - let prop_id = prop.replace("http://www.wikidata.org/prop/", ""); + // Extract property ID from URI (e.g., "http://www.wikidata.org/prop/P123") + let prop_id = prop_uri.split('/').last().unwrap_or_default().to_string(); + + // Get the label from property_labels signal if let Some(label) = property_labels.get().get(&prop_id).cloned() { - result.insert(label.clone(), value_label.to_string()); + result.insert( + prop_id.clone(), + format!("{}: {}", + property_labels.get().get(&prop_id).cloned().unwrap_or(prop_id.clone()), + value_label + ) + ); + + // Ensure the label is stored in property_labels + set_property_labels.update(|labels| { + labels.entry(prop_id.clone()).or_insert_with(|| label.clone()); + }); } } } } - + // Update cache set_property_cache.update(|cache| { cache.insert(wikidata_id.to_string(), result.clone()); }); - + result } else { HashMap::new() @@ -493,21 +493,16 @@ pub fn ItemsList( Err(_) => HashMap::new(), } } - - async fn fetch_property_labels( - property_ids: Vec, - set_property_labels: WriteSignal>, - set_is_fetching_labels: impl Fn(bool) + 'static, - ) -> HashMap { - set_is_fetching_labels(true); + + async fn fetch_property_labels(property_ids: Vec) -> HashMap { log!("Fetching property labels for properties: {:?}", property_ids); - + // Remove the "http://www.wikidata.org/prop/" prefix from property IDs let property_ids: Vec = property_ids .into_iter() .map(|id| id.replace("http://www.wikidata.org/prop/", "")) .collect(); - + let property_ids_str = property_ids.join(" wd:"); let sparql_query = format!( r#" @@ -518,13 +513,13 @@ pub fn ItemsList( "#, property_ids_str ); - + let url = format!( "https://query.wikidata.org/sparql?query={}&format=json", urlencoding::encode(&sparql_query) ); log!("Sending request to URL: {}", url); - + match gloo_net::http::Request::get(&url) .header("Accept", "application/json") .send() @@ -536,7 +531,7 @@ pub fn ItemsList( log!("Error: Unexpected status code {}", response.status()); return HashMap::new(); } - + match response.text().await { Ok(text) => { log!("Response body: {}", text); @@ -551,8 +546,7 @@ pub fn ItemsList( binding["prop"]["value"].as_str(), binding["propLabel"]["value"].as_str() ) { - let prop_id = - prop.split('/').last().unwrap_or("").to_string(); + let prop_id = prop.split('/').last().unwrap_or("").to_string(); result.insert(prop_id.clone(), label.to_string()); log!("Processed binding {}: prop_id = {}, label = {}", i, prop_id, label); } else { @@ -563,7 +557,6 @@ pub fn ItemsList( log!("Warning: No bindings found in the response"); } log!("Fetched {} property labels", result.len()); - set_is_fetching_labels(false); result } Err(e) => { @@ -584,7 +577,7 @@ pub fn ItemsList( } } } - + // Add a new custom property let add_property = { let current_url = Rc::clone(¤t_url); @@ -593,212 +586,199 @@ pub fn ItemsList( let property_cache = property_cache.clone(); let set_property_cache = set_property_cache.clone(); Arc::new(move |property: String| { - // Normalize the property ID - let normalized_property = property.replace("http://www.wikidata.org/prop/", ""); - let normalized_property_clone = normalized_property.clone(); + // Normalize the property ID + let normalized_property = property.replace("http://www.wikidata.org/prop/", ""); + let normalized_property_clone = normalized_property.clone(); - // Check if label already exists - if !property_labels.get().contains_key(&normalized_property) { - spawn_local({ - let normalized_property = normalized_property.clone(); - let set_property_labels = set_property_labels.clone(); - async move { - let labels = fetch_property_labels( - vec![normalized_property.clone()], - set_property_labels.clone(), - move |value| set_is_fetching_labels.set(value), - ) - .await; - set_property_labels.update(|map| { - map.extend(labels); - }); - } - }); - } - - // Check if property is already selected + // Check if label already exists + if !property_labels.get().contains_key(&normalized_property) { + spawn_local({ + let normalized_property = normalized_property.clone(); + let set_property_labels = set_property_labels.clone(); + async move { + let labels = fetch_property_labels(vec![normalized_property.clone()]).await; + set_property_labels.update(|map| { + map.extend(labels); + }); + } + }); + } + + // Check if property is already selected if !selected_properties.get().contains_key(&normalized_property) && !normalized_property.is_empty() { - // Add property to selected properties + // Add property to selected properties + set_selected_properties.update(|selected| { + selected.insert(normalized_property.clone(), true); + }); + + // Save the selected property to the database + spawn_local({ + let current_url = Rc::clone(¤t_url); + let normalized_property = normalized_property_clone.clone(); + async move { + let response = gloo_net::http::Request::post( + &format!("/api/urls/{}/properties", encode(¤t_url)) + ) + .json(&normalized_property) + .unwrap() + .send() + .await; + + match response { + Ok(resp) => { + if resp.status() == 200 { + log!("Property saved successfully"); + } else { + log!("Error saving property: {}", resp.status_text()); + } + } + Err(err) => { + log!("Error saving property: {:?}", err); + } + } + } + }); + } + + set_custom_properties.update(|props| { + if !props.contains(&normalized_property) && !normalized_property.is_empty() { + props.push(normalized_property.clone()); + + //update the selected_properties state when a new property is added set_selected_properties.update(|selected| { selected.insert(normalized_property.clone(), true); }); - // Save the selected property to the database - spawn_local({ - let current_url = Rc::clone(¤t_url); - let normalized_property = normalized_property_clone.clone(); - async move { - let response = gloo_net::http::Request::post( - &format!("/api/urls/{}/properties", encode(¤t_url)) - ) - .json(&normalized_property) - .unwrap() - .send() - .await; - - match response { - Ok(resp) => { - if resp.status() == 200 { - log!("Property saved successfully"); - } else { - log!("Error saving property: {}", resp.status_text()); - } - } - Err(err) => { - log!("Error saving property: {:?}", err); - } - } - } - }); - } - - set_custom_properties.update(|props| { - if !props.contains(&normalized_property) && !normalized_property.is_empty() { - props.push(normalized_property.clone()); - - //update the selected_properties state when a new property is added - set_selected_properties.update(|selected| { - selected.insert(normalized_property.clone(), true); - }); - - // Ensure the grid updates reactively - set_items.update(|items| { - for item in items { + // Ensure the grid updates reactively + set_items.update(|items| { + for item in items { item.custom_properties.entry(normalized_property.clone()).or_insert_with(|| "".to_string()); - - // Save the updated item to the database - let item_clone = item.clone(); - spawn_local({ - let current_url = Rc::clone(¤t_url); - async move { + + // Save the updated item to the database + let item_clone = item.clone(); + spawn_local({ + let current_url = Rc::clone(¤t_url); + async move { save_item_to_db(item_clone, selected_properties, current_url.to_string()).await; - } - }); - } - }); - - // Use the property label from the property_labels signal - let property_label = property_labels.get().get(&normalized_property).cloned().unwrap_or_else(|| normalized_property.clone()); - log!("Added property with label: {}", property_label); - - } - }); - // Fetch the relevant value for each item and populate the corresponding cells - set_items.update(|items| { - for item in items { - if let Some(wikidata_id) = &item.wikidata_id { - let wikidata_id = wikidata_id.clone(); - let set_fetched_properties = set_fetched_properties.clone(); - let set_property_labels = set_property_labels.clone(); - let property_clone = property.clone(); - spawn_local(async move { - let properties = fetch_item_properties( - &wikidata_id, - set_property_labels.clone(), - property_cache.clone(), - set_property_cache.clone(), - property_labels.clone(), - set_is_fetching_labels.clone() - ) - .await; - // Update fetched properties and property labels - set_fetched_properties.update(|fp| { - fp.insert(wikidata_id.clone(), properties.clone()); - }); - set_property_labels.update(|pl| { - for (key, value) in properties.iter() { - pl.entry(key.clone()).or_insert_with(|| value.clone()); - } - }); - if let Some(value) = properties.get(&property_clone) { - set_items.update(|items| { - if let Some(item) = items.iter_mut().find(|item| item.wikidata_id.as_ref().unwrap() == &wikidata_id) { - item.custom_properties.insert(property_clone.clone(), value.clone()); - } - }); } }); } - } - }); - }) - }; + }); + // Use the property label from the property_labels signal + let property_label = property_labels.get().get(&normalized_property).cloned().unwrap_or_else(|| normalized_property.clone()); + log!("Added property with label: {}", property_label); + + } + }); + // Fetch the relevant value for each item and populate the corresponding cells + set_items.update(|items| { + for item in items { + // Initialize property with empty string if it doesn't exist + item.custom_properties.entry(normalized_property.clone()) + .or_insert_with(|| "".to_string()); + + // Only fetch properties if Wikidata ID exists + if let Some(wikidata_id) = &item.wikidata_id { + let wikidata_id = wikidata_id.clone(); + let set_items = set_items.clone(); + let set_fetched_properties = set_fetched_properties.clone(); + let property_clone = normalized_property.clone(); + + spawn_local(async move { + let properties = fetch_item_properties( + &wikidata_id, + set_property_labels.clone(), + property_cache.clone(), + set_property_cache.clone(), + property_labels.clone() + ).await; + + // Update the specific property for this item + if let Some(value) = properties.get(&property_clone) { + set_items.update(|items| { + if let Some(item) = items.iter_mut() + .find(|i| i.wikidata_id.as_ref() == Some(&wikidata_id)) + { + item.custom_properties.insert( + property_clone.clone(), + value.clone() + ); + } + }); + } + }); + } + } + }); + })}; + // Update item fields let update_item = { let set_items = set_items.clone(); let current_url = Rc::clone(¤t_url); Arc::new(move |index: usize, field: &str, value: String| { - let set_items = set_items.clone(); - let current_url = Rc::clone(¤t_url); + let set_items = set_items.clone(); + let current_url = Rc::clone(¤t_url); set_items.update(move|items| { - if let Some(item) = items.get_mut(index) { - match field { - "name" => { - item.name = value.clone(); - fetch_wikidata_suggestions(format!("name-{}", index), value.clone()); + if let Some(item) = items.get_mut(index) { + match field { + "name" => { + item.name = value.clone(); + fetch_wikidata_suggestions(format!("name-{}", index), value.clone()); - // Fetch Wikidata properties if the field is "name" and the item has a valid Wikidata ID - if !value.is_empty() { - if let Some(wikidata_id) = &item.wikidata_id { - let wikidata_id = wikidata_id.clone(); - spawn_local(async move { - let properties = fetch_item_properties( - &wikidata_id, - set_property_labels.clone(), - property_cache.clone(), - set_property_cache.clone(), - property_labels.clone(), - set_is_fetching_labels.clone() - ) - .await; + // Fetch Wikidata properties if the field is "name" and the item has a valid Wikidata ID + if !value.is_empty() { + if let Some(wikidata_id) = &item.wikidata_id { + let wikidata_id = wikidata_id.clone(); + spawn_local(async move { + let properties = fetch_item_properties(&wikidata_id, set_property_labels.clone(), property_cache.clone(), set_property_cache.clone(), property_labels.clone()).await; log!("Fetched properties for index {}: {:?}", index, properties); - }); - } + }); } } - "description" => { - item.description = value.clone(); - } - _ => { - // Update custom property - item.custom_properties.insert(field.to_string(), value.clone()); - } } + "description" => { + item.description = value.clone(); + } + _ => { + // Update custom property + item.custom_properties.insert(field.to_string(), value.clone()); + } + } - // Save the updated item to the database - let item_clone = item.clone(); - spawn_local({ - let current_url = Rc::clone(¤t_url); - async move { + // Save the updated item to the database + let item_clone = item.clone(); + spawn_local({ + let current_url = Rc::clone(¤t_url); + async move { save_item_to_db(item_clone, selected_properties, current_url.to_string()).await; - } - }); - } - // Automatically add a new row when editing the last row - if index == items.len() - 1 && !value.is_empty() { - let new_item = Item { - id: Uuid::new_v4().to_string(), - name: String::new(), - description: String::new(), - // reviews: vec![], - wikidata_id: None, - custom_properties: HashMap::new(), - }; - items.push(new_item.clone()); + } + }); + } + // Automatically add a new row when editing the last row + if index == items.len() - 1 && !value.is_empty() { + let new_item = Item { + id: Uuid::new_v4().to_string(), + name: String::new(), + description: String::new(), + // reviews: vec![], + wikidata_id: None, + custom_properties: HashMap::new(), + }; + items.push(new_item.clone()); - // Save the new item to the database - spawn_local({ - let current_url = Rc::clone(¤t_url); - async move { + // Save the new item to the database + spawn_local({ + let current_url = Rc::clone(¤t_url); + async move { save_item_to_db(new_item, selected_properties, current_url.to_string()).await; - } - }); - } - log!("Items updated: {:?}", items); - }); - }) - }; + } + }); + } + log!("Items updated: {:?}", items); + }); + })}; // List of properties to display as rows let properties = vec!["Name", "Description"]; @@ -818,7 +798,7 @@ pub fn ItemsList( } - }).collect::>()} + }).collect::>()} @@ -886,7 +866,7 @@ pub fn ItemsList( let label_for_display = suggestion.label.clone(); let description_for_click = suggestion.description.clone().unwrap_or_default(); let description_for_display = suggestion.description.clone().unwrap_or_default(); - let id = suggestion.id.clone(); + let id = suggestion.id.clone(); view! {
  • } - }).collect::>()} + }).collect::>()} } }).collect::>()} @@ -977,20 +957,10 @@ pub fn ItemsList( let property_label = property_labels.get().get(&normalized_property).cloned().unwrap_or_else(|| normalized_property.clone()); log!("Rendering property: {} -> {}", normalized_property, property_label); let property_clone_for_button = normalized_property.clone(); - let normalized_property_clone = normalized_property.clone(); view! { - {move || { - let property_label = property_labels.get().get(&normalized_property).cloned(); - let is_fetching = is_fetching_labels.get(); - - if is_fetching { - normalized_property.clone() - } else { - property_label.unwrap_or_else(|| normalized_property.clone()) - } - }} + { property_label } - let normalized_property = property.replace("http://www.wikidata.org/prop/", ""); - let normalized_property_clone = normalized_property.clone(); {move || { let update_item_cell = Arc::clone(&update_item_inner); - let property_clone_for_cells = normalized_property_clone.clone(); + let property_clone_for_cells = normalized_property.clone(); items.get().iter().enumerate().map(move |(index, item)| { let update_item_cell = Arc::clone(&update_item_cell); let property_clone_for_closure = property_clone_for_cells.clone(); @@ -1041,17 +1009,21 @@ pub fn ItemsList(
    ().unwrap(); - let property = input_element.value(); - if !property.is_empty() { - // Extract the coded name from the selected value - let coded_name = property.split(" - ").next().unwrap_or(&property).to_string(); - - // Add the property using the coded name - add_property(coded_name); - - // Clear the input field + let input_value = input_element.value(); + + // Extract property ID from "Label (P123)" format + let property_id = input_value + .split(" (") + .last() + .and_then(|s| s.strip_suffix(')')) + .unwrap_or(&input_value) + .to_string(); + + if !property_id.is_empty() { + // Add the property using the extracted ID + add_property(property_id); input_element.set_value(""); } } @@ -1059,10 +1031,11 @@ pub fn ItemsList( {move || { let property_labels = property_labels.get().clone(); - property_labels.into_iter().map(|(property, label)| { - let property_clone = property.clone(); + property_labels.into_iter().map(|(property_id, label)| { view! { - + } }).collect::>() }} -- 2.45.3 From 46e9b4e48e5b34bdd479fe012858f5b5b732b3a7 Mon Sep 17 00:00:00 2001 From: ryan Date: Tue, 1 Apr 2025 00:02:35 +0300 Subject: [PATCH 7/8] docs(readme): document properties data flow --- README.md | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/README.md b/README.md index e1d31b5..1abdd4b 100644 --- a/README.md +++ b/README.md @@ -59,6 +59,28 @@ flowchart LR items -->|item_id| item_properties properties -->|property_id| item_properties ``` + +### Properties data flow +```mermaid +sequenceDiagram + participant User + participant App as Application + participant Wikidata + + User->>App: Enters search + App->>Wikidata: fetch_wikidata_suggestions() + Wikidata-->>App: Return suggestions + App->>User: Show suggestions + + User->>App: Selects item + App->>Wikidata: fetch_item_properties() + Wikidata-->>App: Return properties (IDs + values) + + App->>Wikidata: fetch_property_labels() + Wikidata-->>App: Return labels + App->>App: Combine labels + properties + App->>User: Show labeled properties +``` ## **Docker Deployment** ### **Prerequisites** -- 2.45.3 From 303b713d5908136b973aa4567db16be3b73857a9 Mon Sep 17 00:00:00 2001 From: ryan Date: Tue, 1 Apr 2025 14:43:36 +0300 Subject: [PATCH 8/8] fix(properties): fetch prop values alongside prop ids and labels --- src/components/items_list.rs | 44 ++++++++++++++++++------------------ 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/src/components/items_list.rs b/src/components/items_list.rs index 2fb4d10..8a96598 100644 --- a/src/components/items_list.rs +++ b/src/components/items_list.rs @@ -398,12 +398,16 @@ pub fn ItemsList( let sparql_query = format!( r#" - SELECT ?propLabel ?value ?valueLabel WHERE {{ + SELECT ?prop ?propLabel ?value ?valueLabel WHERE {{ wd:{} ?prop ?statement. ?statement ?ps ?value. ?property wikibase:claim ?prop. ?property wikibase:statementProperty ?ps. - SERVICE wikibase:label {{ bd:serviceParam wikibase:language "en". }} + SERVICE wikibase:label {{ + bd:serviceParam wikibase:language "en". + ?prop rdfs:label ?propLabel. + ?value rdfs:label ?valueLabel. + }} }} "#, wikidata_id @@ -454,28 +458,24 @@ pub fn ItemsList( // Second pass: build results if let Some(bindings) = data["results"]["bindings"].as_array() { for binding in bindings { - if let (Some(prop_uri), Some(value_label)) = ( - binding["prop"]["value"].as_str(), - binding["valueLabel"]["value"].as_str() - ) { - // Extract property ID from URI (e.g., "http://www.wikidata.org/prop/P123") + let prop_label = binding["propLabel"]["value"].as_str().unwrap_or_default(); + let value = binding["valueLabel"]["value"] + .as_str() + .or_else(|| binding["value"]["value"].as_str()) + .unwrap_or_default(); + + if let Some(prop_uri) = binding["prop"]["value"].as_str() { let prop_id = prop_uri.split('/').last().unwrap_or_default().to_string(); + result.insert( + prop_id.clone(), + value.to_string() + ); - // Get the label from property_labels signal - if let Some(label) = property_labels.get().get(&prop_id).cloned() { - result.insert( - prop_id.clone(), - format!("{}: {}", - property_labels.get().get(&prop_id).cloned().unwrap_or(prop_id.clone()), - value_label - ) - ); - - // Ensure the label is stored in property_labels - set_property_labels.update(|labels| { - labels.entry(prop_id.clone()).or_insert_with(|| label.clone()); - }); - } + // Update labels if missing + set_property_labels.update(|labels| { + labels.entry(prop_id.clone()) + .or_insert(prop_label.to_string()); + }); } } } -- 2.45.3