make emotions named instead, add drop prop

This commit is contained in:
Evan Carroll 2026-01-13 16:49:07 -06:00
parent 989e20757b
commit ea3b444d71
19 changed files with 1429 additions and 150 deletions

View file

@ -12,14 +12,14 @@ use leptos_router::hooks::use_params_map;
use uuid::Uuid;
use crate::components::{
ActiveBubble, Card, ChatInput, ChatMessage, MessageLog, RealmHeader, RealmSceneViewer,
DEFAULT_BUBBLE_TIMEOUT_MS,
ActiveBubble, Card, ChatInput, ChatMessage, InventoryPopup, MessageLog, RealmHeader,
RealmSceneViewer, DEFAULT_BUBBLE_TIMEOUT_MS,
};
#[cfg(feature = "hydrate")]
use crate::components::use_channel_websocket;
use chattyness_db::models::{
AvatarWithPaths, ChannelMemberWithAvatar, EmotionAvailability, RealmRole, RealmWithUserRole,
Scene,
AvatarWithPaths, ChannelMemberWithAvatar, EmotionAvailability, EmotionState, LooseProp,
RealmRole, RealmWithUserRole, Scene,
};
#[cfg(feature = "hydrate")]
use chattyness_db::ws_messages::ClientMessage;
@ -55,6 +55,12 @@ pub fn RealmPage() -> impl IntoView {
let (active_bubbles, set_active_bubbles) =
signal(HashMap::<(Option<Uuid>, Option<Uuid>), ActiveBubble>::new());
// Inventory popup state
let (inventory_open, set_inventory_open) = signal(false);
// Loose props state
let (loose_props, set_loose_props) = signal(Vec::<LooseProp>::new());
let realm_data = LocalResource::new(move || {
let slug = slug.get();
async move {
@ -165,15 +171,40 @@ pub fn RealmPage() -> impl IntoView {
});
});
// Loose props callbacks
#[cfg(feature = "hydrate")]
let on_loose_props_sync = Callback::new(move |props: Vec<LooseProp>| {
set_loose_props.set(props);
});
#[cfg(feature = "hydrate")]
let on_prop_dropped = Callback::new(move |prop: LooseProp| {
set_loose_props.update(|props| {
props.push(prop);
});
});
#[cfg(feature = "hydrate")]
let on_prop_picked_up = Callback::new(move |prop_id: Uuid| {
set_loose_props.update(|props| {
props.retain(|p| p.id != prop_id);
});
});
#[cfg(feature = "hydrate")]
let (_ws_state, ws_sender) = use_channel_websocket(
slug,
Signal::derive(move || channel_id.get()),
on_members_update,
on_chat_message,
on_loose_props_sync,
on_prop_dropped,
on_prop_picked_up,
);
// Set channel ID when scene loads (triggers WebSocket connection)
// Note: Currently using scene.id as the channel_id since channel_members
// uses scenes directly. Proper channel infrastructure can be added later.
#[cfg(feature = "hydrate")]
{
Effect::new(move |_| {
@ -212,6 +243,21 @@ pub fn RealmPage() -> impl IntoView {
#[cfg(not(feature = "hydrate"))]
let on_move = Callback::new(move |(_x, _y): (f64, f64)| {});
// Handle prop click (pickup) via WebSocket
#[cfg(feature = "hydrate")]
let on_prop_click = Callback::new(move |prop_id: Uuid| {
ws_sender.with_value(|sender| {
if let Some(send_fn) = sender {
send_fn(ClientMessage::PickUpProp {
loose_prop_id: prop_id,
});
}
});
});
#[cfg(not(feature = "hydrate"))]
let on_prop_click = Callback::new(move |_prop_id: Uuid| {});
// Handle global keyboard shortcuts (e+0-9 for emotions, : for chat focus)
#[cfg(feature = "hydrate")]
{
@ -266,6 +312,13 @@ pub fn RealmPage() -> impl IntoView {
return;
}
// Handle 'i' to open inventory
if key == "i" || key == "I" {
set_inventory_open.set(true);
ev.prevent_default();
return;
}
// Check if 'e' key was pressed
if key == "e" || key == "E" {
*e_pressed_clone.borrow_mut() = true;
@ -276,8 +329,10 @@ pub fn RealmPage() -> impl IntoView {
if *e_pressed_clone.borrow() {
*e_pressed_clone.borrow_mut() = false; // Reset regardless of outcome
if key.len() == 1 {
if let Ok(emotion) = key.parse::<u8>() {
if emotion <= 9 {
if let Ok(digit) = key.parse::<u8>() {
// Convert digit to emotion name using EmotionState
if let Some(emotion_state) = EmotionState::from_index(digit) {
let emotion = emotion_state.to_string();
#[cfg(debug_assertions)]
web_sys::console::log_1(
&format!("[Emotion] Sending emotion {}", emotion).into(),
@ -394,6 +449,7 @@ pub fn RealmPage() -> impl IntoView {
}>
{move || {
let on_move = on_move.clone();
let on_prop_click = on_prop_click.clone();
let on_chat_focus_change = on_chat_focus_change.clone();
let realm_slug_for_viewer = realm_slug_val.clone();
#[cfg(feature = "hydrate")]
@ -412,6 +468,7 @@ pub fn RealmPage() -> impl IntoView {
#[cfg(not(feature = "hydrate"))]
let ws_for_chat: StoredValue<Option<WsSender>, LocalStorage> = StoredValue::new_local(None);
let active_bubbles_signal = Signal::derive(move || active_bubbles.get());
let loose_props_signal = Signal::derive(move || loose_props.get());
view! {
<div class="relative w-full">
<RealmSceneViewer
@ -419,7 +476,9 @@ pub fn RealmPage() -> impl IntoView {
realm_slug=realm_slug_for_viewer.clone()
members=members_signal
active_bubbles=active_bubbles_signal
loose_props=loose_props_signal
on_move=on_move.clone()
on_prop_click=on_prop_click.clone()
/>
<div class="absolute bottom-0 left-0 right-0 z-10 pb-4 px-4 pointer-events-none">
<ChatInput
@ -451,6 +510,23 @@ pub fn RealmPage() -> impl IntoView {
}}
</Suspense>
</main>
// Inventory popup
{
#[cfg(feature = "hydrate")]
let ws_sender_for_inv = ws_sender.clone();
#[cfg(not(feature = "hydrate"))]
let ws_sender_for_inv: StoredValue<Option<WsSender>, LocalStorage> = StoredValue::new_local(None);
view! {
<InventoryPopup
open=Signal::derive(move || inventory_open.get())
on_close=Callback::new(move |_: ()| {
set_inventory_open.set(false);
})
ws_sender=ws_sender_for_inv
/>
}
}
}
.into_any()
}