From 990a92a32cba6cd6a6b266a76b4334c79ee6980e Mon Sep 17 00:00:00 2001 From: YoJames2019 Date: Mon, 9 Feb 2026 22:17:28 -0500 Subject: [PATCH] redo roomcallnavstatus ui, force user preferred mute/video states when first joining calls, update variable names and remove unnecessary logic --- src/app/features/call/CallView.tsx | 19 +- .../features/room-nav/RoomCallNavStatus.tsx | 119 +++++++------ src/app/pages/client/call/CallProvider.tsx | 162 +++++++++--------- .../client/call/PersistentCallContainer.tsx | 44 ++--- 4 files changed, 175 insertions(+), 169 deletions(-) diff --git a/src/app/features/call/CallView.tsx b/src/app/features/call/CallView.tsx index 1bac7376..3bb6465f 100644 --- a/src/app/features/call/CallView.tsx +++ b/src/app/features/call/CallView.tsx @@ -5,7 +5,7 @@ import { useCallState } from '../../pages/client/call/CallProvider'; import { useCallMembers } from '../../hooks/useCallMemberships'; import { - PrimaryRefContext, + CallRefContext, } from '../../pages/client/call/PersistentCallContainer'; import { ScreenSize, useScreenSizeContext } from '../../hooks/useScreenSize'; import { useDebounce } from '../../hooks/useDebounce'; @@ -42,7 +42,7 @@ export function CallViewUserGrid({ children }: { children: ReactNode }) { export function CallView({ room }: { room: Room }) { - const primaryIframeRef = useContext(PrimaryRefContext); + const callIframeRef = useContext(CallRefContext); const iframeHostRef = useRef(null); const originalIframeStylesRef = useRef(null); @@ -60,7 +60,7 @@ export function CallView({ room }: { room: Room }) { } = useCallState(); const isActiveCallRoom = activeCallRoomId === room.roomId - const shouldDisplayCall = isActiveCallRoom && isActiveCallReady; + const callIsCurrentAndReady = isActiveCallRoom && isActiveCallReady; const callMembers = useCallMembers(mx, room.roomId) const getName = (userId: string) => getMemberDisplayName(room, userId) ?? getMxIdLocalPart(userId); @@ -71,8 +71,7 @@ export function CallView({ room }: { room: Room }) { const screenSize = useScreenSizeContext(); const isMobile = screenSize === ScreenSize.Mobile; - /* eslint-disable-next-line no-nested-ternary */ - const activeIframeDisplayRef = primaryIframeRef + const activeIframeDisplayRef = callIframeRef const applyFixedPositioningToIframe = useCallback(() => { const iframeElement = activeIframeDisplayRef?.current; @@ -118,7 +117,7 @@ export function CallView({ room }: { room: Room }) { const iframeElement = activeIframeDisplayRef?.current; const hostElement = iframeHostRef?.current; - if (room.isCallRoom() || (shouldDisplayCall && iframeElement && hostElement)) { + if (room.isCallRoom() || (callIsCurrentAndReady && iframeElement && hostElement)) { applyFixedPositioningToIframe(); const resizeObserver = new ResizeObserver(debouncedApplyFixedPositioning); @@ -146,7 +145,7 @@ export function CallView({ room }: { room: Room }) { activeIframeDisplayRef, applyFixedPositioningToIframe, debouncedApplyFixedPositioning, - shouldDisplayCall, + callIsCurrentAndReady, room, ]); @@ -157,7 +156,7 @@ export function CallView({ room }: { room: Room }) { setViewedCallRoomId(room.roomId); navigateRoom(room.roomId); } - if (!shouldDisplayCall) { + if (!callIsCurrentAndReady) { hangUp(room.roomId); setActiveCallRoomId(room.roomId); } @@ -185,11 +184,11 @@ export function CallView({ room }: { room: Room }) { height: '100%', position: 'relative', pointerEvents: 'none', - display: shouldDisplayCall ? 'flex' : 'none', + display: callIsCurrentAndReady ? 'flex' : 'none', }} /> {callMembers.map(callMember => ( diff --git a/src/app/features/room-nav/RoomCallNavStatus.tsx b/src/app/features/room-nav/RoomCallNavStatus.tsx index ce3657a7..72c0b656 100644 --- a/src/app/features/room-nav/RoomCallNavStatus.tsx +++ b/src/app/features/room-nav/RoomCallNavStatus.tsx @@ -8,8 +8,8 @@ export function CallNavStatus() { const { activeCallRoomId, isAudioEnabled, - isVideoEnabled, - isActiveCallReady, + isVideoEnabled, + isActiveCallReady, toggleAudio, toggleVideo, hangUp, @@ -21,9 +21,6 @@ export function CallNavStatus() { navigateRoom(activeCallRoomId); } }; - if (!isActiveCallReady) { - return null; - } return ( - + {/* Going to need better icons for this */} - - {!isAudioEnabled ? 'Unmute' : 'Mute'} - - } - > - {(triggerRef) => ( - - - - )} - - - {!isVideoEnabled ? 'Video On' : 'Video Off'} - - } - > - {(triggerRef) => ( - - - - )} - - - Hang Up - - } - > - {(triggerRef) => ( - - - - )} - - - + + {mx.getRoom(activeCallRoomId)?.name} @@ -109,6 +66,62 @@ export function CallNavStatus() { )} + + + + {!isAudioEnabled ? 'Unmute' : 'Mute'} + + } + > + {(triggerRef) => ( + + + + )} + + + {!isVideoEnabled ? 'Video On' : 'Video Off'} + + } + > + {(triggerRef) => ( + + + + )} + + + + Hang Up + + } + > + {(triggerRef) => ( + + + + )} + + ); diff --git a/src/app/pages/client/call/CallProvider.tsx b/src/app/pages/client/call/CallProvider.tsx index c8022899..deccd4b8 100644 --- a/src/app/pages/client/call/CallProvider.tsx +++ b/src/app/pages/client/call/CallProvider.tsx @@ -65,39 +65,24 @@ const DEFAULT_CALL_ACTIVE = false; export function CallProvider({ children }: CallProviderProps) { const [activeCallRoomId, setActiveCallRoomIdState] = useState(null); const [viewedCallRoomId, setViewedCallRoomIdState] = useState(null); - const [activeClientWidgetApi, setActiveClientWidgetApiState] = useState( - null - ); + + const [activeClientWidgetApi, setActiveClientWidgetApiState] = useState(null); const [activeClientWidget, setActiveClientWidget] = useState(null); - const [activeClientWidgetApiRoomId, setActiveClientWidgetApiRoomId] = useState( - null - ); + const [activeClientWidgetApiRoomId, setActiveClientWidgetApiRoomId] = useState(null); const [isAudioEnabled, setIsAudioEnabledState] = useState(DEFAULT_AUDIO_ENABLED); const [isVideoEnabled, setIsVideoEnabledState] = useState(DEFAULT_VIDEO_ENABLED); const [isChatOpen, setIsChatOpenState] = useState(DEFAULT_CHAT_OPENED); - const [isActiveCallReady, setIsCallActive] = useState(DEFAULT_CALL_ACTIVE); + const [isActiveCallReady, setIsActiveCallReady] = useState(DEFAULT_CALL_ACTIVE); const { roomIdOrAlias: viewedRoomId } = useParams<{ roomIdOrAlias: string }>(); - const resetMediaState = useCallback(() => { - logger.debug('CallContext: Resetting media state to defaults.'); - setIsAudioEnabledState(DEFAULT_AUDIO_ENABLED); - setIsVideoEnabledState(DEFAULT_VIDEO_ENABLED); - }, []); - const setActiveCallRoomId = useCallback( (roomId: string | null) => { logger.warn(`CallContext: Setting activeCallRoomId to ${roomId}`); - const previousRoomId = activeCallRoomId; setActiveCallRoomIdState(roomId); - - if (roomId !== previousRoomId) { - logger.debug(`CallContext: Active call room changed, resetting media state.`); - resetMediaState(); - } }, - [resetMediaState, activeCallRoomId] + [] ); const setViewedCallRoomId = useCallback( @@ -136,10 +121,9 @@ export function CallProvider({ children }: CallProviderProps) { setActiveClientWidgetApi(clientWidgetApi, clientWidget, roomId); } else if (roomId === activeClientWidgetApiRoomId || roomId === null) { setActiveClientWidgetApi(null, null, null); - resetMediaState(); } }, - [activeClientWidgetApi, activeClientWidgetApiRoomId, setActiveClientWidgetApi, resetMediaState] + [activeClientWidgetApi, activeClientWidgetApiRoomId, setActiveClientWidgetApi] ); const hangUp = useCallback( @@ -147,7 +131,7 @@ export function CallProvider({ children }: CallProviderProps) { setActiveClientWidgetApi(null, null, null); setActiveCallRoomIdState(null); activeClientWidgetApi?.transport.send(`${WIDGET_HANGUP_ACTION}`, {}); - setIsCallActive(false); + setIsActiveCallReady(false); logger.debug(`CallContext: Hang up called.`); }, @@ -157,6 +141,71 @@ export function CallProvider({ children }: CallProviderProps) { ] ); + + const sendWidgetAction = useCallback( + async (action: WidgetApiToWidgetAction | string, data: T): Promise => { + if (!activeClientWidgetApi) { + logger.warn( + `CallContext: Cannot send action '${action}', no active API clientWidgetApi registered.` + ); + return Promise.reject(new Error('No active call clientWidgetApi')); + } + if (!activeClientWidgetApiRoomId || activeClientWidgetApiRoomId !== activeCallRoomId) { + logger.debug( + `CallContext: Cannot send action '${action}', clientWidgetApi room (${activeClientWidgetApiRoomId}) does not match active call room (${activeCallRoomId}). Stale clientWidgetApi?` + ); + return Promise.reject(new Error('Mismatched active call clientWidgetApi')); + } + + + logger.debug( + `CallContext: Sending action '${action}' via active clientWidgetApi (room: ${activeClientWidgetApiRoomId}) with data:`, + data + ); + await activeClientWidgetApi.transport.send(action as WidgetApiAction, data); + }, + [activeClientWidgetApi, activeCallRoomId, activeClientWidgetApiRoomId] + ); + + const toggleAudio = useCallback(async () => { + const newState = !isAudioEnabled; + logger.debug(`CallContext: Toggling audio. New state: enabled=${newState}`); + setIsAudioEnabledState(newState); + + if(isActiveCallReady) { + try { + await sendWidgetAction(WIDGET_MEDIA_STATE_UPDATE_ACTION, { + audio_enabled: newState, + video_enabled: isVideoEnabled, + }); + logger.debug(`CallContext: Successfully sent audio toggle action.`); + } catch (error) { + setIsAudioEnabledState(!newState); + throw error; + } + } + }, [isAudioEnabled, isVideoEnabled, sendWidgetAction, isActiveCallReady]); + + const toggleVideo = useCallback(async () => { + const newState = !isVideoEnabled; + logger.debug(`CallContext: Toggling video. New state: enabled=${newState}`); + setIsVideoEnabledState(newState); + + if(isActiveCallReady){ + try { + await sendWidgetAction(WIDGET_MEDIA_STATE_UPDATE_ACTION, { + audio_enabled: isAudioEnabled, + video_enabled: newState, + }); + logger.debug(`CallContext: Successfully sent video toggle action.`); + } catch (error) { + setIsVideoEnabledState(!newState); + throw error; + } + } + }, [isVideoEnabled, isAudioEnabled, sendWidgetAction, isActiveCallReady]); + + useEffect(() => { if (!activeCallRoomId && !viewedCallRoomId) { return; @@ -179,6 +228,7 @@ export function CallProvider({ children }: CallProviderProps) { }; const handleMediaStateUpdate = (ev: CustomEvent) => { + if(!isActiveCallReady) return; ev.preventDefault(); logger.debug( `CallContext: Received media state update from widget in room ${activeCallRoomId}:`, @@ -228,13 +278,19 @@ export function CallProvider({ children }: CallProviderProps) { }); logger.debug('Join Call'); observer.observe(iframeDoc, { childList: true, subtree: true }); - setIsCallActive(true); + setIsActiveCallReady(true); + }; logger.debug( `CallContext: Setting up listeners for clientWidgetApi in room ${activeCallRoomId}` ); + sendWidgetAction(WIDGET_MEDIA_STATE_UPDATE_ACTION, { + audio_enabled: isAudioEnabled, + video_enabled: isVideoEnabled, + }); + api.on(`action:${WIDGET_HANGUP_ACTION}`, handleHangup); api.on(`action:${WIDGET_MEDIA_STATE_UPDATE_ACTION}`, handleMediaStateUpdate); api.on(`action:${WIDGET_TILE_UPDATE}`, handleOnTileLayout); @@ -255,65 +311,9 @@ export function CallProvider({ children }: CallProviderProps) { setViewedCallRoomId, activeClientWidget?.iframe?.contentDocument, activeClientWidget?.iframe?.contentWindow?.document, + sendWidgetAction ]); - const sendWidgetAction = useCallback( - async (action: WidgetApiToWidgetAction | string, data: T): Promise => { - if (!activeClientWidgetApi) { - logger.warn( - `CallContext: Cannot send action '${action}', no active API clientWidgetApi registered.` - ); - return Promise.reject(new Error('No active call clientWidgetApi')); - } - if (!activeClientWidgetApiRoomId || activeClientWidgetApiRoomId !== activeCallRoomId) { - logger.debug( - `CallContext: Cannot send action '${action}', clientWidgetApi room (${activeClientWidgetApiRoomId}) does not match active call room (${activeCallRoomId}). Stale clientWidgetApi?` - ); - return Promise.reject(new Error('Mismatched active call clientWidgetApi')); - } - - - logger.debug( - `CallContext: Sending action '${action}' via active clientWidgetApi (room: ${activeClientWidgetApiRoomId}) with data:`, - data - ); - await activeClientWidgetApi.transport.send(action as WidgetApiAction, data); - }, - [activeClientWidgetApi, activeCallRoomId, activeClientWidgetApiRoomId] - ); - - const toggleAudio = useCallback(async () => { - const newState = !isAudioEnabled; - logger.debug(`CallContext: Toggling audio. New state: enabled=${newState}`); - setIsAudioEnabledState(newState); - try { - await sendWidgetAction(WIDGET_MEDIA_STATE_UPDATE_ACTION, { - audio_enabled: newState, - video_enabled: isVideoEnabled, - }); - logger.debug(`CallContext: Successfully sent audio toggle action.`); - } catch (error) { - setIsAudioEnabledState(!newState); - throw error; - } - }, [isAudioEnabled, isVideoEnabled, sendWidgetAction]); - - const toggleVideo = useCallback(async () => { - const newState = !isVideoEnabled; - logger.debug(`CallContext: Toggling video. New state: enabled=${newState}`); - setIsVideoEnabledState(newState); - try { - await sendWidgetAction(WIDGET_MEDIA_STATE_UPDATE_ACTION, { - audio_enabled: isAudioEnabled, - video_enabled: newState, - }); - logger.debug(`CallContext: Successfully sent video toggle action.`); - } catch (error) { - setIsVideoEnabledState(!newState); - throw error; - } - }, [isVideoEnabled, isAudioEnabled, sendWidgetAction]); - const toggleChat = useCallback(async () => { const newState = !isChatOpen; setIsChatOpenState(newState); diff --git a/src/app/pages/client/call/PersistentCallContainer.tsx b/src/app/pages/client/call/PersistentCallContainer.tsx index eb8b3ff6..f2e4144e 100644 --- a/src/app/pages/client/call/PersistentCallContainer.tsx +++ b/src/app/pages/client/call/PersistentCallContainer.tsx @@ -19,12 +19,12 @@ interface PersistentCallContainerProps { children: ReactNode; } -export const PrimaryRefContext = createContext(null); +export const CallRefContext = createContext(null); export function PersistentCallContainer({ children }: PersistentCallContainerProps) { - const primaryIframeRef = useRef(null); - const primaryWidgetApiRef = useRef(null); - const primarySmallWidgetRef = useRef(null); + const callIframeRef = useRef(null); + const callWidgetApiRef = useRef(null); + const callSmallWidgetRef = useRef(null); const { activeCallRoomId, @@ -39,11 +39,6 @@ export function PersistentCallContainer({ children }: PersistentCallContainerPro const screenSize = useScreenSizeContext(); const theme = useTheme() const isMobile = screenSize === ScreenSize.Mobile; - const { roomIdOrAlias: viewedRoomId } = useParams(); - const isViewingActiveCall = useMemo( - () => activeCallRoomId !== null && activeCallRoomId === viewedRoomId, - [activeCallRoomId, viewedRoomId] - ); /* eslint-disable no-param-reassign */ @@ -56,17 +51,19 @@ export function PersistentCallContainer({ children }: PersistentCallContainerPro themeKind: ThemeKind | null ) => { if (mx?.getUserId()) { - logger.debug(`CallContextJ: iframe src - ${iframeRef.current.src}`) - logger.debug(`CallContextJ: activeCallRoomId: ${activeCallRoomId} viewedId: ${viewedCallRoomId} isactive: ${isActiveCallReady}`) + logger.debug(`PersistentCallContainer: (setupWidget) activeCallRoomId: ${activeCallRoomId} viewedId: ${viewedCallRoomId} isactive: ${isActiveCallReady}`) if ( (activeCallRoomId !== viewedCallRoomId && isActiveCallReady) || (activeCallRoomId && !isActiveCallReady) || (!activeCallRoomId && viewedCallRoomId && !isActiveCallReady) ) { + const roomIdToSet = (skipLobby ? activeCallRoomId : viewedCallRoomId) ?? ''; + if (roomIdToSet === '') { return; } + const widgetId = `element-call-${roomIdToSet}-${Date.now()}`; const newUrl = getWidgetUrl( mx, @@ -82,8 +79,8 @@ export function PersistentCallContainer({ children }: PersistentCallContainerPro ); if ( - primarySmallWidgetRef.current?.roomId && - (activeClientWidget?.roomId && activeClientWidget.roomId === primarySmallWidgetRef.current?.roomId) + callSmallWidgetRef.current?.roomId && + (activeClientWidget?.roomId && activeClientWidget.roomId === callSmallWidgetRef.current?.roomId) ) { return; } @@ -134,30 +131,27 @@ export function PersistentCallContainer({ children }: PersistentCallContainerPro ] ); - useEffect(() => { - logger.debug(`CallContextJ: ${isActiveCallReady} ${isViewingActiveCall}`) - }, [isActiveCallReady, isViewingActiveCall]) useEffect(() => { if (activeCallRoomId){ - setupWidget(primaryWidgetApiRef, primarySmallWidgetRef, primaryIframeRef, true, theme.kind); - logger.debug(`CallContextJ: set primary widget: ${primaryWidgetApiRef.current?.eventNames()} ${primarySmallWidgetRef.current} ${primaryIframeRef.current?.baseURI}`) + setupWidget(callWidgetApiRef, callSmallWidgetRef, callIframeRef, true, theme.kind); + logger.debug(`PersistentCallContainer: set call widget: ${callWidgetApiRef.current?.eventNames()} ${callSmallWidgetRef.current} ${callIframeRef.current?.baseURI}`) } }, [ theme, setupWidget, - primaryWidgetApiRef, - primarySmallWidgetRef, - primaryIframeRef, + callWidgetApiRef, + callSmallWidgetRef, + callIframeRef, registerActiveClientWidgetApi, activeCallRoomId, viewedCallRoomId, isActiveCallReady ]); - const memoizedIframeRef = useMemo(() => primaryIframeRef, [primaryIframeRef]); + const memoizedIframeRef = useMemo(() => callIframeRef, [callIframeRef]); return ( - +