🚸(frontend) offer additionnal controls on participant tile

On hover, based on participant's type (remove/local) offer an
appropriate action. Either applying effects on the local participant
video or muting the remote participant.

This is a huge enhancement in term of UX, nobody was finding these two
controls in the current menus, and though the features were not
implemented.
This commit is contained in:
lebaudantoine
2025-01-31 17:50:46 +01:00
committed by aleb_the_flash
parent 03f6e6519b
commit 6373593de3
4 changed files with 84 additions and 5 deletions

View File

@@ -1,11 +1,24 @@
import { css } from '@/styled-system/css'
import { HStack } from '@/styled-system/jsx'
import { Button } from '@/primitives'
import { RiPushpin2Line, RiUnpinLine } from '@remixicon/react'
import { useFocusToggle } from '@livekit/components-react'
import {
RiImageCircleAiFill,
RiMicLine,
RiMicOffLine,
RiPushpin2Line,
RiUnpinLine,
} from '@remixicon/react'
import {
useFocusToggle,
useTrackMutedIndicator,
} from '@livekit/components-react'
import { useTranslation } from 'react-i18next'
import { TrackReferenceOrPlaceholder } from '@livekit/components-core'
import { useEffect, useState } from 'react'
import { useSidePanel } from '@/features/rooms/livekit/hooks/useSidePanel'
import { Participant, Track } from 'livekit-client'
import { MuteAlertDialog } from './MuteAlertDialog'
import { useMuteParticipant } from '../api/muteParticipant'
const FocusButton = ({
trackRef,
@@ -31,6 +44,59 @@ const FocusButton = ({
)
}
const EffectsButton = () => {
const { t } = useTranslation('rooms', { keyPrefix: 'participantTileFocus' })
const { isEffectsOpen, toggleEffects } = useSidePanel()
return (
<Button
size={'sm'}
variant={'primaryTextDark'}
square
tooltip={t('effects')}
onPress={() => !isEffectsOpen && toggleEffects()}
>
<RiImageCircleAiFill />
</Button>
)
}
const MuteButton = ({ participant }: { participant: Participant }) => {
const { t } = useTranslation('rooms', { keyPrefix: 'participantTileFocus' })
const { isMuted } = useTrackMutedIndicator({
participant: participant,
source: Track.Source.Microphone,
})
const { muteParticipant } = useMuteParticipant()
const [isAlertOpen, setIsAlertOpen] = useState(false)
const name = participant.name || participant.identity
return (
<>
<Button
isDisabled={isMuted}
size={'sm'}
variant={'primaryTextDark'}
square
onPress={() => setIsAlertOpen(true)}
tooltip={t('muteParticipant', { name })}
>
{!isMuted ? <RiMicLine /> : <RiMicOffLine />}
</Button>
<MuteAlertDialog
isOpen={isAlertOpen}
onSubmit={() =>
muteParticipant(participant).then(() => setIsAlertOpen(false))
}
onClose={() => setIsAlertOpen(false)}
name={name}
/>
</>
)
}
export const ParticipantTileFocus = ({
trackRef,
}: {
@@ -50,6 +116,8 @@ export const ParticipantTileFocus = ({
}
}, [hovered])
const participant = trackRef.participant
return (
<div
className={css({
@@ -89,6 +157,11 @@ export const ParticipantTileFocus = ({
})}
>
<FocusButton trackRef={trackRef} />
{participant.isLocal ? (
<EffectsButton />
) : (
<MuteButton participant={participant} />
)}
</HStack>
</div>
)}

View File

@@ -195,6 +195,8 @@
"pin": {
"enable": "",
"disable": ""
}
},
"effects": "",
"muteParticipant": ""
}
}

View File

@@ -194,6 +194,8 @@
"pin": {
"enable": "Pin",
"disable": "Unpin"
}
},
"effects": "Apply visual effects",
"muteParticipant": "Mute {{name}}"
}
}

View File

@@ -194,6 +194,8 @@
"pin": {
"enable": "Épingler",
"disable": "Annuler l'épinglage"
}
},
"effects": "Appliquer des effets",
"muteParticipant": "Couper le micro de {{name}}"
}
}