feat(labels): add state to track when labels are being fetched(in progress)

This commit is contained in:
ryan 2025-03-31 17:36:46 +03:00
parent ef7245b716
commit 40bb35d6a8

View file

@ -143,6 +143,10 @@ pub fn ItemsList(
// State to manage property cache
let (property_cache, set_property_cache) = create_signal(HashMap::<String, HashMap<String, String>>::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::<serde_json::Value>().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<String> = 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<String>) -> HashMap<String, String> {
async fn fetch_property_labels(
property_ids: Vec<String>,
set_property_labels: WriteSignal<HashMap<String, String>>,
set_is_fetching_labels: impl Fn(bool) + 'static,
) -> HashMap<String, String> {
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<String> = 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(&current_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(&current_url);
let normalized_property = normalized_property_clone.clone();
async move {
let response = gloo_net::http::Request::post(
&format!("/api/urls/{}/properties", encode(&current_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(&current_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(&current_url);
let normalized_property = normalized_property_clone.clone();
async move {
let response = gloo_net::http::Request::post(
&format!("/api/urls/{}/properties", encode(&current_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(&current_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(&current_url);
Arc::new(move |index: usize, field: &str, value: String| {
let set_items = set_items.clone();
let current_url = Rc::clone(&current_url);
let set_items = set_items.clone();
let current_url = Rc::clone(&current_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(&current_url);
async move {
// Save the updated item to the database
let item_clone = item.clone();
spawn_local({
let current_url = Rc::clone(&current_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(&current_url);
async move {
// Save the new item to the database
spawn_local({
let current_url = Rc::clone(&current_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(
<button on:click=move |_| remove_item(index)>{ "Delete" }</button>
</th>
}
}).collect::<Vec<_>>()}
}).collect::<Vec<_>>()}
</tr>
</thead>
<tbody>
@ -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! {
<li class="editable-cell-suggestions-li" on:click=move |_| {
// Update item with basic suggestion details
@ -859,7 +899,7 @@ pub fn ItemsList(
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 Wikidata ID {}: {:?}", wikidata_id, properties);
// Populate the custom properties for the new item
set_items.update(|items| {
if let Some(item) = items.iter_mut().find(|item| item.wikidata_id.as_ref() == Some(&wikidata_id)) {
@ -914,7 +954,7 @@ pub fn ItemsList(
}}
</td>
}
}).collect::<Vec<_>>()}
}).collect::<Vec<_>>()}
</tr>
}
}).collect::<Vec<_>>()}
@ -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! {
<tr>
<td>
{ 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())
}
}}
<button class="delete-property" on:click=move |_| {
log!("Deleting property: {}", property_clone_for_button);
remove_property_clone(property_clone_for_button.clone());
@ -953,9 +1003,11 @@ pub fn ItemsList(
});
}>{ "Delete" }</button>
</td>
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();