✨(frontend) display position of a raised hand in the queue
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
This commit is contained in:
committed by
aleb_the_flash
parent
1db189ace2
commit
965d823d08
@@ -22,7 +22,7 @@ import {
|
|||||||
} from '@livekit/components-core'
|
} from '@livekit/components-core'
|
||||||
import { Track } from 'livekit-client'
|
import { Track } from 'livekit-client'
|
||||||
import { RiHand } from '@remixicon/react'
|
import { RiHand } from '@remixicon/react'
|
||||||
import { useRaisedHand } from '../hooks/useRaisedHand'
|
import { useRaisedHand, useRaisedHandPosition } from '../hooks/useRaisedHand'
|
||||||
import { HStack } from '@/styled-system/jsx'
|
import { HStack } from '@/styled-system/jsx'
|
||||||
import { MutedMicIndicator } from './MutedMicIndicator'
|
import { MutedMicIndicator } from './MutedMicIndicator'
|
||||||
import { ParticipantPlaceholder } from './ParticipantPlaceholder'
|
import { ParticipantPlaceholder } from './ParticipantPlaceholder'
|
||||||
@@ -97,6 +97,10 @@ export const ParticipantTile: (
|
|||||||
participant: trackReference.participant,
|
participant: trackReference.participant,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const { positionInQueue, firstInQueue } = useRaisedHandPosition({
|
||||||
|
participant: trackReference.participant,
|
||||||
|
})
|
||||||
|
|
||||||
const isScreenShare = trackReference.source != Track.Source.Camera
|
const isScreenShare = trackReference.source != Track.Source.Camera
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -141,24 +145,32 @@ export const ParticipantTile: (
|
|||||||
style={{
|
style={{
|
||||||
padding: '0.1rem 0.25rem',
|
padding: '0.1rem 0.25rem',
|
||||||
backgroundColor:
|
backgroundColor:
|
||||||
isHandRaised && !isScreenShare ? 'white' : undefined,
|
isHandRaised && !isScreenShare
|
||||||
|
? firstInQueue
|
||||||
|
? '#fde047'
|
||||||
|
: 'white'
|
||||||
|
: undefined,
|
||||||
color:
|
color:
|
||||||
isHandRaised && !isScreenShare ? 'black' : undefined,
|
isHandRaised && !isScreenShare ? 'black' : undefined,
|
||||||
transition: 'background 200ms ease, color 400ms ease',
|
transition: 'background 200ms ease, color 400ms ease',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{isHandRaised && !isScreenShare && (
|
{isHandRaised && !isScreenShare && (
|
||||||
<RiHand
|
<>
|
||||||
color="black"
|
<span>{positionInQueue}</span>
|
||||||
size={16}
|
<RiHand
|
||||||
style={{
|
color="black"
|
||||||
marginRight: '0.4rem',
|
size={16}
|
||||||
minWidth: '16px',
|
style={{
|
||||||
animationDuration: '300ms',
|
marginRight: '0.4rem',
|
||||||
animationName: 'wave_hand',
|
marginLeft: '0.1rem',
|
||||||
animationIterationCount: '2',
|
minWidth: '16px',
|
||||||
}}
|
animationDuration: '300ms',
|
||||||
/>
|
animationName: 'wave_hand',
|
||||||
|
animationIterationCount: '2',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
{isScreenShare && (
|
{isScreenShare && (
|
||||||
<ScreenShareIcon
|
<ScreenShareIcon
|
||||||
|
|||||||
@@ -1,11 +1,41 @@
|
|||||||
import { LocalParticipant, Participant } from 'livekit-client'
|
import { LocalParticipant, Participant } from 'livekit-client'
|
||||||
import { useParticipantAttribute } from '@livekit/components-react'
|
import {
|
||||||
|
useParticipantAttribute,
|
||||||
|
useParticipants,
|
||||||
|
} from '@livekit/components-react'
|
||||||
import { isLocal } from '@/utils/livekit'
|
import { isLocal } from '@/utils/livekit'
|
||||||
|
import { useMemo } from 'react'
|
||||||
|
|
||||||
type useRaisedHandProps = {
|
type useRaisedHandProps = {
|
||||||
participant: Participant
|
participant: Participant
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function useRaisedHandPosition({ participant }: useRaisedHandProps) {
|
||||||
|
const { isHandRaised } = useRaisedHand({ participant })
|
||||||
|
|
||||||
|
const participants = useParticipants()
|
||||||
|
|
||||||
|
const positionInQueue = useMemo(() => {
|
||||||
|
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) {
|
export function useRaisedHand({ participant }: useRaisedHandProps) {
|
||||||
const handRaisedAtAttribute = useParticipantAttribute('handRaisedAt', {
|
const handRaisedAtAttribute = useParticipantAttribute('handRaisedAt', {
|
||||||
participant,
|
participant,
|
||||||
|
|||||||
Reference in New Issue
Block a user