add :emote and :list to chat
This commit is contained in:
parent
1ca300098f
commit
bd28e201a2
7 changed files with 741 additions and 22 deletions
|
|
@ -1,6 +1,7 @@
|
|||
//! Realm landing page after login.
|
||||
|
||||
use leptos::prelude::*;
|
||||
use leptos::reactive::owner::LocalStorage;
|
||||
#[cfg(feature = "hydrate")]
|
||||
use leptos::task::spawn_local;
|
||||
#[cfg(feature = "hydrate")]
|
||||
|
|
@ -10,10 +11,14 @@ use leptos_router::hooks::use_params_map;
|
|||
use crate::components::{Card, ChatInput, RealmHeader, RealmSceneViewer};
|
||||
#[cfg(feature = "hydrate")]
|
||||
use crate::components::use_channel_websocket;
|
||||
use chattyness_db::models::{ChannelMemberWithAvatar, RealmRole, RealmWithUserRole, Scene};
|
||||
use chattyness_db::models::{
|
||||
ChannelMemberWithAvatar, EmotionAvailability, RealmRole, RealmWithUserRole, Scene,
|
||||
};
|
||||
#[cfg(feature = "hydrate")]
|
||||
use chattyness_db::ws_messages::ClientMessage;
|
||||
|
||||
use crate::components::ws_client::WsSender;
|
||||
|
||||
/// Realm landing page component.
|
||||
#[component]
|
||||
pub fn RealmPage() -> impl IntoView {
|
||||
|
|
@ -27,6 +32,16 @@ pub fn RealmPage() -> impl IntoView {
|
|||
let (members, set_members) = signal(Vec::<ChannelMemberWithAvatar>::new());
|
||||
let (channel_id, set_channel_id) = signal(Option::<uuid::Uuid>::None);
|
||||
|
||||
// Chat focus coordination
|
||||
let (chat_focused, set_chat_focused) = signal(false);
|
||||
let (focus_chat_trigger, set_focus_chat_trigger) = signal(false);
|
||||
|
||||
// Emotion availability for emote picker
|
||||
let (emotion_availability, set_emotion_availability) =
|
||||
signal(Option::<EmotionAvailability>::None);
|
||||
// Skin preview path for emote picker (position 4 of skin layer)
|
||||
let (skin_preview_path, set_skin_preview_path) = signal(Option::<String>::None);
|
||||
|
||||
let realm_data = LocalResource::new(move || {
|
||||
let slug = slug.get();
|
||||
async move {
|
||||
|
|
@ -78,6 +93,52 @@ pub fn RealmPage() -> impl IntoView {
|
|||
}
|
||||
});
|
||||
|
||||
// Fetch emotion availability and avatar render data for emote picker
|
||||
#[cfg(feature = "hydrate")]
|
||||
{
|
||||
let slug_for_emotions = slug.clone();
|
||||
Effect::new(move |_| {
|
||||
use gloo_net::http::Request;
|
||||
|
||||
let current_slug = slug_for_emotions.get();
|
||||
if current_slug.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
// Fetch emotion availability
|
||||
let slug_clone = current_slug.clone();
|
||||
spawn_local(async move {
|
||||
let response = Request::get(&format!("/api/realms/{}/avatar/emotions", slug_clone))
|
||||
.send()
|
||||
.await;
|
||||
if let Ok(resp) = response {
|
||||
if resp.ok() {
|
||||
if let Ok(avail) = resp.json::<EmotionAvailability>().await {
|
||||
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());
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// WebSocket connection for real-time updates
|
||||
#[cfg(feature = "hydrate")]
|
||||
let on_members_update = Callback::new(move |new_members: Vec<ChannelMemberWithAvatar>| {
|
||||
|
|
@ -115,7 +176,7 @@ pub fn RealmPage() -> impl IntoView {
|
|||
#[cfg(not(feature = "hydrate"))]
|
||||
let on_move = Callback::new(move |(_x, _y): (f64, f64)| {});
|
||||
|
||||
// Handle emotion change via keyboard (e then 0-9)
|
||||
// Handle global keyboard shortcuts (e+0-9 for emotions, : for chat focus)
|
||||
#[cfg(feature = "hydrate")]
|
||||
{
|
||||
use std::cell::RefCell;
|
||||
|
|
@ -149,6 +210,25 @@ pub fn RealmPage() -> impl IntoView {
|
|||
let closure = Closure::new(move |ev: web_sys::KeyboardEvent| {
|
||||
let key = ev.key();
|
||||
|
||||
// If chat is focused, let it handle all keys
|
||||
if chat_focused.get() {
|
||||
*e_pressed_clone.borrow_mut() = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle ':' to focus chat input
|
||||
if key == ":" {
|
||||
set_focus_chat_trigger.set(true);
|
||||
// Reset trigger after a short delay so it can be triggered again
|
||||
use gloo_timers::callback::Timeout;
|
||||
Timeout::new(100, move || {
|
||||
set_focus_chat_trigger.set(false);
|
||||
})
|
||||
.forget();
|
||||
ev.prevent_default();
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if 'e' key was pressed
|
||||
if key == "e" || key == "E" {
|
||||
*e_pressed_clone.borrow_mut() = true;
|
||||
|
|
@ -189,6 +269,11 @@ pub fn RealmPage() -> impl IntoView {
|
|||
});
|
||||
}
|
||||
|
||||
// Callback for chat focus changes
|
||||
let on_chat_focus_change = Callback::new(move |focused: bool| {
|
||||
set_chat_focused.set(focused);
|
||||
});
|
||||
|
||||
// Create logout callback (WebSocket disconnects automatically)
|
||||
let on_logout = Callback::new(move |_: ()| {
|
||||
#[cfg(feature = "hydrate")]
|
||||
|
|
@ -272,13 +357,23 @@ pub fn RealmPage() -> impl IntoView {
|
|||
}>
|
||||
{move || {
|
||||
let on_move = on_move.clone();
|
||||
let on_chat_focus_change = on_chat_focus_change.clone();
|
||||
let realm_slug_for_viewer = realm_slug_val.clone();
|
||||
#[cfg(feature = "hydrate")]
|
||||
let ws_sender_clone = ws_sender.clone();
|
||||
entry_scene
|
||||
.get()
|
||||
.map(|maybe_scene| {
|
||||
match maybe_scene {
|
||||
Some(scene) => {
|
||||
let members_signal = Signal::derive(move || members.get());
|
||||
let emotion_avail_signal = Signal::derive(move || emotion_availability.get());
|
||||
let skin_path_signal = Signal::derive(move || skin_preview_path.get());
|
||||
let focus_trigger_signal = Signal::derive(move || focus_chat_trigger.get());
|
||||
#[cfg(feature = "hydrate")]
|
||||
let ws_for_chat = ws_sender_clone.clone();
|
||||
#[cfg(not(feature = "hydrate"))]
|
||||
let ws_for_chat: StoredValue<Option<WsSender>, LocalStorage> = StoredValue::new_local(None);
|
||||
view! {
|
||||
<div class="relative w-full">
|
||||
<RealmSceneViewer
|
||||
|
|
@ -287,8 +382,14 @@ pub fn RealmPage() -> impl IntoView {
|
|||
members=members_signal
|
||||
on_move=on_move.clone()
|
||||
/>
|
||||
<div class="absolute bottom-0 left-0 right-0 z-10 pb-4 px-4">
|
||||
<ChatInput />
|
||||
<div class="absolute bottom-0 left-0 right-0 z-10 pb-4 px-4 pointer-events-none">
|
||||
<ChatInput
|
||||
ws_sender=ws_for_chat
|
||||
emotion_availability=emotion_avail_signal
|
||||
skin_preview_path=skin_path_signal
|
||||
focus_trigger=focus_trigger_signal
|
||||
on_focus_change=on_chat_focus_change.clone()
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue