use crate::components::editable_cell::EditableCell; use crate::components::editable_cell::InputType; use leptos::*; use serde::Deserialize; use uuid::Uuid; use leptos::logging::log; use crate::models::item::Item; use std::collections::HashMap; use std::sync::Arc; use wasm_bindgen::JsCast; use serde_json::json; #[derive(Deserialize, Clone, Debug)] struct WikidataSuggestion { id: String, label: String, description: Option, } #[component] pub fn ItemsList( items: ReadSignal>, set_items: WriteSignal>, ) -> impl IntoView { // State to track the currently focused cell let (focused_cell, set_focused_cell) = create_signal(None::); // State to manage dynamic property names let (custom_properties, set_custom_properties) = create_signal(Vec::::new()); // state to manage suggestions visibility let (show_suggestions, set_show_suggestions) = create_signal(HashMap::::new()); // cache to store fetched properties let (fetched_properties, set_fetched_properties) = create_signal(HashMap::::new()); //signal to store the fetched property labels let (property_labels, set_property_labels) = create_signal(HashMap::::new()); // Ensure there's an initial empty row if items.get().is_empty() { set_items.set(vec![Item { id: Uuid::new_v4().to_string(), name: String::new(), description: String::new(), // reviews: vec![], wikidata_id: None, custom_properties: HashMap::new(), }]); } let (wikidata_suggestions, set_wikidata_suggestions) = create_signal(HashMap::>::new()); // Fetch Wikidata suggestions let fetch_wikidata_suggestions = move |key:String, query: String| { log!("Fetching suggestions for key: {}, query: {}", key, query); spawn_local(async move { if query.is_empty() { set_wikidata_suggestions.update(|suggestions| { suggestions.remove(&key); }); 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 { log!("Fetching suggestions for key: {}, query: {}", key, query); set_wikidata_suggestions.update(|suggestions| { log!("Updated suggestions: {:?}", suggestions); suggestions.insert(key, data.search); }); } } Err(_) => log!("Failed to fetch Wikidata suggestions"), } }); }; //function to fetch properties async fn fetch_item_properties(wikidata_id: &str, set_fetched_properties: WriteSignal>, set_property_labels: WriteSignal>) -> HashMap { let query = r#" query FetchReferencedItems($wikidata_id: String!) { entity(id: $wikidata_id) { claims { property { id label } value { ... on Item { id label } } } } } "#; let variables = json!({ "wikidata_id": wikidata_id, }); let request_body = json!({ "query": query, "variables": variables, }); log!("Sending GraphQL request for Wikidata ID: {}", wikidata_id); log!("Request body: {}", request_body); let graphql_endpoint = "https://cors-anywhere.herokuapp.com/https://query.wikidata.org/graphql"; let request_builder = match gloo_net::http::Request::post(graphql_endpoint) .header("Content-Type", "application/json") .header("Accept", "application/json") .header("Origin", "http://localhost:3000") .json(&request_body) { Ok(builder) => builder, Err(err) => { log!("Failed to build request: {:?}", err); return HashMap::new(); } }; match request_builder.send().await { Ok(response) => { log!("Received response for Wikidata ID: {}", wikidata_id); let raw_response_text = response.text().await.unwrap_or_default(); // Log the raw response text log!("Raw response text: {}", raw_response_text); if let Ok(data) = serde_json::from_str::(&raw_response_text) { log!("Parsed response data: {}", data); if let Some(entity) = data["data"]["entity"].as_object() { log!("Entity data found for Wikidata ID: {}", wikidata_id); let mut result = HashMap::new(); if let Some(claims) = entity["claims"].as_array() { log!("Claims found: {}", claims.len()); for claim in claims { if let Some(property) = claim["property"].as_object() { if let Some(property_id) = property["id"].as_str() { log!("Property ID found: {}", property_id); if let Some(value) = claim["value"].as_object() { if let Some(label) = value["label"].as_str() { log!("Label found for property {}: {}", property_id, label); result.insert(property_id.to_string(), label.to_string()); } else { log!("No label found for property {}", property_id); } } else { log!("No value found for property {}", property_id); } } else { log!("No property ID found in claim"); } } else { log!("No property object found in claim"); } } } else { log!("No claims found in entity"); } // Update fetched properties and labels set_fetched_properties.update(|properties| { for (key, val) in result.clone() { properties.insert(key.clone(), val.clone()); } }); set_property_labels.update(|labels_map| { for (key, val) in result.clone() { labels_map.insert(key.clone(), val.clone()); } }); log!("Fetched properties for Wikidata ID {}: {:?}", wikidata_id, result); return result; } else { log!("No entity found in response data"); } } else { log!("Failed to parse response JSON"); } } Err(err) => { log!("Error fetching item properties: {:?}", err); } } HashMap::new() } async fn fetch_property_labels(property_ids: Vec) -> HashMap { let mut property_labels = HashMap::new(); // Construct the API URL to fetch labels for multiple properties let url = format!( "https://www.wikidata.org/w/api.php?action=wbgetentities&ids={}&props=labels&format=json&languages=en&origin=*", property_ids.join("|") ); match gloo_net::http::Request::get(&url).send().await { Ok(response) => { if let Ok(data) = response.json::().await { if let Some(entities) = data["entities"].as_object() { for (property_id, entity) in entities { if let Some(label) = entity["labels"]["en"]["value"].as_str() { property_labels.insert(property_id.clone(), label.to_string()); } } } } } Err(err) => log!("Error fetching property labels: {:?}", err), } property_labels } // Add a new custom property let add_property = move |property: String| { set_custom_properties.update(|props| { if !props.contains(&property) && !property.is_empty() { props.push(property.clone()); // Ensure the grid updates reactively set_items.update(|items| { for item in items { item.custom_properties.entry(property.clone()).or_insert_with(|| "".to_string()); } }); // Fetch the property label let property_id = property.clone(); spawn_local(async move { let labels = fetch_property_labels(vec![property_id.clone()]).await; set_property_labels.update(|labels_map| { if let Some(label) = labels.get(&property_id) { labels_map.insert(property_id, label.clone()); } }); }); } }); // 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_fetched_properties, set_property_labels).await; log!("Fetched properties for Wikidata ID {}: {:?}", wikidata_id, properties); 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 = move |index: usize, field: &str, value: String| { set_items.update(|items| { 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(); let set_fetched_properties = set_fetched_properties.clone(); let set_property_labels = set_property_labels.clone(); spawn_local(async move { let properties = fetch_item_properties(&wikidata_id, set_fetched_properties, set_property_labels).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()); } } } // Automatically add a new row when editing the last row if index == items.len() - 1 && !value.is_empty() { items.push(Item { id: Uuid::new_v4().to_string(), name: String::new(), description: String::new(), // reviews: vec![], wikidata_id: None, custom_properties: HashMap::new(), }); } log!("Items updated: {:?}", items); }); }; // Remove an item let remove_item = move |index: usize| { set_items.update(|items| { items.remove(index); }); }; // List of properties to display as rows let properties = vec!["Name", "Description", "Actions"]; view! {

{ "Items List" }

{move || items.get().iter().enumerate().map(|(index, _)| { view! { } }).collect::>()} {properties.into_iter().map(|property| { log!("Rendering property: {}", property); view! { {move || items.get().iter().enumerate().map(|(index, item)| { view! { } }).collect::>()} } }).collect::>()} // Dynamically adding custom properties as columns {move || { let custom_props = custom_properties.get().clone(); custom_props.into_iter().map(move |property| { let property_clone = property.clone(); let property_label = property_labels.get().get(&property_clone).cloned().unwrap_or_else(|| property_clone.clone()); view! { {move || { let property_clone = property_clone.clone(); // Clone `property_clone` again for the inner closure items.get().iter().enumerate().map(move |(index, item)| { let property_clone_for_closure = property_clone.clone(); view! { } }).collect::>() }} } }).collect::>() }}
{ "Property" }{ format!("Item {}", index + 1) }
{ property } {match property { "Name" => view! {
{move || { if *show_suggestions.get().get(&format!("name-{}", index)).unwrap_or(&false) { log!("Rendering suggestions list"); view! {
    {move || { let suggestions = wikidata_suggestions.get() .get(&format!("name-{}", index)) .cloned() .unwrap_or_default(); log!("Suggestions for cell {}: {:?}", index, suggestions); suggestions.into_iter().map(|suggestion| { let label_for_click = suggestion.label.clone(); 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(); view! {
  • { format!("{} - {}", label_for_display, description_for_display) }
  • } }).collect::>() }}
} } else { log!("Suggestions list hidden"); view! {
    } } }}
    }.into_view(), "Description" => view! { }.into_view(), "Actions" => view! { }.into_view(), _ => view! { { "" } }.into_view(), }}
    { property_label }
    {move || { let properties = fetched_properties.get().clone(); let property_labels = property_labels.get().clone(); properties.into_iter().map(|(key, _)| { let key_clone = key.clone(); let label = property_labels.get(&key_clone).cloned().unwrap_or_else(|| key_clone.clone()); view! { } }).collect::>() }}
    } } #[derive(Deserialize, Clone, Debug)] struct WikidataResponse { search: Vec, }