Fix (ItemsList)(inprogress): Improve Popup Positioning and Debounced Fetching

- Introduced `ActiveCell` struct to manage active cell state and position more robustly.
- Added debounce mechanism using `futures_timer::Delay` for optimized Wikidata suggestion fetching.
- Enhanced popup rendering logic to include z-index and improved styling for better positioning.
- Implemented validation for bounding box dimensions during active cell positioning.
- Adjusted event handling for focus and blur with asynchronous state updates.
- General refactoring for better readability and maintainability.

(Note: Popup functionality remains incomplete and will be addressed in future iterations.)
This commit is contained in:
ryan 2024-12-30 14:18:33 +03:00
parent 8cd277d66a
commit c4a45d9185

View file

@ -8,6 +8,8 @@ use crate::models::item::Item;
use std::sync::{Arc, Mutex}; use std::sync::{Arc, Mutex};
use wasm_bindgen::JsCast; use wasm_bindgen::JsCast;
use web_sys::{FocusEvent, HtmlElement, Element, Node}; use web_sys::{FocusEvent, HtmlElement, Element, Node};
use futures_timer::Delay;
use std::time::Duration;
#[derive(Deserialize, Clone, Debug)] #[derive(Deserialize, Clone, Debug)]
struct WikidataSuggestion { struct WikidataSuggestion {
@ -15,6 +17,11 @@ struct WikidataSuggestion {
label: String, label: String,
description: Option<String>, description: Option<String>,
} }
#[derive(Clone)]
struct ActiveCell {
row_index: usize,
position: (f64, f64),
}
#[component] #[component]
pub fn ItemsList( pub fn ItemsList(
@ -32,14 +39,16 @@ pub fn ItemsList(
wikidata_id: None, wikidata_id: None,
}]); }]);
let (active_cell, set_active_cell) = create_signal(None::<ActiveCell>);
let (active_cell_position, set_active_cell_position) = create_signal(None::<(f64, f64)>); let (active_cell_position, set_active_cell_position) = create_signal(None::<(f64, f64)>);
let (active_row_index, set_active_row_index) = create_signal(None::<usize>); let (active_row_index, set_active_row_index) = create_signal(None::<usize>);
let (wikidata_suggestions, set_wikidata_suggestions) = let (wikidata_suggestions, set_wikidata_suggestions) = create_signal(Vec::<WikidataSuggestion>::new());
create_signal(Vec::<WikidataSuggestion>::new()); let debounce_duration = Duration::from_millis(300);
// Fetch Wikidata suggestions // Fetch Wikidata suggestions
let fetch_wikidata_suggestions = move |query: String| { let fetch_wikidata_suggestions = move |query: String| {
spawn_local(async move { spawn_local(async move {
Delay::new(debounce_duration).await;
if query.is_empty() { if query.is_empty() {
set_wikidata_suggestions.set(Vec::new()); set_wikidata_suggestions.set(Vec::new());
return; return;
@ -56,7 +65,10 @@ pub fn ItemsList(
set_wikidata_suggestions.set(data.search); set_wikidata_suggestions.set(data.search);
} }
} }
Err(_) => log!("Failed to fetch Wikidata suggestions"), Err(err) => {
log!("Failed to fetch Wikidata suggestions: {:?}", err);
set_wikidata_suggestions.set(Vec::new());
}
} }
}); });
}; };
@ -70,10 +82,15 @@ pub fn ItemsList(
item.name = value.clone(); item.name = value.clone();
fetch_wikidata_suggestions(value.clone()); fetch_wikidata_suggestions(value.clone());
set_active_row_index.set(Some(index)); set_active_row_index.set(Some(index));
// Set active cell position // Set active cell position with validation
if let Some(element) = document().get_element_by_id(&format!("name-{}", index)) { if let Some(element) = document().get_element_by_id(&format!("name-{}", index)) {
let rect = element.get_bounding_client_rect(); let rect = element.get_bounding_client_rect();
log!("Bounding rect: {:?}", rect); // Log rect details
if rect.width() > 0.0 && rect.height() > 0.0 {
set_active_cell_position.set(Some((rect.left(), rect.bottom()))); set_active_cell_position.set(Some((rect.left(), rect.bottom())));
} else {
log!("Element bounding box is not valid for popup positioning.");
}
} }
} }
"description" => { "description" => {
@ -129,10 +146,15 @@ pub fn ItemsList(
class="suggestions-popup" class="suggestions-popup"
style=move || { style=move || {
let suggestions = wikidata_suggestions.get(); let suggestions = wikidata_suggestions.get();
let position = active_cell_position.get(); if !suggestions.is_empty() {
if !suggestions.is_empty() && position.is_some() { if let Some((x, y)) = active_cell_position.get() {
let (x, y) = position.unwrap(); format!(
format!("position: absolute; left: {}px; top: {}px; display: block;", x, y) "position: absolute; left: {}px; top: {}px; display: block; z-index: 1000;",
x, y
)
} else {
"display: none;".to_string()
}
} else { } else {
"display: none;".to_string() "display: none;".to_string()
} }
@ -190,26 +212,20 @@ pub fn ItemsList(
<td> <td>
<div <div
on:focus=move |event: FocusEvent| { on:focus=move |event: FocusEvent| {
if let Some(target) = event.target() { if let Some(element) = event.target().and_then(|t| t.dyn_into::<HtmlElement>().ok()) {
// Try casting the target to Node first, then to HtmlElement
if let Some(node) = target.dyn_ref::<Node>() {
if let Some(element_ref) = node.dyn_ref::<HtmlElement>(){
if let Some(element) = element_ref.dyn_ref::<Element>() {
let rect = element.get_bounding_client_rect(); let rect = element.get_bounding_client_rect();
set_active_cell_position.set(Some((rect.left(), rect.top() + rect.height()))); set_active_cell.set(Some(ActiveCell {
set_active_row_index.set(Some(index)); row_index: index,
} else { position: (rect.left(), rect.top() + rect.height()),
log!("Failed to cast to Element"); }));
}
} else {
log!("Target is not a valid HTML element");
}
} else {
log!("Target is not a valid Node");
} }
} }
on:blur=move |_| {
spawn_local(async move {
Delay::new(Duration::from_millis(100)).await;
set_active_cell.set(None);
});
} }
on:blur=move |_| set_active_row_index.set(None)
> >
<EditableCell <EditableCell
value=item.name.clone() value=item.name.clone()