diff --git a/packages/call/src/components/modals/base.tsx b/packages/call/src/components/modals/base.tsx index 03e1883..6b9593b 100644 --- a/packages/call/src/components/modals/base.tsx +++ b/packages/call/src/components/modals/base.tsx @@ -1,21 +1,24 @@ -// show speaking ring - import { ContextMenu, ContextMenuContent, ContextMenuItem, ContextMenuTrigger, } from "@tensamin/ui"; -import { focusParticipant, setCallView, useCall } from "../../store"; +import { + focusParticipant, + getParsedParticipantMetadata, + setCallView, + useCall, +} from "../../store"; import { Track, type Participant } from "livekit-client"; import { useEffect, useState } from "react"; import { useUser, type User } from "@tensamin/user/context"; import VideoViewer from "../videoViewer"; -import { Loader2, MicOff, Monitor } from "lucide-react"; +import { Loader2, MicOff, Monitor, Shield } from "lucide-react"; function TransparentButton({ children }: { children: React.ReactNode }) { return ( -
+
{children}
); @@ -30,8 +33,12 @@ function Overlay({ user: User; participant: Participant; }) { + const { isAdmin } = getParsedParticipantMetadata(user.user_id) || { + isAdmin: false, + }; + return ( -
+
{type === "user" && ( <> {!participant.isMicrophoneEnabled && ( @@ -39,6 +46,11 @@ function Overlay({ )} + {isAdmin && ( + + + + )} )} {type === "stream" && ( @@ -130,9 +142,10 @@ export default function Base({ }`} >
- {view === "grid" && ( + {view === "grid" || + (view === "focused" && user.user_id !== focusedParticipantId) ? ( - )} + ) : null}
; isEncrypted: boolean; room: Room; keyProvider: ExternalE2EEKeyProvider; @@ -181,6 +186,51 @@ function hasParticipant(participantId: number) { ); } +function parseParticipantMetadata( + metadata: string | undefined, +): ParticipantMetadata | undefined { + if (!metadata) { + return undefined; + } + + try { + return JSON.parse(metadata); + } catch { + return undefined; + } +} + +function getParsedParticipantMetadataById(): Record< + number, + ParticipantMetadata +> { + return Object.fromEntries( + getAllParticipants() + .map((participant) => { + const participantId = getParticipantId(participant.identity); + + if (participantId == null) { + return null; + } + + return [ + participantId, + parseParticipantMetadata(participant.metadata), + ] as const; + }) + .filter( + (entry): entry is readonly [number, ParticipantMetadata] => + entry != null, + ), + ); +} + +function syncParsedParticipantMetadata() { + useCall.setState({ + parsedParticipantMetadataById: getParsedParticipantMetadataById(), + }); +} + function syncScreenShareParticipants() { const activeScreenShareParticipantIds = getActiveScreenShareParticipantIds(); const state = useCall.getState(); @@ -231,6 +281,13 @@ export function syncParticipantState() { }); syncScreenShareParticipants(); + syncParsedParticipantMetadata(); +} + +export function getParsedParticipantMetadata( + participantId: number, +): ParticipantMetadata | undefined { + return useCall.getState().parsedParticipantMetadataById[participantId]; } // set state functions @@ -468,6 +525,7 @@ export async function disconnect() { watchedStreamParticipantIds: [], pendingWatchedParticipantIds: [], activeScreenShareParticipantIds: [], + parsedParticipantMetadataById: {}, }); room.remoteParticipants.forEach((participant) => { @@ -611,6 +669,7 @@ export function resetCallState() { watchedStreamParticipantIds: [], pendingWatchedParticipantIds: [], activeScreenShareParticipantIds: [], + parsedParticipantMetadataById: {}, }); syncParticipantState(); @@ -652,6 +711,7 @@ export const useCall = create(() => ({ watchedStreamParticipantIds: [], pendingWatchedParticipantIds: [], activeScreenShareParticipantIds: [], + parsedParticipantMetadataById: {}, isEncrypted: room.localParticipant.isE2EEEnabled && room.localParticipant.isEncrypted, room, diff --git a/packages/call/todo.md b/packages/call/todo.md index 2534699..d34ceff 100644 --- a/packages/call/todo.md +++ b/packages/call/todo.md @@ -8,3 +8,5 @@ - Preview image - Mobile - Deaf status is not synced +- Call invite popup +- Sounds