'use client' import { useEffect, useState } from 'react' import { useReactFlow } from 'reactflow' import Avatar from '@/app/components/base/avatar' import { useCollaboration } from '../collaboration/hooks/use-collaboration' import { useStore } from '../store' import cn from '@/utils/classnames' import { ChevronDown } from '@/app/components/base/icons/src/vender/solid/arrows' import { getUserColor } from '../collaboration/utils/user-color' import Tooltip from '@/app/components/base/tooltip' import { PortalToFollowElem, PortalToFollowElemContent, PortalToFollowElemTrigger, } from '@/app/components/base/portal-to-follow-elem' import { useAppContext } from '@/context/app-context' import { getAvatar } from '@/service/common' const useAvatarUrls = (users: any[]) => { const [avatarUrls, setAvatarUrls] = useState>({}) useEffect(() => { const fetchAvatars = async () => { const newAvatarUrls: Record = {} await Promise.all( users.map(async (user) => { if (user.avatar) { try { const response = await getAvatar({ avatar: user.avatar }) newAvatarUrls[user.sid] = response.avatar_url } catch (error) { console.error('Failed to fetch avatar:', error) newAvatarUrls[user.sid] = user.avatar } } }), ) setAvatarUrls(newAvatarUrls) } if (users.length > 0) fetchAvatars() }, [users]) return avatarUrls } const OnlineUsers = () => { const appId = useStore(s => s.appId) const { onlineUsers, cursors } = useCollaboration(appId as string) const { userProfile } = useAppContext() const reactFlow = useReactFlow() const [dropdownOpen, setDropdownOpen] = useState(false) const avatarUrls = useAvatarUrls(onlineUsers || []) const currentUserId = userProfile?.id // Function to jump to user's cursor position const jumpToUserCursor = (userId: string) => { const cursor = cursors[userId] if (!cursor) return // Convert world coordinates to center the view on the cursor reactFlow.setCenter(cursor.x, cursor.y, { zoom: 1, duration: 800 }) } if (!onlineUsers || onlineUsers.length === 0) return null // Display logic: // 1-3 users: show all avatars // 4+ users: show 2 avatars + count + arrow const shouldShowCount = onlineUsers.length >= 4 const maxVisible = shouldShowCount ? 2 : 3 const visibleUsers = onlineUsers.slice(0, maxVisible) const remainingCount = onlineUsers.length - maxVisible const getAvatarUrl = (user: any) => { return avatarUrls[user.sid] || user.avatar } return (
{visibleUsers.map((user, index) => { const isCurrentUser = user.user_id === currentUserId const userColor = isCurrentUser ? undefined : getUserColor(user.user_id) const displayName = isCurrentUser ? `${user.username || 'User'} (You)` : (user.username || 'User') return (
!isCurrentUser && jumpToUserCursor(user.user_id)} >
) })} {remainingCount > 0 && ( setDropdownOpen(true)} onMouseLeave={() => setDropdownOpen(false)} asChild >
+{remainingCount}
setDropdownOpen(true)} onMouseLeave={() => setDropdownOpen(false)} className="z-[9999]" >
{onlineUsers.map((user) => { const isCurrentUser = user.user_id === currentUserId const userColor = isCurrentUser ? undefined : getUserColor(user.user_id) const displayName = isCurrentUser ? `${user.username || 'User'} (You)` : (user.username || 'User') return (
!isCurrentUser && jumpToUserCursor(user.user_id)} >
{displayName}
) })}
)}
) } export default OnlineUsers