♻️(frontend) create a ParticipantListItem component

Main update: create a component, which has the responsability of
rendering a Participant in the list. I tried gettig closer to a
cleaner codebase, with small and well-scoped components. WIP.

Formatting participants before rendering the list was way to over
complicated, iterating twice on the list was kinda useless. Fixed!

Few updates:
- Removed capitalizing Participant's name to be consistent with the
avatar. This choices mimics GMeet UX.
- Duplicated isLocal utils function from LiveKit, which is not
exported by their package.
This commit is contained in:
lebaudantoine
2024-08-28 16:24:53 +02:00
committed by aleb_the_flash
parent 4ecb7202ec
commit 1e140a01b5
3 changed files with 88 additions and 58 deletions

View File

@@ -0,0 +1,63 @@
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'
type ParticipantListItemProps = {
participant: Participant
}
export const ParticipantListItem = ({
participant,
}: ParticipantListItemProps) => {
const { t } = useTranslation('rooms')
const name = participant.name || participant.identity
return (
<HStack
role="listitem"
key={participant.identity}
id={participant.identity}
className={css({
padding: '0.25rem 0',
})}
>
<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>
)
}

View File

@@ -3,15 +3,13 @@ import { useParticipants } from '@livekit/components-react'
import { Heading } from 'react-aria-components'
import { Box, Button, Div } from '@/primitives'
import { HStack, VStack } from '@/styled-system/jsx'
import { Text, text } from '@/primitives/Text'
import { text } from '@/primitives/Text'
import { RiCloseLine } from '@remixicon/react'
import { capitalize } from '@/utils/capitalize'
import { participantsStore } from '@/stores/participants'
import { useTranslation } from 'react-i18next'
import { allParticipantRoomEvents } from '@/features/rooms/livekit/constants/events'
import { Avatar } from '@/components/Avatar'
import { getParticipantColor } from '@/features/rooms/utils/getParticipantColor'
import { ParticipantListItem } from '@/features/rooms/livekit/components/controls/Participants/ParticipantListItem'
import { VStack } from '@/styled-system/jsx'
// TODO: Optimize rendering performance, especially for longer participant lists, even though they are generally short.
export const ParticipantsList = () => {
@@ -24,18 +22,16 @@ export const ParticipantsList = () => {
updateOnlyOn: allParticipantRoomEvents,
})
const formattedParticipants = participants.map((participant) => ({
name: participant.name || participant.identity,
id: participant.identity,
color: getParticipantColor(participant),
}))
const sortedRemoteParticipants = formattedParticipants
const sortedRemoteParticipants = participants
.slice(1)
.sort((a, b) => a.name.localeCompare(b.name))
.sort((participantA, participantB) => {
const nameA = participantA.name || participantA.identity
const nameB = participantB.name || participantB.identity
return nameA.localeCompare(nameB)
})
const allParticipants = [
formattedParticipants[0], // first participant returned by the hook, is always the local one
const sortedParticipants = [
participants[0], // first participant returned by the hook, is always the local one
...sortedRemoteParticipants,
]
@@ -74,7 +70,7 @@ export const ParticipantsList = () => {
<RiCloseLine />
</Button>
</Div>
{participants?.length > 0 && (
{sortedParticipants?.length > 0 && (
<VStack
role="list"
className={css({
@@ -87,47 +83,8 @@ export const ParticipantsList = () => {
display: 'flex',
})}
>
{allParticipants.map((participant, index) => (
<HStack
role="listitem"
key={participant.id}
id={participant.id}
className={css({
padding: '0.25rem 0',
})}
>
<Avatar name={participant.name} bgColor={participant.color} />
<Text
variant={'sm'}
className={css({
userSelect: 'none',
cursor: 'default',
display: 'flex',
})}
>
<span
className={css({
whiteSpace: 'nowrap',
overflow: 'hidden',
textOverflow: 'ellipsis',
maxWidth: '120px',
display: 'block',
})}
>
{capitalize(participant.name)}
</span>
{index === 0 && (
<span
className={css({
marginLeft: '.25rem',
whiteSpace: 'nowrap',
})}
>
({t('participants.you')})
</span>
)}
</Text>
</HStack>
{sortedParticipants.map((participant) => (
<ParticipantListItem participant={participant} />
))}
</VStack>
)}

View File

@@ -1,4 +1,10 @@
import { getBrowser, LogLevel, setLogLevel } from 'livekit-client'
import {
getBrowser,
LocalParticipant,
LogLevel,
Participant,
setLogLevel,
} from 'livekit-client'
export const silenceLiveKitLogs = (shouldSilenceLogs: boolean) => {
setLogLevel(shouldSilenceLogs ? LogLevel.silent : LogLevel.debug)
@@ -15,3 +21,7 @@ export function isChromiumBased(): boolean {
export function isSafari(): boolean {
return getBrowser()?.name === 'Safari'
}
export function isLocal(p: Participant) {
return p instanceof LocalParticipant
}