feat(item_list): Added active cell tracking with position updates for popup rendering.

This commit is contained in:
ryan 2024-12-27 03:01:03 +03:00
parent 9f28d30d48
commit 8cd277d66a
4 changed files with 115 additions and 39 deletions

1
Cargo.lock generated
View File

@ -724,6 +724,7 @@ dependencies = [
"futures", "futures",
"gloo-net 0.5.0", "gloo-net 0.5.0",
"http 1.2.0", "http 1.2.0",
"js-sys",
"leptos", "leptos",
"leptos_actix", "leptos_actix",
"leptos_meta", "leptos_meta",

View File

@ -18,7 +18,8 @@ leptos_router = { version = "0.6" }
wasm-bindgen = "=0.2.99" wasm-bindgen = "=0.2.99"
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
uuid = { version = "1.0", features = ["v4"] } uuid = { version = "1.0", features = ["v4"] }
web-sys = { version = "0.3", features = ["Event"] } web-sys = { version = "0.3", features = ["Event", "HtmlElement", "Window", "EventTarget", "Element", "DomRect"] }
js-sys = "0.3"
nostr-sdk = "0.37" nostr-sdk = "0.37"
tokio = "1" tokio = "1"
gloo-net = "0.5" gloo-net = "0.5"

View File

@ -68,4 +68,28 @@ th, td {
th { th {
background-color: #f2f2f2; background-color: #f2f2f2;
}
.suggestions-popup {
background-color: white;
border: 1px solid #ccc;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
max-height: 200px;
overflow-y: auto;
z-index: 1000;
}
.suggestions-popup ul {
list-style-type: none;
padding: 0;
margin: 0;
}
.suggestions-popup li {
padding: 8px 12px;
cursor: pointer;
}
.suggestions-popup li:hover {
background-color: #f0f0f0;
} }

View File

@ -6,6 +6,8 @@ use uuid::Uuid;
use leptos::logging::log; use leptos::logging::log;
use crate::models::item::Item; use crate::models::item::Item;
use std::sync::{Arc, Mutex}; use std::sync::{Arc, Mutex};
use wasm_bindgen::JsCast;
use web_sys::{FocusEvent, HtmlElement, Element, Node};
#[derive(Deserialize, Clone, Debug)] #[derive(Deserialize, Clone, Debug)]
struct WikidataSuggestion { struct WikidataSuggestion {
@ -30,6 +32,8 @@ pub fn ItemsList(
wikidata_id: None, wikidata_id: None,
}]); }]);
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 (wikidata_suggestions, set_wikidata_suggestions) = let (wikidata_suggestions, set_wikidata_suggestions) =
create_signal(Vec::<WikidataSuggestion>::new()); create_signal(Vec::<WikidataSuggestion>::new());
@ -65,13 +69,19 @@ pub fn ItemsList(
"name" => { "name" => {
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 cell position
if let Some(element) = document().get_element_by_id(&format!("name-{}", index)) {
let rect = element.get_bounding_client_rect();
set_active_cell_position.set(Some((rect.left(), rect.bottom())));
}
} }
"description" => { "description" => {
item.description = value.clone(); item.description = value.clone();
} }
_ => (), _ => (),
} }
} }
// Automatically add a new row when editing the last row // Automatically add a new row when editing the last row
if index == items.len() - 1 && !value.is_empty() { if index == items.len() - 1 && !value.is_empty() {
@ -112,6 +122,54 @@ pub fn ItemsList(
}); });
}; };
// Position and render the popup
let render_popup = move || {
view! {
<div
class="suggestions-popup"
style=move || {
let suggestions = wikidata_suggestions.get();
let position = active_cell_position.get();
if !suggestions.is_empty() && position.is_some() {
let (x, y) = position.unwrap();
format!("position: absolute; left: {}px; top: {}px; display: block;", x, y)
} else {
"display: none;".to_string()
}
}
>
<ul>
{move || wikidata_suggestions.get().iter().map(|suggestion| {
let label_for_click = suggestion.label.clone();
let description_for_click = suggestion.description.clone().unwrap_or_default();
let id = suggestion.id.clone();
let label_for_display = label_for_click.clone();
let description_for_display = description_for_click.clone();
view! {
<li on:click=move |_| {
if let Some(index) = active_row_index.get() {
set_items.update(|items| {
if let Some(item) = items.get_mut(index) {
item.name = label_for_click.clone();
item.description = description_for_click.clone();
item.wikidata_id = Some(id.clone());
item.tags.push(("wikidata_id".to_string(), id.clone()));
}
});
}
set_wikidata_suggestions.set(Vec::new());
set_active_cell_position.set(None);
}>
{format!("{} - {}", label_for_display, description_for_display)}
</li>
}
}).collect::<Vec<_>>()}
</ul>
</div>
}
};
view! { view! {
<div> <div>
<h1>{ "Items List" }</h1> <h1>{ "Items List" }</h1>
@ -130,44 +188,35 @@ pub fn ItemsList(
<tr> <tr>
// Editable Name Field with Wikidata Integration // Editable Name Field with Wikidata Integration
<td> <td>
<EditableCell <div
value=item.name.clone() on:focus=move |event: FocusEvent| {
on_input=move |value| update_item(index, "name", value) if let Some(target) = event.target() {
key=format!("name-{}", index) // Unique key per cell // Try casting the target to Node first, then to HtmlElement
/> if let Some(node) = target.dyn_ref::<Node>() {
<ul> if let Some(element_ref) = node.dyn_ref::<HtmlElement>(){
{move || { if let Some(element) = element_ref.dyn_ref::<Element>() {
let suggestions = wikidata_suggestions.get().to_vec(); let rect = element.get_bounding_client_rect();
suggestions.into_iter().map(|suggestion| { set_active_cell_position.set(Some((rect.left(), rect.top() + rect.height())));
let label_for_click = suggestion.label.clone(); set_active_row_index.set(Some(index));
let label_for_display = suggestion.label.clone(); } else {
let description_for_click = suggestion.description.clone().unwrap_or_default(); log!("Failed to cast to Element");
let description_for_display = suggestion.description.clone().unwrap_or_default(); }
let id = suggestion.id.clone(); } else {
log!("Target is not a valid HTML element");
// Tags for the item }
let tags = vec![ } else {
("source".to_string(), "wikidata".to_string()), log!("Target is not a valid Node");
("wikidata_id".to_string(), id.clone()),
];
view! {
<li on:click=move |_| {
set_items.update(|items| {
if let Some(item) = items.get_mut(index) {
item.description = description_for_click.clone();
item.tags.extend(tags.clone());
item.wikidata_id = Some(id.clone());
item.name = label_for_click.clone();
}
});
}>
{ format!("{} - {}", label_for_display, description_for_display) }
</li>
} }
}).collect::<Vec<_>>() }
}} }
</ul> on:blur=move |_| set_active_row_index.set(None)
>
<EditableCell
value=item.name.clone()
on_input=move |value| update_item(index, "name", value)
key=format!("name-{}", index)
/>
</div>
</td> </td>
// Editable Description Field // Editable Description Field
<td> <td>
@ -194,6 +243,7 @@ pub fn ItemsList(
}).collect::<Vec<_>>()} }).collect::<Vec<_>>()}
</tbody> </tbody>
</table> </table>
{render_popup()}
</div> </div>
} }
} }