diff --git a/src/frontend/src/features/rooms/livekit/components/FocusLayout.tsx b/src/frontend/src/features/rooms/livekit/components/FocusLayout.tsx new file mode 100644 index 00000000..b491f558 --- /dev/null +++ b/src/frontend/src/features/rooms/livekit/components/FocusLayout.tsx @@ -0,0 +1,6 @@ +import { ParticipantTile } from './ParticipantTile' +import { FocusLayoutProps } from '@livekit/components-react' + +export function FocusLayout({ trackRef, ...htmlProps }: FocusLayoutProps) { + return +} diff --git a/src/frontend/src/features/rooms/livekit/components/ParticipantTile.tsx b/src/frontend/src/features/rooms/livekit/components/ParticipantTile.tsx new file mode 100644 index 00000000..66bf217e --- /dev/null +++ b/src/frontend/src/features/rooms/livekit/components/ParticipantTile.tsx @@ -0,0 +1,145 @@ +import { + AudioTrack, + ConnectionQualityIndicator, + FocusToggle, + LockLockedIcon, + ParticipantName, + ParticipantPlaceholder, + ParticipantTileProps, + ScreenShareIcon, + TrackMutedIndicator, + useEnsureTrackRef, + useFeatureContext, + useIsEncrypted, + useMaybeLayoutContext, + useMaybeTrackRefContext, + useParticipantTile, + VideoTrack, + TrackRefContext, + ParticipantContextIfNeeded, +} from '@livekit/components-react' +import React from 'react' +import { + isTrackReference, + isTrackReferencePinned, + TrackReferenceOrPlaceholder, +} from '@livekit/components-core' +import { Track } from 'livekit-client' + +export function TrackRefContextIfNeeded( + props: React.PropsWithChildren<{ + trackRef?: TrackReferenceOrPlaceholder + }> +) { + const hasContext = !!useMaybeTrackRefContext() + return props.trackRef && !hasContext ? ( + + {props.children} + + ) : ( + <>{props.children}> + ) +} + +export const ParticipantTile: ( + props: ParticipantTileProps & React.RefAttributes +) => React.ReactNode = /* @__PURE__ */ React.forwardRef< + HTMLDivElement, + ParticipantTileProps +>(function ParticipantTile( + { + trackRef, + children, + onParticipantClick, + disableSpeakingIndicator, + ...htmlProps + }: ParticipantTileProps, + ref +) { + const trackReference = useEnsureTrackRef(trackRef) + + const { elementProps } = useParticipantTile({ + htmlProps, + disableSpeakingIndicator, + onParticipantClick, + trackRef: trackReference, + }) + const isEncrypted = useIsEncrypted(trackReference.participant) + const layoutContext = useMaybeLayoutContext() + + const autoManageSubscription = useFeatureContext()?.autoSubscription + + const handleSubscribe = React.useCallback( + (subscribed: boolean) => { + if ( + trackReference.source && + !subscribed && + layoutContext && + layoutContext.pin.dispatch && + isTrackReferencePinned(trackReference, layoutContext.pin.state) + ) { + layoutContext.pin.dispatch({ msg: 'clear_pin' }) + } + }, + [trackReference, layoutContext] + ) + + return ( + + + + {children ?? ( + <> + {isTrackReference(trackReference) && + (trackReference.publication?.kind === 'video' || + trackReference.source === Track.Source.Camera || + trackReference.source === Track.Source.ScreenShare) ? ( + + ) : ( + isTrackReference(trackReference) && ( + + ) + )} + + + + + + {trackReference.source === Track.Source.Camera ? ( + <> + {isEncrypted && ( + + )} + + + > + ) : ( + <> + + 's screen + > + )} + + + + > + )} + + + + + ) +}) diff --git a/src/frontend/src/features/rooms/livekit/prefabs/VideoConference.tsx b/src/frontend/src/features/rooms/livekit/prefabs/VideoConference.tsx index 5a48642a..f6b36f2d 100644 --- a/src/frontend/src/features/rooms/livekit/prefabs/VideoConference.tsx +++ b/src/frontend/src/features/rooms/livekit/prefabs/VideoConference.tsx @@ -16,11 +16,9 @@ import * as React from 'react' import { CarouselLayout, ConnectionStateToast, - FocusLayout, FocusLayoutContainer, GridLayout, LayoutContextProvider, - ParticipantTile, RoomAudioRenderer, MessageFormatter, usePinnedTracks, @@ -35,6 +33,8 @@ import { cva } from '@/styled-system/css' import { ParticipantsList } from '@/features/rooms/livekit/components/controls/Participants/ParticipantsList' import { useSnapshot } from 'valtio' import { participantsStore } from '@/stores/participants' +import { FocusLayout } from '../components/FocusLayout' +import { ParticipantTile } from '../components/ParticipantTile' const LayoutWrapper = styled( 'div',