Add the right-click ability on avatars
This commit is contained in:
parent
d1cbb3ba34
commit
0492043625
7 changed files with 438 additions and 2 deletions
|
|
@ -15,8 +15,11 @@ use uuid::Uuid;
|
|||
|
||||
use chattyness_db::models::{ChannelMemberWithAvatar, LooseProp, Scene};
|
||||
|
||||
#[cfg(feature = "hydrate")]
|
||||
use super::avatar_canvas::hit_test_canvas;
|
||||
use super::avatar_canvas::{member_key, AvatarCanvas};
|
||||
use super::chat_types::ActiveBubble;
|
||||
use super::context_menu::{ContextMenu, ContextMenuItem};
|
||||
use super::settings::{
|
||||
calculate_min_zoom, ViewerSettings, BASE_PROP_SIZE, REFERENCE_HEIGHT, REFERENCE_WIDTH,
|
||||
};
|
||||
|
|
@ -53,6 +56,15 @@ pub fn RealmSceneViewer(
|
|||
/// Members that are fading out after timeout disconnect.
|
||||
#[prop(optional, into)]
|
||||
fading_members: Option<Signal<Vec<FadingMember>>>,
|
||||
/// Current user's user_id (for context menu filtering).
|
||||
#[prop(optional, into)]
|
||||
current_user_id: Option<Signal<Option<Uuid>>>,
|
||||
/// Current user's guest_session_id (for context menu filtering).
|
||||
#[prop(optional, into)]
|
||||
current_guest_session_id: Option<Signal<Option<Uuid>>>,
|
||||
/// Callback when whisper is requested on a member.
|
||||
#[prop(optional, into)]
|
||||
on_whisper_request: Option<Callback<String>>,
|
||||
) -> impl IntoView {
|
||||
// Use default settings if none provided
|
||||
let settings = settings.unwrap_or_else(|| Signal::derive(ViewerSettings::default));
|
||||
|
|
@ -115,6 +127,11 @@ pub fn RealmSceneViewer(
|
|||
// Signal to track when scale factors have been properly calculated
|
||||
let (scales_ready, set_scales_ready) = signal(false);
|
||||
|
||||
// Context menu state
|
||||
let (context_menu_open, set_context_menu_open) = signal(false);
|
||||
let (context_menu_position, set_context_menu_position) = signal((0.0_f64, 0.0_f64));
|
||||
let (context_menu_target, set_context_menu_target) = signal(Option::<String>::None);
|
||||
|
||||
// Handle overlay click for movement or prop pickup
|
||||
// TODO: Add hit-testing for avatar clicks
|
||||
#[cfg(feature = "hydrate")]
|
||||
|
|
@ -166,6 +183,75 @@ pub fn RealmSceneViewer(
|
|||
}
|
||||
};
|
||||
|
||||
// Handle right-click for context menu on avatars
|
||||
#[cfg(feature = "hydrate")]
|
||||
let on_overlay_contextmenu = {
|
||||
let current_user_id = current_user_id.clone();
|
||||
let current_guest_session_id = current_guest_session_id.clone();
|
||||
move |ev: web_sys::MouseEvent| {
|
||||
use wasm_bindgen::JsCast;
|
||||
|
||||
// Get click position
|
||||
let client_x = ev.client_x() as f64;
|
||||
let client_y = ev.client_y() as f64;
|
||||
|
||||
// Get current user identity for filtering
|
||||
let my_user_id = current_user_id.map(|s| s.get()).flatten();
|
||||
let my_guest_session_id = current_guest_session_id.map(|s| s.get()).flatten();
|
||||
|
||||
// Query all avatar canvases and check for hit
|
||||
let document = web_sys::window().unwrap().document().unwrap();
|
||||
|
||||
// Get the avatars container and find all canvas elements within it
|
||||
if let Some(container) = document.query_selector(".avatars-container").ok().flatten() {
|
||||
let canvases = container.get_elements_by_tag_name("canvas");
|
||||
let canvas_count = canvases.length();
|
||||
|
||||
for i in 0..canvas_count {
|
||||
if let Some(element) = canvases.item(i) {
|
||||
if let Ok(canvas) = element.dyn_into::<web_sys::HtmlCanvasElement>() {
|
||||
// Check for data-member-id attribute
|
||||
if let Some(member_id_str) = canvas.get_attribute("data-member-id") {
|
||||
// Check if click hits a non-transparent pixel
|
||||
if hit_test_canvas(&canvas, client_x, client_y) {
|
||||
// Parse the member ID to determine if it's a user_id or guest_session_id
|
||||
if let Ok(member_id) = member_id_str.parse::<Uuid>() {
|
||||
// Check if this is the current user's avatar
|
||||
let is_current_user = my_user_id == Some(member_id) ||
|
||||
my_guest_session_id == Some(member_id);
|
||||
|
||||
if !is_current_user {
|
||||
// Find the display name for this member
|
||||
let display_name = members.get().iter()
|
||||
.find(|m| {
|
||||
m.member.user_id == Some(member_id) ||
|
||||
m.member.guest_session_id == Some(member_id)
|
||||
})
|
||||
.map(|m| m.member.display_name.clone());
|
||||
|
||||
if let Some(name) = display_name {
|
||||
// Prevent default browser context menu
|
||||
ev.prevent_default();
|
||||
|
||||
// Show context menu at click position
|
||||
set_context_menu_position.set((client_x, client_y));
|
||||
set_context_menu_target.set(Some(name));
|
||||
set_context_menu_open.set(true);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// No avatar hit - allow default context menu
|
||||
}
|
||||
};
|
||||
|
||||
#[cfg(feature = "hydrate")]
|
||||
{
|
||||
use std::cell::RefCell;
|
||||
|
|
@ -918,6 +1004,41 @@ pub fn RealmSceneViewer(
|
|||
#[cfg(not(feature = "hydrate"))]
|
||||
let _ = ev;
|
||||
}
|
||||
on:contextmenu=move |ev| {
|
||||
#[cfg(feature = "hydrate")]
|
||||
on_overlay_contextmenu(ev);
|
||||
#[cfg(not(feature = "hydrate"))]
|
||||
let _ = ev;
|
||||
}
|
||||
/>
|
||||
// Context menu for avatar interactions
|
||||
<ContextMenu
|
||||
open=Signal::derive(move || context_menu_open.get())
|
||||
position=Signal::derive(move || context_menu_position.get())
|
||||
items=Signal::derive(move || {
|
||||
vec![
|
||||
ContextMenuItem {
|
||||
label: "Whisper".to_string(),
|
||||
action: "whisper".to_string(),
|
||||
},
|
||||
]
|
||||
})
|
||||
on_select=Callback::new({
|
||||
let on_whisper_request = on_whisper_request.clone();
|
||||
move |action: String| {
|
||||
if action == "whisper" {
|
||||
if let Some(target) = context_menu_target.get() {
|
||||
if let Some(ref callback) = on_whisper_request {
|
||||
callback.run(target);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
on_close=Callback::new(move |_: ()| {
|
||||
set_context_menu_open.set(false);
|
||||
set_context_menu_target.set(None);
|
||||
})
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue