fix some emotion bugs
This commit is contained in:
parent
bd28e201a2
commit
989e20757b
11 changed files with 1203 additions and 190 deletions
|
|
@ -1,5 +1,7 @@
|
|||
//! Realm landing page after login.
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
use leptos::prelude::*;
|
||||
use leptos::reactive::owner::LocalStorage;
|
||||
#[cfg(feature = "hydrate")]
|
||||
|
|
@ -7,12 +9,17 @@ use leptos::task::spawn_local;
|
|||
#[cfg(feature = "hydrate")]
|
||||
use leptos_router::hooks::use_navigate;
|
||||
use leptos_router::hooks::use_params_map;
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::components::{Card, ChatInput, RealmHeader, RealmSceneViewer};
|
||||
use crate::components::{
|
||||
ActiveBubble, Card, ChatInput, ChatMessage, MessageLog, RealmHeader, RealmSceneViewer,
|
||||
DEFAULT_BUBBLE_TIMEOUT_MS,
|
||||
};
|
||||
#[cfg(feature = "hydrate")]
|
||||
use crate::components::use_channel_websocket;
|
||||
use chattyness_db::models::{
|
||||
ChannelMemberWithAvatar, EmotionAvailability, RealmRole, RealmWithUserRole, Scene,
|
||||
AvatarWithPaths, ChannelMemberWithAvatar, EmotionAvailability, RealmRole, RealmWithUserRole,
|
||||
Scene,
|
||||
};
|
||||
#[cfg(feature = "hydrate")]
|
||||
use chattyness_db::ws_messages::ClientMessage;
|
||||
|
|
@ -42,6 +49,12 @@ pub fn RealmPage() -> impl IntoView {
|
|||
// Skin preview path for emote picker (position 4 of skin layer)
|
||||
let (skin_preview_path, set_skin_preview_path) = signal(Option::<String>::None);
|
||||
|
||||
// Chat message state - use StoredValue for WASM compatibility (single-threaded)
|
||||
let message_log: StoredValue<MessageLog, LocalStorage> =
|
||||
StoredValue::new_local(MessageLog::new());
|
||||
let (active_bubbles, set_active_bubbles) =
|
||||
signal(HashMap::<(Option<Uuid>, Option<Uuid>), ActiveBubble>::new());
|
||||
|
||||
let realm_data = LocalResource::new(move || {
|
||||
let slug = slug.get();
|
||||
async move {
|
||||
|
|
@ -93,45 +106,31 @@ pub fn RealmPage() -> impl IntoView {
|
|||
}
|
||||
});
|
||||
|
||||
// Fetch emotion availability and avatar render data for emote picker
|
||||
// Fetch full avatar with paths for client-side emotion computation
|
||||
#[cfg(feature = "hydrate")]
|
||||
{
|
||||
let slug_for_emotions = slug.clone();
|
||||
let slug_for_avatar = slug.clone();
|
||||
Effect::new(move |_| {
|
||||
use gloo_net::http::Request;
|
||||
|
||||
let current_slug = slug_for_emotions.get();
|
||||
let current_slug = slug_for_avatar.get();
|
||||
if current_slug.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
// Fetch emotion availability
|
||||
let slug_clone = current_slug.clone();
|
||||
// Fetch full avatar with all paths resolved
|
||||
spawn_local(async move {
|
||||
let response = Request::get(&format!("/api/realms/{}/avatar/emotions", slug_clone))
|
||||
let response = Request::get(&format!("/api/realms/{}/avatar", current_slug))
|
||||
.send()
|
||||
.await;
|
||||
if let Ok(resp) = response {
|
||||
if resp.ok() {
|
||||
if let Ok(avail) = resp.json::<EmotionAvailability>().await {
|
||||
if let Ok(avatar) = resp.json::<AvatarWithPaths>().await {
|
||||
// Compute emotion availability client-side
|
||||
let avail = avatar.compute_emotion_availability();
|
||||
set_emotion_availability.set(Some(avail));
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Fetch avatar render data for skin preview
|
||||
let slug_clone2 = current_slug.clone();
|
||||
spawn_local(async move {
|
||||
use chattyness_db::models::AvatarRenderData;
|
||||
let response = Request::get(&format!("/api/realms/{}/avatar/current", slug_clone2))
|
||||
.send()
|
||||
.await;
|
||||
if let Ok(resp) = response {
|
||||
if resp.ok() {
|
||||
if let Ok(render_data) = resp.json::<AvatarRenderData>().await {
|
||||
// Get skin layer position 4 (center)
|
||||
set_skin_preview_path.set(render_data.skin_layer[4].clone());
|
||||
// Get skin layer position 4 (center) for preview
|
||||
set_skin_preview_path.set(avatar.skin_layer[4].clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -145,11 +144,33 @@ pub fn RealmPage() -> impl IntoView {
|
|||
set_members.set(new_members);
|
||||
});
|
||||
|
||||
// Chat message callback
|
||||
#[cfg(feature = "hydrate")]
|
||||
let on_chat_message = Callback::new(move |msg: ChatMessage| {
|
||||
// Add to message log
|
||||
message_log.update_value(|log| log.push(msg.clone()));
|
||||
|
||||
// Update active bubbles
|
||||
let key = (msg.user_id, msg.guest_session_id);
|
||||
let expires_at = msg.timestamp + DEFAULT_BUBBLE_TIMEOUT_MS;
|
||||
|
||||
set_active_bubbles.update(|bubbles| {
|
||||
bubbles.insert(
|
||||
key,
|
||||
ActiveBubble {
|
||||
message: msg,
|
||||
expires_at,
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
#[cfg(feature = "hydrate")]
|
||||
let (_ws_state, ws_sender) = use_channel_websocket(
|
||||
slug,
|
||||
Signal::derive(move || channel_id.get()),
|
||||
on_members_update,
|
||||
on_chat_message,
|
||||
);
|
||||
|
||||
// Set channel ID when scene loads (triggers WebSocket connection)
|
||||
|
|
@ -163,6 +184,21 @@ pub fn RealmPage() -> impl IntoView {
|
|||
});
|
||||
}
|
||||
|
||||
// Cleanup expired speech bubbles every 5 seconds
|
||||
#[cfg(feature = "hydrate")]
|
||||
{
|
||||
use gloo_timers::callback::Interval;
|
||||
|
||||
let cleanup_interval = Interval::new(5000, move || {
|
||||
let now = js_sys::Date::now() as i64;
|
||||
set_active_bubbles.update(|bubbles| {
|
||||
bubbles.retain(|_, bubble| bubble.expires_at > now);
|
||||
});
|
||||
});
|
||||
// Keep interval alive
|
||||
std::mem::forget(cleanup_interval);
|
||||
}
|
||||
|
||||
// Handle position update via WebSocket
|
||||
#[cfg(feature = "hydrate")]
|
||||
let on_move = Callback::new(move |(x, y): (f64, f64)| {
|
||||
|
|
@ -211,7 +247,8 @@ pub fn RealmPage() -> impl IntoView {
|
|||
let key = ev.key();
|
||||
|
||||
// If chat is focused, let it handle all keys
|
||||
if chat_focused.get() {
|
||||
// Use get_untracked() since we're in a JS event handler, not a reactive context
|
||||
if chat_focused.get_untracked() {
|
||||
*e_pressed_clone.borrow_mut() = false;
|
||||
return;
|
||||
}
|
||||
|
|
@ -374,12 +411,14 @@ pub fn RealmPage() -> impl IntoView {
|
|||
let ws_for_chat = ws_sender_clone.clone();
|
||||
#[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());
|
||||
view! {
|
||||
<div class="relative w-full">
|
||||
<RealmSceneViewer
|
||||
scene=scene
|
||||
realm_slug=realm_slug_for_viewer.clone()
|
||||
members=members_signal
|
||||
active_bubbles=active_bubbles_signal
|
||||
on_move=on_move.clone()
|
||||
/>
|
||||
<div class="absolute bottom-0 left-0 right-0 z-10 pb-4 px-4 pointer-events-none">
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue