From 965d823d088d8b4b3e3872b219ce275d7a634513 Mon Sep 17 00:00:00 2001 From: lebaudantoine Date: Fri, 1 Aug 2025 12:35:42 +0200 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8(frontend)=20display=20position=20of?= =?UTF-8?q?=20a=20raised=20hand=20in=20the=20queue?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Users requested an enhanced visual indicator for raised hands on the participant tile. Most major video conferencing platforms display the position of a raised hand in the queue. This helps hosts quickly see who is requesting to speak, and in what order, without needing to open the full participant list. While a minor feature, this improvement is especially valuable for power user --- .../livekit/components/ParticipantTile.tsx | 38 ++++++++++++------- .../rooms/livekit/hooks/useRaisedHand.ts | 32 +++++++++++++++- 2 files changed, 56 insertions(+), 14 deletions(-) diff --git a/src/frontend/src/features/rooms/livekit/components/ParticipantTile.tsx b/src/frontend/src/features/rooms/livekit/components/ParticipantTile.tsx index 4e5947e2..dabe9203 100644 --- a/src/frontend/src/features/rooms/livekit/components/ParticipantTile.tsx +++ b/src/frontend/src/features/rooms/livekit/components/ParticipantTile.tsx @@ -22,7 +22,7 @@ import { } from '@livekit/components-core' import { Track } from 'livekit-client' import { RiHand } from '@remixicon/react' -import { useRaisedHand } from '../hooks/useRaisedHand' +import { useRaisedHand, useRaisedHandPosition } from '../hooks/useRaisedHand' import { HStack } from '@/styled-system/jsx' import { MutedMicIndicator } from './MutedMicIndicator' import { ParticipantPlaceholder } from './ParticipantPlaceholder' @@ -97,6 +97,10 @@ export const ParticipantTile: ( participant: trackReference.participant, }) + const { positionInQueue, firstInQueue } = useRaisedHandPosition({ + participant: trackReference.participant, + }) + const isScreenShare = trackReference.source != Track.Source.Camera return ( @@ -141,24 +145,32 @@ export const ParticipantTile: ( style={{ padding: '0.1rem 0.25rem', backgroundColor: - isHandRaised && !isScreenShare ? 'white' : undefined, + isHandRaised && !isScreenShare + ? firstInQueue + ? '#fde047' + : 'white' + : undefined, color: isHandRaised && !isScreenShare ? 'black' : undefined, transition: 'background 200ms ease, color 400ms ease', }} > {isHandRaised && !isScreenShare && ( - + <> + {positionInQueue} + + )} {isScreenShare && ( { + if (!isHandRaised) return + + return ( + participants + .filter((p) => !!p.attributes.handRaisedAt) + .sort((a, b) => { + const dateA = new Date(a.attributes.handRaisedAt) + const dateB = new Date(b.attributes.handRaisedAt) + return dateA.getTime() - dateB.getTime() + }) + .findIndex((p) => p.identity === participant.identity) + 1 + ) + }, [participants, participant, isHandRaised]) + + return { + positionInQueue, + firstInQueue: positionInQueue == 1, + } +} + export function useRaisedHand({ participant }: useRaisedHandProps) { const handRaisedAtAttribute = useParticipantAttribute('handRaisedAt', { participant,