mirror of
https://github.com/suitenumerique/meet.git
synced 2026-07-26 11:58:53 +00:00
♻️(frontend) extract pinned track a11y announcement into a leaf component
Extract the code responsible for announcing pinned track changes (for accessibility) into a well-scoped leaf component. Previously, calling the useScreenReaderAnnounce hook inside StageLayout triggered a re-render of the whole layout subtree. Encapsulating the announcement logic in a narrow component keeps those re-renders local to that leaf.
This commit is contained in:
committed by
aleb_the_flash
parent
f842cc340e
commit
63a7de402b
@@ -0,0 +1,6 @@
|
||||
import { usePinAnnouncements } from '../hooks/usePinAnnouncements'
|
||||
|
||||
export const PinAnnouncer = () => {
|
||||
usePinAnnouncements()
|
||||
return null
|
||||
}
|
||||
@@ -1,8 +1,4 @@
|
||||
import {
|
||||
FocusLayoutContainer,
|
||||
useRoomContext,
|
||||
useTracks,
|
||||
} from '@livekit/components-react'
|
||||
import { FocusLayoutContainer, useTracks } from '@livekit/components-react'
|
||||
import { CarouselLayout } from '@/features/layout/components/CarouselLayout'
|
||||
import { FocusLayout } from '@/features/layout/components/FocusLayout'
|
||||
import { ParticipantTile } from '@/features/rooms/livekit/components/ParticipantTile'
|
||||
@@ -13,36 +9,15 @@ import {
|
||||
log,
|
||||
type TrackReferenceOrPlaceholder,
|
||||
} from '@livekit/components-core'
|
||||
import { type Participant, RoomEvent, Track } from 'livekit-client'
|
||||
import { RoomEvent, Track } from 'livekit-client'
|
||||
import { useSnapshot } from 'valtio'
|
||||
import { clearPinnedTrack, layoutStore, setPinnedTrack } from '@/stores/layout'
|
||||
import { useCallback, useEffect, useRef } from 'react'
|
||||
import { useScreenReaderAnnounce } from '@/hooks/useScreenReaderAnnounce'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { getParticipantName } from '@/features/rooms/utils/getParticipantName'
|
||||
import { useEffect, useRef } from 'react'
|
||||
|
||||
export const StageLayout = () => {
|
||||
const lastAutoFocusedScreenShareTrack =
|
||||
useRef<TrackReferenceOrPlaceholder | null>(null)
|
||||
|
||||
const lastPinnedParticipantIdentityRef = useRef<string | null>(null)
|
||||
|
||||
const { t } = useTranslation('rooms', { keyPrefix: 'pinAnnouncements' })
|
||||
const { t: tRooms } = useTranslation('rooms')
|
||||
|
||||
const room = useRoomContext()
|
||||
const announce = useScreenReaderAnnounce()
|
||||
|
||||
const getAnnouncementName = useCallback(
|
||||
(participant?: Participant | null) => {
|
||||
if (!participant) return tRooms('participants.unknown')
|
||||
return participant.isLocal
|
||||
? tRooms('participants.you')
|
||||
: getParticipantName(participant)
|
||||
},
|
||||
[tRooms]
|
||||
)
|
||||
|
||||
const tracks = useTracks(
|
||||
[
|
||||
{ source: Track.Source.Camera, withPlaceholder: true },
|
||||
@@ -61,58 +36,6 @@ export const StageLayout = () => {
|
||||
(track) => !isEqualTrackRef(track, pinnedTrackRef)
|
||||
)
|
||||
|
||||
// handle pin announcements
|
||||
useEffect(() => {
|
||||
const participant = pinnedTrackRef?.participant
|
||||
|
||||
// 1. unpin
|
||||
if (!participant) {
|
||||
if (!lastPinnedParticipantIdentityRef.current) return
|
||||
|
||||
const lastIdentity = lastPinnedParticipantIdentityRef.current
|
||||
const lastParticipant =
|
||||
room.localParticipant.identity === lastIdentity
|
||||
? room.localParticipant
|
||||
: room.remoteParticipants.get(lastIdentity)
|
||||
const announcementName = getAnnouncementName(lastParticipant)
|
||||
|
||||
announce(
|
||||
lastParticipant?.isLocal
|
||||
? t('self.unpin')
|
||||
: t('unpin', {
|
||||
name: announcementName,
|
||||
})
|
||||
)
|
||||
|
||||
lastPinnedParticipantIdentityRef.current = null
|
||||
return
|
||||
}
|
||||
|
||||
// 2. same pin → do nothing
|
||||
if (lastPinnedParticipantIdentityRef.current === participant.identity) {
|
||||
return
|
||||
}
|
||||
|
||||
// 3. new pin
|
||||
const participantName = participant.isLocal
|
||||
? tRooms('participants.you')
|
||||
: getParticipantName(participant)
|
||||
|
||||
lastPinnedParticipantIdentityRef.current = participant.identity
|
||||
|
||||
announce(
|
||||
participant.isLocal ? t('self.pin') : t('pin', { name: participantName })
|
||||
)
|
||||
}, [
|
||||
announce,
|
||||
pinnedTrackRef,
|
||||
getAnnouncementName,
|
||||
room.localParticipant,
|
||||
room.remoteParticipants,
|
||||
t,
|
||||
tRooms,
|
||||
])
|
||||
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
// Code duplicated from LiveKit; this warning will be addressed in the refactoring.
|
||||
useEffect(() => {
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
import { useEffect, useRef, useCallback } from 'react'
|
||||
import { useRoomContext } from '@livekit/components-react'
|
||||
import { Participant } from 'livekit-client'
|
||||
import { useSnapshot } from 'valtio'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { layoutStore } from '@/stores/layout'
|
||||
import { getParticipantName } from '@/features/rooms/utils/getParticipantName'
|
||||
import { useScreenReaderAnnounce } from '@/hooks/useScreenReaderAnnounce'
|
||||
|
||||
export const usePinAnnouncements = () => {
|
||||
const { t } = useTranslation('rooms', { keyPrefix: 'pinAnnouncements' })
|
||||
const { t: tRooms } = useTranslation('rooms')
|
||||
|
||||
const room = useRoomContext()
|
||||
const announce = useScreenReaderAnnounce()
|
||||
|
||||
const { pinnedTrackRef } = useSnapshot(layoutStore)
|
||||
const lastPinnedIdentityRef = useRef<string | null>(null)
|
||||
|
||||
const getAnnouncementName = useCallback(
|
||||
(participant?: Participant | null) => {
|
||||
if (!participant) return tRooms('participants.unknown')
|
||||
return participant.isLocal
|
||||
? tRooms('participants.you')
|
||||
: getParticipantName(participant)
|
||||
},
|
||||
[tRooms]
|
||||
)
|
||||
|
||||
const pinnedIdentity = pinnedTrackRef?.participant?.identity ?? null
|
||||
|
||||
useEffect(() => {
|
||||
// 1. unpin
|
||||
if (!pinnedIdentity) {
|
||||
const lastIdentity = lastPinnedIdentityRef.current
|
||||
if (!lastIdentity) return
|
||||
|
||||
const lastParticipant =
|
||||
room.localParticipant.identity === lastIdentity
|
||||
? room.localParticipant
|
||||
: room.remoteParticipants.get(lastIdentity)
|
||||
|
||||
announce(
|
||||
lastParticipant?.isLocal
|
||||
? t('self.unpin')
|
||||
: t('unpin', { name: getAnnouncementName(lastParticipant) })
|
||||
)
|
||||
|
||||
lastPinnedIdentityRef.current = null
|
||||
return
|
||||
}
|
||||
|
||||
// 2. same pin → do nothing
|
||||
if (lastPinnedIdentityRef.current === pinnedIdentity) return
|
||||
|
||||
// 3. new pin
|
||||
const participant =
|
||||
room.localParticipant.identity === pinnedIdentity
|
||||
? room.localParticipant
|
||||
: room.remoteParticipants.get(pinnedIdentity)
|
||||
|
||||
lastPinnedIdentityRef.current = pinnedIdentity
|
||||
|
||||
announce(
|
||||
participant?.isLocal
|
||||
? t('self.pin')
|
||||
: t('pin', { name: getAnnouncementName(participant) })
|
||||
)
|
||||
}, [pinnedIdentity, announce, getAnnouncementName, room, t])
|
||||
}
|
||||
@@ -24,7 +24,8 @@ import { ReactionPortals } from '@/features/reactions/components/ReactionPortals
|
||||
import { RoomContentArea } from '@/features/layout/components/RoomContentArea'
|
||||
import { usePictureInPicture } from '@/features/pip/hooks/usePictureInPicture'
|
||||
import { PipRoomPlaceholder } from '@/features/pip/components/PipRoomPlaceholder'
|
||||
import { StageLayout } from '@/features/layout/components/StageLayout.tsx'
|
||||
import { StageLayout } from '@/features/layout/components/StageLayout'
|
||||
import { PinAnnouncer } from '@/features/layout/components/PinAnnouncer'
|
||||
|
||||
/**
|
||||
* @public
|
||||
@@ -88,6 +89,7 @@ export function VideoConference({ ...props }: VideoConferenceProps) {
|
||||
onClose={() => setIsShareErrorVisible(false)}
|
||||
/>
|
||||
<IsIdleDisconnectModal />
|
||||
<PinAnnouncer />
|
||||
<RoomContentArea>
|
||||
{isPictureInPictureOpen ? <PipRoomPlaceholder /> : <StageLayout />}
|
||||
</RoomContentArea>
|
||||
|
||||
Reference in New Issue
Block a user