fix: avatar center to cursor, and cleanup.

Lots of cleanup, went in with this too
This commit is contained in:
Evan Carroll 2026-01-17 22:32:34 -06:00
parent c3320ddcce
commit fe65835f4a
14 changed files with 769 additions and 708 deletions

View file

@ -8,6 +8,8 @@ use chattyness_db::models::{InventoryItem, PublicProp};
#[cfg(feature = "hydrate")]
use chattyness_db::ws_messages::ClientMessage;
use super::modals::Modal;
use super::tabs::{Tab, TabBar};
use super::ws_client::WsSender;
/// Inventory popup component.
@ -191,36 +193,6 @@ pub fn InventoryPopup(
});
}
// Handle escape key to close
#[cfg(feature = "hydrate")]
{
use wasm_bindgen::{closure::Closure, JsCast};
Effect::new(move |_| {
if !open.get() {
return;
}
let on_close_clone = on_close.clone();
let closure =
Closure::<dyn Fn(web_sys::KeyboardEvent)>::new(move |ev: web_sys::KeyboardEvent| {
if ev.key() == "Escape" {
on_close_clone.run(());
}
});
if let Some(window) = web_sys::window() {
let _ = window.add_event_listener_with_callback(
"keydown",
closure.as_ref().unchecked_ref(),
);
}
// Intentionally not cleaning up - closure lives for session
closure.forget();
});
}
// Handle drop action via WebSocket
#[cfg(feature = "hydrate")]
let handle_drop = {
@ -250,135 +222,64 @@ pub fn InventoryPopup(
#[cfg(not(feature = "hydrate"))]
let handle_drop = |_item_id: Uuid| {};
let on_close_backdrop = on_close.clone();
let on_close_button = on_close.clone();
view! {
<Show when=move || open.get()>
<div
class="fixed inset-0 z-50 flex items-center justify-center"
role="dialog"
aria-modal="true"
aria-labelledby="inventory-modal-title"
>
// Backdrop
<div
class="absolute inset-0 bg-black/70 backdrop-blur-sm"
on:click=move |_| on_close_backdrop.run(())
aria-hidden="true"
/>
<Modal
open=open
on_close=on_close
title="Inventory"
title_id="inventory-modal-title"
max_width="max-w-2xl"
class="max-h-[80vh] flex flex-col"
>
// Tab bar
<TabBar
tabs=vec![
Tab::new("my_inventory", "My Inventory"),
Tab::new("server", "Server"),
Tab::new("realm", "Realm"),
]
active=Signal::derive(move || active_tab.get())
on_select=Callback::new(move |id| set_active_tab.set(id))
/>
// Modal content
<div class="relative bg-gray-800 rounded-lg shadow-2xl max-w-2xl w-full mx-4 p-6 border border-gray-700 max-h-[80vh] flex flex-col">
// Header
<div class="flex items-center justify-between mb-4">
<h2 id="inventory-modal-title" class="text-xl font-bold text-white">
"Inventory"
</h2>
<button
type="button"
class="text-gray-400 hover:text-white transition-colors"
on:click=move |_| on_close_button.run(())
aria-label="Close inventory"
>
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/>
</svg>
</button>
</div>
// Tab content
<div class="flex-1 overflow-y-auto min-h-[300px]">
// My Inventory tab
<Show when=move || active_tab.get() == "my_inventory">
<MyInventoryTab
items=items
loading=loading
error=error
selected_item=selected_item
set_selected_item=set_selected_item
dropping=dropping
on_drop=Callback::new(handle_drop)
/>
</Show>
// Tab bar
<div class="flex border-b border-gray-700 mb-4" role="tablist">
<button
type="button"
role="tab"
aria-selected=move || active_tab.get() == "my_inventory"
class=move || format!(
"px-4 py-2 font-medium transition-colors border-b-2 -mb-px {}",
if active_tab.get() == "my_inventory" {
"text-blue-400 border-blue-400"
} else {
"text-gray-400 border-transparent hover:text-gray-300"
}
)
on:click=move |_| set_active_tab.set("my_inventory")
>
"My Inventory"
</button>
<button
type="button"
role="tab"
aria-selected=move || active_tab.get() == "server"
class=move || format!(
"px-4 py-2 font-medium transition-colors border-b-2 -mb-px {}",
if active_tab.get() == "server" {
"text-blue-400 border-blue-400"
} else {
"text-gray-400 border-transparent hover:text-gray-300"
}
)
on:click=move |_| set_active_tab.set("server")
>
"Server"
</button>
<button
type="button"
role="tab"
aria-selected=move || active_tab.get() == "realm"
class=move || format!(
"px-4 py-2 font-medium transition-colors border-b-2 -mb-px {}",
if active_tab.get() == "realm" {
"text-blue-400 border-blue-400"
} else {
"text-gray-400 border-transparent hover:text-gray-300"
}
)
on:click=move |_| set_active_tab.set("realm")
>
"Realm"
</button>
</div>
// Server tab
<Show when=move || active_tab.get() == "server">
<PublicPropsTab
props=server_props
loading=server_loading
error=server_error
tab_name="Server"
empty_message="No public server props available"
/>
</Show>
// Tab content
<div class="flex-1 overflow-y-auto min-h-[300px]">
// My Inventory tab
<Show when=move || active_tab.get() == "my_inventory">
<MyInventoryTab
items=items
loading=loading
error=error
selected_item=selected_item
set_selected_item=set_selected_item
dropping=dropping
on_drop=Callback::new(handle_drop)
/>
</Show>
// Server tab
<Show when=move || active_tab.get() == "server">
<PublicPropsTab
props=server_props
loading=server_loading
error=server_error
tab_name="Server"
empty_message="No public server props available"
/>
</Show>
// Realm tab
<Show when=move || active_tab.get() == "realm">
<PublicPropsTab
props=realm_props
loading=realm_loading
error=realm_error
tab_name="Realm"
empty_message="No public realm props available"
/>
</Show>
</div>
</div>
// Realm tab
<Show when=move || active_tab.get() == "realm">
<PublicPropsTab
props=realm_props
loading=realm_loading
error=realm_error
tab_name="Realm"
empty_message="No public realm props available"
/>
</Show>
</div>
</Show>
</Modal>
}
}