fix(memory issues): made the followng to help debug memory issues.
- Reduce Closure Captures: Take snapshots of signal values before using them in closures. -Simplify Event Handlers: Break down complex event handlers into smaller, more manageable pieces. -Improve Error Handling: Add proper error handling to prevent crashes when unexpected conditions occur. -Optimize Data Flow: Ensure data flows in a more predictable way through the application. -Isolate Side Effects: Move side effects like API calls into separate tasks to avoid blocking the main thread. (in progress)
This commit is contained in:
parent
26724d9c45
commit
1fd4131298
1 changed files with 230 additions and 186 deletions
|
@ -597,59 +597,65 @@ pub fn ItemsList(
|
||||||
let set_property_labels = set_property_labels.clone();
|
let set_property_labels = set_property_labels.clone();
|
||||||
let property_cache = property_cache.clone();
|
let property_cache = property_cache.clone();
|
||||||
let set_property_cache = set_property_cache.clone();
|
let set_property_cache = set_property_cache.clone();
|
||||||
|
let set_custom_properties = set_custom_properties.clone();
|
||||||
|
let set_selected_properties = set_selected_properties.clone();
|
||||||
|
|
||||||
Arc::new(move |property: String| {
|
Arc::new(move |property: String| {
|
||||||
// Normalize the property ID
|
// Normalize the property ID
|
||||||
let normalized_property = property.replace("http://www.wikidata.org/prop/", "");
|
let normalized_property = property.replace("http://www.wikidata.org/prop/", "");
|
||||||
let normalized_property_clone = normalized_property.clone();
|
|
||||||
|
// Early return if property is empty
|
||||||
// Check if property is empty
|
if normalized_property.is_empty() {
|
||||||
if normalized_property.is_empty() {
|
log!("Attempted to add empty property, ignoring");
|
||||||
log!("Attempted to add empty property, ignoring");
|
return;
|
||||||
return;
|
}
|
||||||
}
|
|
||||||
|
// Create local copies of all signals to avoid capturing them in closures
|
||||||
// Check if label already exists
|
let property_labels_snapshot = property_labels.get();
|
||||||
if !property_labels.get().contains_key(&normalized_property) {
|
let selected_properties_snapshot = selected_properties.get();
|
||||||
spawn_local({
|
let custom_properties_snapshot = custom_properties.get();
|
||||||
let normalized_property = normalized_property.clone();
|
|
||||||
let set_property_labels = set_property_labels.clone();
|
// Check if label already exists
|
||||||
async move {
|
if !property_labels_snapshot.contains_key(&normalized_property) {
|
||||||
// Add a placeholder label while fetching
|
let normalized_property_clone = normalized_property.clone();
|
||||||
set_property_labels.update(|map| {
|
let set_property_labels_clone = set_property_labels.clone();
|
||||||
map.insert(normalized_property.clone(), normalized_property.clone());
|
|
||||||
});
|
// Add a placeholder label immediately
|
||||||
|
set_property_labels.update(|map| {
|
||||||
match fetch_property_labels(vec![normalized_property.clone()]).await {
|
map.insert(normalized_property_clone.clone(), normalized_property_clone.clone());
|
||||||
|
});
|
||||||
|
|
||||||
|
// Fetch the actual label in a separate task
|
||||||
|
spawn_local(async move {
|
||||||
|
match fetch_property_labels(vec![normalized_property_clone.clone()]).await {
|
||||||
Ok(labels) => {
|
Ok(labels) => {
|
||||||
set_property_labels.update(|map| {
|
set_property_labels_clone.update(|map| {
|
||||||
map.extend(labels);
|
map.extend(labels);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
log!("Error fetching property labels: {:?}", e);
|
log!("Error fetching property labels: {:?}", e);
|
||||||
// Keep the placeholder label
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
});
|
}
|
||||||
}
|
|
||||||
|
// Check if property is already selected
|
||||||
// Check if property is already selected
|
if !selected_properties_snapshot.contains_key(&normalized_property) {
|
||||||
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| {
|
||||||
set_selected_properties.update(|selected| {
|
selected.insert(normalized_property.clone(), true);
|
||||||
selected.insert(normalized_property.clone(), true);
|
});
|
||||||
});
|
|
||||||
|
// Save the selected property to the database
|
||||||
// Save the selected property to the database
|
let current_url_clone = Rc::clone(¤t_url);
|
||||||
spawn_local({
|
let normalized_property_clone = normalized_property.clone();
|
||||||
let current_url = Rc::clone(¤t_url);
|
|
||||||
let normalized_property = normalized_property_clone.clone();
|
spawn_local(async move {
|
||||||
async move {
|
|
||||||
let response = gloo_net::http::Request::post(
|
let response = gloo_net::http::Request::post(
|
||||||
&format!("/api/urls/{}/properties", encode(¤t_url))
|
&format!("/api/urls/{}/properties", encode(¤t_url_clone))
|
||||||
)
|
)
|
||||||
.json(&normalized_property)
|
.json(&normalized_property_clone)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.send()
|
.send()
|
||||||
.await;
|
.await;
|
||||||
|
@ -666,85 +672,84 @@ pub fn ItemsList(
|
||||||
log!("Error saving property: {:?}", 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
|
|
||||||
|
// Update custom properties if not already present
|
||||||
|
if !custom_properties_snapshot.contains(&normalized_property) {
|
||||||
|
set_custom_properties.update(|props| {
|
||||||
|
props.push(normalized_property.clone());
|
||||||
|
});
|
||||||
|
|
||||||
|
// Update items with the new property
|
||||||
set_items.update(|items| {
|
set_items.update(|items| {
|
||||||
for item in items {
|
for item in items {
|
||||||
// Safely insert the property if it doesn't exist
|
// Only add if it doesn't exist
|
||||||
if !item.custom_properties.contains_key(&normalized_property) {
|
if !item.custom_properties.contains_key(&normalized_property) {
|
||||||
item.custom_properties.insert(normalized_property.clone(), "".to_string());
|
item.custom_properties.insert(normalized_property.clone(), "".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
|
// Save each item to the database
|
||||||
let property_label = property_labels.get().get(&normalized_property).cloned().unwrap_or_else(|| normalized_property.clone());
|
let items_snapshot = items.get();
|
||||||
|
for item in items_snapshot {
|
||||||
|
let item_clone = item.clone();
|
||||||
|
let current_url_clone = Rc::clone(¤t_url);
|
||||||
|
let selected_properties_clone = selected_properties;
|
||||||
|
|
||||||
|
spawn_local(async move {
|
||||||
|
save_item_to_db(item_clone, selected_properties_clone, current_url_clone.to_string()).await;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Log the addition
|
||||||
|
let property_label = property_labels_snapshot
|
||||||
|
.get(&normalized_property)
|
||||||
|
.cloned()
|
||||||
|
.unwrap_or_else(|| normalized_property.clone());
|
||||||
|
|
||||||
log!("Added property with label: {}", property_label);
|
log!("Added property with label: {}", property_label);
|
||||||
|
|
||||||
}
|
}
|
||||||
});
|
|
||||||
// Fetch the relevant value for each item and populate the corresponding cells
|
// Fetch Wikidata properties for items with IDs
|
||||||
set_items.update(|items| {
|
let items_snapshot = items.get();
|
||||||
for item in items {
|
for item in items_snapshot {
|
||||||
// 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 {
|
if let Some(wikidata_id) = &item.wikidata_id {
|
||||||
let wikidata_id = wikidata_id.clone();
|
let wikidata_id_clone = wikidata_id.clone();
|
||||||
let set_items = set_items.clone();
|
let normalized_property_clone = normalized_property.clone();
|
||||||
let set_fetched_properties = set_fetched_properties.clone();
|
let set_items_clone = set_items.clone();
|
||||||
let property_clone = normalized_property.clone();
|
let set_property_labels_clone = set_property_labels.clone();
|
||||||
|
let property_cache_clone = property_cache.clone();
|
||||||
|
let set_property_cache_clone = set_property_cache.clone();
|
||||||
|
let property_labels_clone = property_labels.clone();
|
||||||
|
|
||||||
spawn_local(async move {
|
spawn_local(async move {
|
||||||
let properties = fetch_item_properties(
|
let properties = fetch_item_properties(
|
||||||
&wikidata_id,
|
&wikidata_id_clone,
|
||||||
set_property_labels.clone(),
|
set_property_labels_clone,
|
||||||
property_cache.clone(),
|
property_cache_clone,
|
||||||
set_property_cache.clone(),
|
set_property_cache_clone,
|
||||||
property_labels.clone()
|
property_labels_clone
|
||||||
).await;
|
).await;
|
||||||
|
|
||||||
// Update the specific property for this item
|
if let Some(value) = properties.get(&normalized_property_clone) {
|
||||||
if let Some(value) = properties.get(&property_clone) {
|
set_items_clone.update(|items| {
|
||||||
set_items.update(|items| {
|
for item in items {
|
||||||
if let Some(item) = items.iter_mut()
|
if item.wikidata_id.as_ref() == Some(&wikidata_id_clone) {
|
||||||
.find(|i| i.wikidata_id.as_ref() == Some(&wikidata_id))
|
item.custom_properties.insert(
|
||||||
{
|
normalized_property_clone.clone(),
|
||||||
item.custom_properties.insert(
|
value.clone()
|
||||||
property_clone.clone(),
|
);
|
||||||
value.clone()
|
}
|
||||||
);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
})};
|
};
|
||||||
|
|
||||||
// Update item fields
|
// Update item fields
|
||||||
let update_item = {
|
let update_item = {
|
||||||
|
@ -835,19 +840,22 @@ pub fn ItemsList(
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{properties.into_iter().map(|property| {
|
{properties.into_iter().map(|property| {
|
||||||
let update_item_cloned = Arc::clone(&update_item);
|
let update_item_cloned = Arc::clone(&update_item);
|
||||||
let current_url_for_closure = Rc::clone(¤t_url);
|
let current_url_for_closure = Rc::clone(¤t_url);
|
||||||
log!("Rendering property: {}", property);
|
log!("Rendering property: {}", property);
|
||||||
view! {
|
view! {
|
||||||
<tr>
|
<tr>
|
||||||
<td>{ property }</td>
|
<td>{ property }</td>
|
||||||
{{
|
{{
|
||||||
|
// Clone current_url before the nested closure
|
||||||
|
let current_url_for_inner = Rc::clone(¤t_url_for_closure);
|
||||||
|
|
||||||
move || {
|
move || {
|
||||||
let items = items.get();
|
let items = items.get();
|
||||||
items.iter().enumerate().map(|(index, item)| {
|
items.iter().enumerate().map(|(index, item)| {
|
||||||
let update_item_clone = Arc::clone(&update_item_cloned);
|
let update_item_clone = Arc::clone(&update_item_cloned);
|
||||||
let current_url_clone = Rc::clone(¤t_url_for_closure);
|
let current_url_clone = Rc::clone(¤t_url_for_inner);
|
||||||
|
|
||||||
view! {
|
view! {
|
||||||
<td>
|
<td>
|
||||||
|
@ -856,54 +864,69 @@ pub fn ItemsList(
|
||||||
<div class="typeahead-container">
|
<div class="typeahead-container">
|
||||||
<TypeaheadInput
|
<TypeaheadInput
|
||||||
value=item.name.clone()
|
value=item.name.clone()
|
||||||
fetch_suggestions=Callback::new(move |query: String| -> Vec<WikidataSuggestion> {
|
fetch_suggestions=Callback::new({
|
||||||
let key = format!("name-{}", index);
|
let key = format!("name-{}", index);
|
||||||
fetch_wikidata_suggestions(key.clone(), query.clone());
|
let wikidata_suggestions_clone = wikidata_suggestions.clone();
|
||||||
|
|
||||||
// Add a small delay to ensure state is updated
|
move |query: String| -> Vec<WikidataSuggestion> {
|
||||||
let suggestions = wikidata_suggestions.get();
|
// Fetch suggestions in a separate function to avoid capturing too much
|
||||||
suggestions.get(&key).cloned().unwrap_or_default()
|
fetch_wikidata_suggestions(key.clone(), query.clone());
|
||||||
|
|
||||||
|
// Return current suggestions from the signal
|
||||||
|
let suggestions = wikidata_suggestions_clone.get();
|
||||||
|
suggestions.get(&key).cloned().unwrap_or_default()
|
||||||
|
}
|
||||||
})
|
})
|
||||||
on_select=Callback::new(move |suggestion: WikidataSuggestion| {
|
on_select=Callback::new({
|
||||||
set_items.update(|items| {
|
let set_items_clone = set_items.clone();
|
||||||
if let Some(item) = items.get_mut(index) {
|
let set_property_labels_clone = set_property_labels.clone();
|
||||||
item.name = suggestion.display.label.value.clone();
|
let property_cache_clone = property_cache.clone();
|
||||||
item.description = suggestion.display.description.value.clone();
|
let set_property_cache_clone = set_property_cache.clone();
|
||||||
item.wikidata_id = Some(suggestion.id.clone());
|
let property_labels_clone = property_labels.clone();
|
||||||
|
|
||||||
// Automatically fetch properties when Wikidata ID is set
|
move |suggestion: WikidataSuggestion| {
|
||||||
if let Some(wikidata_id) = &item.wikidata_id {
|
let wikidata_id = suggestion.id.clone();
|
||||||
spawn_local({
|
|
||||||
let set_property_labels = set_property_labels.clone();
|
set_items_clone.update(|items| {
|
||||||
let property_cache = property_cache.clone();
|
if let Some(item) = items.get_mut(index) {
|
||||||
let set_property_cache = set_property_cache.clone();
|
item.name = suggestion.display.label.value.clone();
|
||||||
let property_labels = property_labels.clone();
|
item.description = suggestion.display.description.value.clone();
|
||||||
let wikidata_id = wikidata_id.clone();
|
item.wikidata_id = Some(wikidata_id.clone());
|
||||||
|
|
||||||
async move {
|
|
||||||
fetch_item_properties(
|
|
||||||
&wikidata_id,
|
|
||||||
set_property_labels,
|
|
||||||
property_cache,
|
|
||||||
set_property_cache,
|
|
||||||
property_labels
|
|
||||||
).await;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
});
|
|
||||||
|
// Fetch properties in a separate task
|
||||||
|
let set_property_labels_for_task = set_property_labels_clone.clone();
|
||||||
|
let property_cache_for_task = property_cache_clone.clone();
|
||||||
|
let set_property_cache_for_task = set_property_cache_clone.clone();
|
||||||
|
let property_labels_for_task = property_labels_clone.clone();
|
||||||
|
let wikidata_id_for_task = wikidata_id.clone();
|
||||||
|
|
||||||
|
spawn_local(async move {
|
||||||
|
fetch_item_properties(
|
||||||
|
&wikidata_id_for_task,
|
||||||
|
set_property_labels_for_task,
|
||||||
|
property_cache_for_task,
|
||||||
|
set_property_cache_for_task,
|
||||||
|
property_labels_for_task
|
||||||
|
).await;
|
||||||
|
});
|
||||||
|
}
|
||||||
})
|
})
|
||||||
is_last_row={index == items.len() - 1}
|
is_last_row={index == items.len() - 1}
|
||||||
on_input=Callback::new({
|
on_input=Callback::new({
|
||||||
// Clone items.len() before moving into the closure
|
// Clone items.len() before moving into the closure
|
||||||
let items_len = items.len();
|
let items_len = items.len();
|
||||||
let current_url_for_spawn = Rc::clone(¤t_url_clone);
|
let set_items_clone = set_items.clone();
|
||||||
|
let current_url_clone = Rc::clone(¤t_url_clone);
|
||||||
|
let selected_properties_clone = selected_properties.clone();
|
||||||
|
|
||||||
move |value: String| {
|
move |value: String| {
|
||||||
if index == items_len - 1 && !value.is_empty() {
|
if index == items_len - 1 && !value.is_empty() {
|
||||||
// Use the cloned current_url
|
let current_url_for_new_item = Rc::clone(¤t_url_clone);
|
||||||
let current_url_for_new_item = Rc::clone(¤t_url_for_spawn);
|
let selected_properties_for_new_item = selected_properties_clone.clone();
|
||||||
set_items.update(|items| {
|
|
||||||
|
set_items_clone.update(|items| {
|
||||||
let new_item = Item {
|
let new_item = Item {
|
||||||
id: Uuid::new_v4().to_string(),
|
id: Uuid::new_v4().to_string(),
|
||||||
name: String::new(),
|
name: String::new(),
|
||||||
|
@ -913,13 +936,17 @@ pub fn ItemsList(
|
||||||
};
|
};
|
||||||
items.push(new_item.clone());
|
items.push(new_item.clone());
|
||||||
|
|
||||||
// Save the new item to the database
|
// Save the new item to the database in a separate task
|
||||||
spawn_local({
|
let new_item_clone = new_item.clone();
|
||||||
let current_url = Rc::clone(¤t_url_for_new_item);
|
let current_url_for_task = Rc::clone(¤t_url_for_new_item);
|
||||||
let selected_properties = selected_properties;
|
let selected_properties_for_task = selected_properties_for_new_item;
|
||||||
async move {
|
|
||||||
save_item_to_db(new_item, selected_properties, current_url.to_string()).await;
|
spawn_local(async move {
|
||||||
}
|
save_item_to_db(
|
||||||
|
new_item_clone,
|
||||||
|
selected_properties_for_task,
|
||||||
|
current_url_for_task.to_string()
|
||||||
|
).await;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -952,12 +979,12 @@ pub fn ItemsList(
|
||||||
}}
|
}}
|
||||||
</td>
|
</td>
|
||||||
}
|
}
|
||||||
}).collect::<Vec<_>>()
|
}).collect::<Vec<_>>()
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
</tr>
|
</tr>
|
||||||
}
|
}
|
||||||
}).collect::<Vec<_>>()}
|
}).collect::<Vec<_>>()}
|
||||||
// Dynamically adding custom properties as columns
|
// Dynamically adding custom properties as columns
|
||||||
{{
|
{{
|
||||||
let update_item_outer = Arc::clone(&update_item);
|
let update_item_outer = Arc::clone(&update_item);
|
||||||
|
@ -1025,36 +1052,53 @@ pub fn ItemsList(
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
<div style="margin-bottom: 20px;">
|
<div style="margin-bottom: 20px;">
|
||||||
<input type="text" id="new-property" placeholder="Add New Property" list="properties" on:keydown=move |event| {
|
<input
|
||||||
if event.key() == "Enter" {
|
type="text"
|
||||||
let input_element = event.target().unwrap().dyn_into::<web_sys::HtmlInputElement>().unwrap();
|
id="new-property"
|
||||||
let input_value = input_element.value();
|
placeholder="Add New Property"
|
||||||
|
list="properties"
|
||||||
// Extract property ID from "Label (P123)" format
|
on:keydown=move |event| {
|
||||||
let property_id = input_value
|
if event.key() == "Enter" {
|
||||||
.split(" (")
|
// Safely get the input element
|
||||||
.last()
|
if let Some(target) = event.target() {
|
||||||
.and_then(|s| s.strip_suffix(')'))
|
if let Ok(input_element) = target.dyn_into::<web_sys::HtmlInputElement>() {
|
||||||
.unwrap_or(&input_value)
|
let input_value = input_element.value();
|
||||||
.to_string();
|
|
||||||
|
// Extract property ID from "Label (P123)" format
|
||||||
if !property_id.is_empty() {
|
let property_id = if input_value.contains(" (") && input_value.ends_with(')') {
|
||||||
// Add the property using the extracted ID
|
let parts: Vec<&str> = input_value.rsplitn(2, " (").collect();
|
||||||
add_property(property_id);
|
if parts.len() == 2 {
|
||||||
input_element.set_value("");
|
parts[0].trim_end_matches(')').to_string()
|
||||||
|
} else {
|
||||||
|
input_value.clone()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
input_value.clone()
|
||||||
|
};
|
||||||
|
|
||||||
|
if !property_id.is_empty() {
|
||||||
|
// Add the property using the extracted ID
|
||||||
|
add_property(property_id);
|
||||||
|
input_element.set_value("");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} />
|
/>
|
||||||
<datalist id="properties">
|
<datalist id="properties">
|
||||||
{move || {
|
{move || {
|
||||||
let property_labels = property_labels.get().clone();
|
let property_labels_snapshot = property_labels.get();
|
||||||
property_labels.into_iter().map(|(property_id, label)| {
|
property_labels_snapshot.iter()
|
||||||
view! {
|
.map(|(property_id, label)| {
|
||||||
<option value={format!("{} ({})", label, property_id)}>
|
let option_value = format!("{} ({})", label, property_id);
|
||||||
{ format!("{} ({})", label, property_id) }
|
view! {
|
||||||
</option>
|
<option value={option_value.clone()}>
|
||||||
}
|
{option_value}
|
||||||
}).collect::<Vec<_>>()
|
</option>
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>()
|
||||||
}}
|
}}
|
||||||
</datalist>
|
</datalist>
|
||||||
</div>
|
</div>
|
||||||
|
|
Loading…
Add table
Reference in a new issue