✨(frontend) add raised hands waiting room
Show the raised hand waiting list, allow any participant to lower other participants' hands. Inspired from Gmeet. I found it quite clear to have a dedicated waiting list for raised hands.
This commit is contained in:
committed by
aleb_the_flash
parent
59ec88e84a
commit
584be7e65b
@@ -0,0 +1,33 @@
|
|||||||
|
import { Participant } from 'livekit-client'
|
||||||
|
import { fetchServerApi } from './fetchServerApi'
|
||||||
|
import { buildServerApiUrl } from './buildServerApiUrl'
|
||||||
|
import { useRoomData } from '../hooks/useRoomData'
|
||||||
|
|
||||||
|
export const useLowerHandParticipant = () => {
|
||||||
|
const data = useRoomData()
|
||||||
|
|
||||||
|
const lowerHandParticipant = (participant: Participant) => {
|
||||||
|
if (!data || !data?.livekit) {
|
||||||
|
throw new Error('Room data is not available')
|
||||||
|
}
|
||||||
|
const newMetadata = JSON.parse(participant.metadata || '{}')
|
||||||
|
newMetadata.raised = !newMetadata.raised
|
||||||
|
return fetchServerApi(
|
||||||
|
buildServerApiUrl(
|
||||||
|
data.livekit.url,
|
||||||
|
'twirp/livekit.RoomService/UpdateParticipant'
|
||||||
|
),
|
||||||
|
data.livekit.token,
|
||||||
|
{
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify({
|
||||||
|
room: data.livekit.room,
|
||||||
|
identity: participant.identity,
|
||||||
|
metadata: JSON.stringify(newMetadata),
|
||||||
|
permission: participant.permissions,
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return { lowerHandParticipant }
|
||||||
|
}
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
import { css } from '@/styled-system/css'
|
||||||
|
|
||||||
|
import { HStack } from '@/styled-system/jsx'
|
||||||
|
import { Text } from '@/primitives/Text'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import { Avatar } from '@/components/Avatar'
|
||||||
|
import { getParticipantColor } from '@/features/rooms/utils/getParticipantColor'
|
||||||
|
import { Participant } from 'livekit-client'
|
||||||
|
import { isLocal } from '@/utils/livekit'
|
||||||
|
import { RiHand } from '@remixicon/react'
|
||||||
|
import { ListItemActionButton } from '@/features/rooms/livekit/components/controls/Participants/ListItemActionButton'
|
||||||
|
import { useLowerHandParticipant } from '@/features/rooms/livekit/api/lowerHandParticipant.ts'
|
||||||
|
|
||||||
|
type HandRaisedListItemProps = {
|
||||||
|
participant: Participant
|
||||||
|
}
|
||||||
|
|
||||||
|
export const HandRaisedListItem = ({
|
||||||
|
participant,
|
||||||
|
}: HandRaisedListItemProps) => {
|
||||||
|
const { t } = useTranslation('rooms')
|
||||||
|
const name = participant.name || participant.identity
|
||||||
|
|
||||||
|
const { lowerHandParticipant } = useLowerHandParticipant()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<HStack
|
||||||
|
role="listitem"
|
||||||
|
justify="space-between"
|
||||||
|
key={participant.identity}
|
||||||
|
id={participant.identity}
|
||||||
|
className={css({
|
||||||
|
padding: '0.25rem 0',
|
||||||
|
width: 'full',
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<HStack>
|
||||||
|
<Avatar name={name} bgColor={getParticipantColor(participant)} />
|
||||||
|
<Text
|
||||||
|
variant={'sm'}
|
||||||
|
className={css({
|
||||||
|
userSelect: 'none',
|
||||||
|
cursor: 'default',
|
||||||
|
display: 'flex',
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
className={css({
|
||||||
|
whiteSpace: 'nowrap',
|
||||||
|
overflow: 'hidden',
|
||||||
|
textOverflow: 'ellipsis',
|
||||||
|
maxWidth: '120px',
|
||||||
|
display: 'block',
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
{name}
|
||||||
|
</span>
|
||||||
|
{isLocal(participant) && (
|
||||||
|
<span
|
||||||
|
className={css({
|
||||||
|
marginLeft: '.25rem',
|
||||||
|
whiteSpace: 'nowrap',
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
({t('participants.you')})
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</Text>
|
||||||
|
</HStack>
|
||||||
|
<ListItemActionButton
|
||||||
|
onPress={() => lowerHandParticipant(participant)}
|
||||||
|
tooltip={t('participants.lowerParticipantHand', { name })}
|
||||||
|
>
|
||||||
|
<RiHand color="gray" />
|
||||||
|
</ListItemActionButton>
|
||||||
|
</HStack>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -10,6 +10,7 @@ import { useTranslation } from 'react-i18next'
|
|||||||
import { allParticipantRoomEvents } from '@/features/rooms/livekit/constants/events'
|
import { allParticipantRoomEvents } from '@/features/rooms/livekit/constants/events'
|
||||||
import { ParticipantListItem } from '@/features/rooms/livekit/components/controls/Participants/ParticipantListItem'
|
import { ParticipantListItem } from '@/features/rooms/livekit/components/controls/Participants/ParticipantListItem'
|
||||||
import { ParticipantsCollapsableList } from '@/features/rooms/livekit/components/controls/Participants/ParticipantsCollapsableList'
|
import { ParticipantsCollapsableList } from '@/features/rooms/livekit/components/controls/Participants/ParticipantsCollapsableList'
|
||||||
|
import { HandRaisedListItem } from '@/features/rooms/livekit/components/controls/Participants/HandRaisedListItem'
|
||||||
|
|
||||||
// TODO: Optimize rendering performance, especially for longer participant lists, even though they are generally short.
|
// TODO: Optimize rendering performance, especially for longer participant lists, even though they are generally short.
|
||||||
export const ParticipantsList = () => {
|
export const ParticipantsList = () => {
|
||||||
@@ -35,6 +36,11 @@ export const ParticipantsList = () => {
|
|||||||
...sortedRemoteParticipants,
|
...sortedRemoteParticipants,
|
||||||
]
|
]
|
||||||
|
|
||||||
|
const raisedHandParticipants = participants.filter((participant) => {
|
||||||
|
const data = JSON.parse(participant.metadata || '{}')
|
||||||
|
return data.raised
|
||||||
|
})
|
||||||
|
|
||||||
// TODO - extract inline styling in a centralized styling file, and avoid magic numbers
|
// TODO - extract inline styling in a centralized styling file, and avoid magic numbers
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
@@ -83,6 +89,21 @@ export const ParticipantsList = () => {
|
|||||||
>
|
>
|
||||||
{t('participants.subheading').toUpperCase()}
|
{t('participants.subheading').toUpperCase()}
|
||||||
</H>
|
</H>
|
||||||
|
{raisedHandParticipants.length > 0 && (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
marginBottom: '.9375rem',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<ParticipantsCollapsableList
|
||||||
|
heading={t('participants.raisedHands')}
|
||||||
|
participants={raisedHandParticipants}
|
||||||
|
renderParticipant={(participant) => (
|
||||||
|
<HandRaisedListItem participant={participant} />
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
<ParticipantsCollapsableList
|
<ParticipantsCollapsableList
|
||||||
heading={t('participants.contributors')}
|
heading={t('participants.contributors')}
|
||||||
participants={sortedParticipants}
|
participants={sortedParticipants}
|
||||||
|
|||||||
@@ -66,6 +66,8 @@
|
|||||||
"description": "",
|
"description": "",
|
||||||
"confirm": "",
|
"confirm": "",
|
||||||
"cancel": ""
|
"cancel": ""
|
||||||
}
|
},
|
||||||
|
"raisedHands": "",
|
||||||
|
"lowerParticipantHand": ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -66,6 +66,8 @@
|
|||||||
"description": "Mute {{name}} for all participants? {{name}} is the only person who can unmute themselves.",
|
"description": "Mute {{name}} for all participants? {{name}} is the only person who can unmute themselves.",
|
||||||
"confirm": "Mute",
|
"confirm": "Mute",
|
||||||
"cancel": "Cancel"
|
"cancel": "Cancel"
|
||||||
}
|
},
|
||||||
|
"raisedHands": "Raised hands",
|
||||||
|
"lowerParticipantHand": "Lower {{name}}'s hand"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -66,6 +66,8 @@
|
|||||||
"description": "Couper le micro de {{name}} pour tous les participants ? {{name}} est la seule personne habilitée à réactiver son micro",
|
"description": "Couper le micro de {{name}} pour tous les participants ? {{name}} est la seule personne habilitée à réactiver son micro",
|
||||||
"confirm": "Couper le micro",
|
"confirm": "Couper le micro",
|
||||||
"cancel": "Annuler"
|
"cancel": "Annuler"
|
||||||
}
|
},
|
||||||
|
"raisedHands": "Mains levées",
|
||||||
|
"lowerParticipantHand": "Baisser la main de {{name}}"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user