From 9e1aab29734c31a0f00f963a8da02785a18379cf Mon Sep 17 00:00:00 2001 From: YoJames2019 Date: Mon, 9 Feb 2026 00:45:48 -0500 Subject: [PATCH] bump element call to 0.16.3, apply cinny theme to element call ui, replace element call lobby (backup iframe) with custom ui and only use element call for the in-call ui --- package-lock.json | 8 +- package.json | 3 +- src/app/features/call/CallView.css.ts | 37 +++ src/app/features/call/CallView.tsx | 122 +++++++-- src/app/features/call/CallViewUser.tsx | 69 +++++ src/app/features/call/SmallWidget.ts | 8 +- .../features/room-nav/RoomCallNavStatus.tsx | 4 +- src/app/features/room-nav/RoomNavItem.tsx | 6 +- src/app/features/room-nav/RoomNavUser.tsx | 4 +- src/app/pages/client/call/CallProvider.tsx | 256 +++--------------- .../client/call/PersistentCallContainer.tsx | 95 ++----- src/app/pages/client/space/Space.tsx | 6 +- 12 files changed, 302 insertions(+), 316 deletions(-) create mode 100644 src/app/features/call/CallView.css.ts create mode 100644 src/app/features/call/CallViewUser.tsx diff --git a/package-lock.json b/package-lock.json index 934bb070..8545a5ba 100644 --- a/package-lock.json +++ b/package-lock.json @@ -74,7 +74,7 @@ "ua-parser-js": "1.0.35" }, "devDependencies": { - "@element-hq/element-call-embedded": "0.12.2", + "@element-hq/element-call-embedded": "0.16.3", "@esbuild-plugins/node-globals-polyfill": "0.2.3", "@rollup/plugin-inject": "5.0.3", "@rollup/plugin-wasm": "6.1.1", @@ -1661,9 +1661,9 @@ } }, "node_modules/@element-hq/element-call-embedded": { - "version": "0.12.2", - "resolved": "https://registry.npmjs.org/@element-hq/element-call-embedded/-/element-call-embedded-0.12.2.tgz", - "integrity": "sha512-2u5/bOARcjc5TFq4929x1R0tvsNbeVA58FBtiW05GlIJCapxzPSOeeGhbqEcJ1TW3/hLGpiKMcw0QwRBQVNzQA==", + "version": "0.16.3", + "resolved": "https://registry.npmjs.org/@element-hq/element-call-embedded/-/element-call-embedded-0.16.3.tgz", + "integrity": "sha512-OViKJonDaDNVBUW9WdV9mk78/Ruh34C7XsEgt3O8D9z+64C39elbIgllHSoH5S12IRlv9RYrrV37FZLo6QWsDQ==", "dev": true }, "node_modules/@emotion/hash": { diff --git a/package.json b/package.json index 44385104..a7719566 100644 --- a/package.json +++ b/package.json @@ -86,7 +86,7 @@ "ua-parser-js": "1.0.35" }, "devDependencies": { - "@element-hq/element-call-embedded": "0.12.2", + "@element-hq/element-call-embedded": "0.16.3", "@esbuild-plugins/node-globals-polyfill": "0.2.3", "@rollup/plugin-inject": "5.0.3", "@rollup/plugin-wasm": "6.1.1", @@ -119,5 +119,4 @@ "vite-plugin-static-copy": "1.0.4", "vite-plugin-top-level-await": "1.4.4" } - } diff --git a/src/app/features/call/CallView.css.ts b/src/app/features/call/CallView.css.ts new file mode 100644 index 00000000..ba1146e2 --- /dev/null +++ b/src/app/features/call/CallView.css.ts @@ -0,0 +1,37 @@ +import { style } from '@vanilla-extract/css'; +import { DefaultReset, config } from 'folds'; +import { ContainerColor } from '../../styles/ContainerColor.css'; + +export const CallViewUserGrid = style({ + display: 'flex', + flexWrap: 'wrap', + justifyContent: 'center', + alignItems: 'center', + marginInline: "20px", + gap: config.space.S400, +}) + +export const CallViewUser = style([ + DefaultReset, + ContainerColor({ variant: 'SurfaceVariant' }), + { + height: "90px", + width: "150px", + borderRadius: config.radii.R500, + }, +]) + +export const UserLink = style({ + color: 'inherit', + minWidth: 0, + cursor: 'pointer', + flexGrow: 0, + transition: "all ease-out 200ms", + ':hover': { + transform: "translateY(-3px)", + textDecoration: 'unset', + }, + ':focus': { + outline: 'none', + }, +}); \ No newline at end of file diff --git a/src/app/features/call/CallView.tsx b/src/app/features/call/CallView.tsx index e5118024..3ea2396e 100644 --- a/src/app/features/call/CallView.tsx +++ b/src/app/features/call/CallView.tsx @@ -1,13 +1,21 @@ import { Room } from 'matrix-js-sdk'; -import React, { useContext, useMemo, useCallback, useEffect, useRef } from 'react'; -import { Box } from 'folds'; +import React, { useContext, useMemo, useCallback, useEffect, useRef, MouseEventHandler, useState, ReactNode } from 'react'; +import { Box, Button, Spinner, Text } from 'folds'; import { useCallState } from '../../pages/client/call/CallProvider'; +import { useCallMembers } from '../../hooks/useCallMemberships'; + import { PrimaryRefContext, - BackupRefContext, } from '../../pages/client/call/PersistentCallContainer'; import { ScreenSize, useScreenSizeContext } from '../../hooks/useScreenSize'; import { useDebounce } from '../../hooks/useDebounce'; +import { useMatrixClient } from '../../hooks/useMatrixClient'; +import { CallViewUser } from './CallViewUser'; +import { useRoomNavigate } from '../../hooks/useRoomNavigate'; +import { getMemberDisplayName } from '../../utils/room'; +import { getMxIdLocalPart } from '../../utils/matrix'; +import * as css from "./CallView.css" + type OriginalStyles = { position?: string; @@ -22,27 +30,49 @@ type OriginalStyles = { border?: string; }; +export function CallViewUserGrid({ children }: { children: ReactNode }) { + return ( + + {children} + + ); +} + + export function CallView({ room }: { room: Room }) { const primaryIframeRef = useContext(PrimaryRefContext); - const backupIframeRef = useContext(BackupRefContext); const iframeHostRef = useRef(null); const originalIframeStylesRef = useRef(null); - const { activeCallRoomId, isPrimaryIframe, isChatOpen } = useCallState(); - const isViewingActiveCall = useMemo( - () => activeCallRoomId !== null && activeCallRoomId === room.roomId, - [activeCallRoomId, room.roomId] - ); + const mx = useMatrixClient(); + + const [visibleCallNames, setVisibleCallNames] = useState("") + const { + isActiveCallReady, + activeCallRoomId, + isChatOpen, + setActiveCallRoomId, + hangUp, + setViewedCallRoomId + } = useCallState(); + + const isActiveCallRoom = activeCallRoomId === room.roomId + const shouldDisplayCall = isActiveCallRoom && isActiveCallReady; + const callMembers = useCallMembers(mx, room.roomId) + + const getName = (userId: string) => getMemberDisplayName(room, userId) ?? getMxIdLocalPart(userId); + + const memberDisplayNames = callMembers.map(callMembership => getName(callMembership.sender ?? '')) + + const { navigateRoom } = useRoomNavigate(); const screenSize = useScreenSizeContext(); + const isMobile = screenSize === ScreenSize.Mobile; + /* eslint-disable-next-line no-nested-ternary */ - const activeIframeDisplayRef = isPrimaryIframe - ? isViewingActiveCall - ? primaryIframeRef - : backupIframeRef - : isViewingActiveCall - ? backupIframeRef - : primaryIframeRef; + const activeIframeDisplayRef = primaryIframeRef const applyFixedPositioningToIframe = useCallback(() => { const iframeElement = activeIframeDisplayRef?.current; @@ -88,7 +118,7 @@ export function CallView({ room }: { room: Room }) { const iframeElement = activeIframeDisplayRef?.current; const hostElement = iframeHostRef?.current; - if (room.isCallRoom() || (isViewingActiveCall && iframeElement && hostElement)) { + if (room.isCallRoom() || (shouldDisplayCall && iframeElement && hostElement)) { applyFixedPositioningToIframe(); const resizeObserver = new ResizeObserver(debouncedApplyFixedPositioning); @@ -116,13 +146,36 @@ export function CallView({ room }: { room: Room }) { activeIframeDisplayRef, applyFixedPositioningToIframe, debouncedApplyFixedPositioning, - isPrimaryIframe, - isViewingActiveCall, + shouldDisplayCall, room, ]); + + const handleJoinVCClick: MouseEventHandler = (evt) => { + if (isMobile) { + evt.stopPropagation(); + setViewedCallRoomId(room.roomId); + navigateRoom(room.roomId); + } + if (!shouldDisplayCall) { + hangUp(room.roomId); + setActiveCallRoomId(room.roomId); + } + }; + const isCallViewVisible = room.isCallRoom() && (screenSize === ScreenSize.Desktop || !isChatOpen); + useEffect(() => { + if(memberDisplayNames.length <= 2){ + setVisibleCallNames(memberDisplayNames.join(" and ")) + } else { + const visible = memberDisplayNames.slice(0, 2); + const remaining = memberDisplayNames.length - 2; + + setVisibleCallNames(`${visible.join(", ")}, and ${remaining} other${remaining > 1 ? "s" : ""}`) + } + }, [memberDisplayNames]) + return (
+ + + {callMembers.map(callMember => ( + + )).slice(0, 6)} + + + + {room.name} + {visibleCallNames !== "" ? visibleCallNames : "No one"} {memberDisplayNames.length > 1 ? "are" : "is"} currently in voice + + + ); } diff --git a/src/app/features/call/CallViewUser.tsx b/src/app/features/call/CallViewUser.tsx new file mode 100644 index 00000000..8d55f77e --- /dev/null +++ b/src/app/features/call/CallViewUser.tsx @@ -0,0 +1,69 @@ +import { as, Avatar, Box, Icon, Icons, Text } from 'folds'; +import React from 'react'; +import classNames from 'classnames'; +import { Room } from 'matrix-js-sdk'; +import { CallMembership } from 'matrix-js-sdk/lib/matrixrtc/CallMembership'; +import { UserAvatar } from '../../components/user-avatar'; +import { useMatrixClient } from '../../hooks/useMatrixClient'; +import { getMxIdLocalPart } from '../../utils/matrix'; +import { getMemberAvatarMxc, getMemberDisplayName } from '../../utils/room'; +import { useMediaAuthentication } from '../../hooks/useMediaAuthentication'; +import { openProfileViewer } from '../../../client/action/navigation'; +import * as css from './CallView.css'; + +type CallViewUserProps = { + room: Room; + callMembership: CallMembership; +}; + + +export const UserProfileButton = as<'button'>( + ({ as: AsUserProfileButton = 'button', className, ...props }, ref) => ( + + ) +); + +export const CallViewUserBase = as<'div'>(({ className, ...props }, ref) => ( + +)); + +export function CallViewUser({ room, callMembership }: CallViewUserProps) { + const mx = useMatrixClient(); + const useAuthentication = useMediaAuthentication(); + const userId = callMembership.sender ?? ''; + const avatarMxcUrl = getMemberAvatarMxc(room, userId); + const avatarUrl = avatarMxcUrl + ? mx.mxcUrlToHttp(avatarMxcUrl, 32, 32, 'crop', undefined, false, useAuthentication) + : undefined; + const getName = getMemberDisplayName(room, userId) ?? getMxIdLocalPart(userId); + + const handleUserClick = () => { + openProfileViewer(userId, room.roomId); + }; + + return ( + + + + + } + /> + + + {getName} + + + + + ); +} diff --git a/src/app/features/call/SmallWidget.ts b/src/app/features/call/SmallWidget.ts index 2b24e978..fd5a1a4a 100644 --- a/src/app/features/call/SmallWidget.ts +++ b/src/app/features/call/SmallWidget.ts @@ -52,11 +52,12 @@ export const getWidgetUrl = ( embed: 'true', widgetId, appPrompt: 'false', - preload: 'false', - skipLobby: setParams.skipLobby ?? 'true', + skipLobby: setParams.skipLobby ?? 'true', // TODO: skipLobby is deprecated, use intent instead (intent doesn't produce the same effect?) returnToLobby: setParams.returnToLobby ?? 'true', perParticipantE2EE: setParams.perParticipantE2EE ?? 'true', - hideHeader: 'true', + header: 'none', + confineToRoom: 'true', + theme: setParams.theme ?? 'dark', userId: mx.getUserId()!, deviceId: mx.getDeviceId()!, roomId, @@ -137,6 +138,7 @@ export class SmallWidget extends EventEmitter { // Populate the map of "read up to" events for this widget with the current event in every room. // This is a bit inefficient, but should be okay. We do this for all rooms in case the widget // requests timeline capabilities in other rooms down the road. It's just easier to manage here. + // eslint-disable-next-line no-restricted-syntax for (const room of this.client.getRooms()) { // Timelines are most recent last const events = room.getLiveTimeline()?.getEvents() || []; diff --git a/src/app/features/room-nav/RoomCallNavStatus.tsx b/src/app/features/room-nav/RoomCallNavStatus.tsx index d3f23794..ce3657a7 100644 --- a/src/app/features/room-nav/RoomCallNavStatus.tsx +++ b/src/app/features/room-nav/RoomCallNavStatus.tsx @@ -9,7 +9,7 @@ export function CallNavStatus() { activeCallRoomId, isAudioEnabled, isVideoEnabled, - isCallActive, + isActiveCallReady, toggleAudio, toggleVideo, hangUp, @@ -21,7 +21,7 @@ export function CallNavStatus() { navigateRoom(activeCallRoomId); } }; - if (!isCallActive) { + if (!isActiveCallReady) { return null; } diff --git a/src/app/features/room-nav/RoomNavItem.tsx b/src/app/features/room-nav/RoomNavItem.tsx index b666c48b..ce9c0006 100644 --- a/src/app/features/room-nav/RoomNavItem.tsx +++ b/src/app/features/room-nav/RoomNavItem.tsx @@ -227,7 +227,7 @@ export function RoomNavItem({ const [menuAnchor, setMenuAnchor] = useState(); const unread = useRoomUnread(room.roomId, roomToUnreadAtom); const { - isCallActive, + isActiveCallReady, activeCallRoomId, setActiveCallRoomId, setViewedCallRoomId, @@ -238,7 +238,7 @@ export function RoomNavItem({ const typingMember = useRoomTypingMember(room.roomId).filter( (receipt) => receipt.userId !== mx.getUserId() ); - const isActiveCall = isCallActive && activeCallRoomId === room.roomId; + const isActiveCall = isActiveCallReady && activeCallRoomId === room.roomId; const callMemberships = useCallMembers(mx, room.roomId); const { navigateRoom } = useRoomNavigate(); const { roomIdOrAlias: viewedRoomId } = useParams(); @@ -269,7 +269,7 @@ export function RoomNavItem({ } if (room.isCallRoom()) { if (!isMobile) { - if (activeCallRoomId !== room.roomId) { + if (!isActiveCall) { if (mx.getRoom(viewedRoomId)?.isCallRoom()) { navigateRoom(room.roomId); } diff --git a/src/app/features/room-nav/RoomNavUser.tsx b/src/app/features/room-nav/RoomNavUser.tsx index 169ebde4..5a9fed5e 100644 --- a/src/app/features/room-nav/RoomNavUser.tsx +++ b/src/app/features/room-nav/RoomNavUser.tsx @@ -18,8 +18,8 @@ type RoomNavUserProps = { export function RoomNavUser({ room, callMembership }: RoomNavUserProps) { const mx = useMatrixClient(); const useAuthentication = useMediaAuthentication(); - const { isCallActive, activeCallRoomId } = useCallState(); - const isActiveCall = isCallActive && activeCallRoomId === room.roomId; + const { isActiveCallReady, activeCallRoomId } = useCallState(); + const isActiveCall = isActiveCallReady && activeCallRoomId === room.roomId; const userId = callMembership.sender ?? ''; const avatarMxcUrl = getMemberAvatarMxc(room, userId); const avatarUrl = avatarMxcUrl diff --git a/src/app/pages/client/call/CallProvider.tsx b/src/app/pages/client/call/CallProvider.tsx index 364670eb..c8022899 100644 --- a/src/app/pages/client/call/CallProvider.tsx +++ b/src/app/pages/client/call/CallProvider.tsx @@ -38,13 +38,6 @@ interface CallContextState { clientWidgetApi: ClientWidgetApi | null, clientWidget: SmallWidget ) => void; - viewedClientWidgetApi: ClientWidgetApi | null; - viewedClientWidget: SmallWidget | null; - registerViewedClientWidgetApi: ( - roomId: string | null, - clientWidgetApi: ClientWidgetApi | null, - clientWidget: SmallWidget - ) => void; sendWidgetAction: ( action: WidgetApiToWidgetAction | string, data: T @@ -52,12 +45,10 @@ interface CallContextState { isAudioEnabled: boolean; isVideoEnabled: boolean; isChatOpen: boolean; - isCallActive: boolean; - isPrimaryIframe: boolean; + isActiveCallReady: boolean; toggleAudio: () => Promise; toggleVideo: () => Promise; toggleChat: () => Promise; - toggleIframe: () => Promise; } const CallContext = createContext(undefined); @@ -66,11 +57,10 @@ interface CallProviderProps { children: ReactNode; } -const DEFAULT_AUDIO_ENABLED = true; +const DEFAULT_AUDIO_ENABLED = false; const DEFAULT_VIDEO_ENABLED = false; const DEFAULT_CHAT_OPENED = false; const DEFAULT_CALL_ACTIVE = false; -const DEFAULT_PRIMARY_IFRAME = true; export function CallProvider({ children }: CallProviderProps) { const [activeCallRoomId, setActiveCallRoomIdState] = useState(null); @@ -82,22 +72,13 @@ export function CallProvider({ children }: CallProviderProps) { const [activeClientWidgetApiRoomId, setActiveClientWidgetApiRoomId] = useState( null ); - const [viewedClientWidgetApi, setViewedClientWidgetApiState] = useState( - null - ); - const [viewedClientWidget, setViewedClientWidget] = useState(null); - const [viewedClientWidgetApiRoomId, setViewedClientWidgetApiRoomId] = useState( - null - ); const [isAudioEnabled, setIsAudioEnabledState] = useState(DEFAULT_AUDIO_ENABLED); const [isVideoEnabled, setIsVideoEnabledState] = useState(DEFAULT_VIDEO_ENABLED); const [isChatOpen, setIsChatOpenState] = useState(DEFAULT_CHAT_OPENED); - const [isCallActive, setIsCallActive] = useState(DEFAULT_CALL_ACTIVE); - const [isPrimaryIframe, setIsPrimaryIframe] = useState(DEFAULT_PRIMARY_IFRAME); + const [isActiveCallReady, setIsCallActive] = useState(DEFAULT_CALL_ACTIVE); const { roomIdOrAlias: viewedRoomId } = useParams<{ roomIdOrAlias: string }>(); - const [lastViewedRoomDuringCall, setLastViewedRoomDuringCall] = useState(null); const resetMediaState = useCallback(() => { logger.debug('CallContext: Resetting media state to defaults.'); @@ -115,19 +96,13 @@ export function CallProvider({ children }: CallProviderProps) { logger.debug(`CallContext: Active call room changed, resetting media state.`); resetMediaState(); } - - if (roomId === null || roomId !== activeClientWidgetApiRoomId) { - logger.warn( - `CallContext: Clearing active clientWidgetApi because active room changed to ${roomId} or was cleared.` - ); - } }, - [activeClientWidgetApiRoomId, resetMediaState, activeCallRoomId] + [resetMediaState, activeCallRoomId] ); const setViewedCallRoomId = useCallback( (roomId: string | null) => { - logger.warn(`CallContext: Setting activeCallRoomId to ${roomId}`); + logger.warn(`CallContext: Setting viewedCallRoomId to ${roomId}`); setViewedCallRoomIdState(roomId); }, [setViewedCallRoomIdState] @@ -167,72 +142,18 @@ export function CallProvider({ children }: CallProviderProps) { [activeClientWidgetApi, activeClientWidgetApiRoomId, setActiveClientWidgetApi, resetMediaState] ); - const setViewedClientWidgetApi = useCallback( - ( - clientWidgetApi: ClientWidgetApi | null, - clientWidget: SmallWidget | null, - roomId: string | null - ) => { - setViewedClientWidgetApiState(clientWidgetApi); - setViewedClientWidget(clientWidget); - setViewedClientWidgetApiRoomId(roomId); - }, - [] - ); - - const registerViewedClientWidgetApi = useCallback( - ( - roomId: string | null, - clientWidgetApi: ClientWidgetApi | null, - clientWidget: SmallWidget | null - ) => { - if (viewedClientWidgetApi && viewedClientWidgetApi !== clientWidgetApi) { - logger.debug(`CallContext: Cleaning up listeners for previous clientWidgetApi instance.`); - } - - if (roomId && clientWidgetApi) { - logger.debug(`CallContext: Registering viewed clientWidgetApi for room ${roomId}.`); - setViewedClientWidgetApi(clientWidgetApi, clientWidget, roomId); - } else if (roomId === viewedClientWidgetApiRoomId || roomId === null) { - logger.debug( - `CallContext: Clearing viewed clientWidgetApi for room ${viewedClientWidgetApiRoomId}.` - ); - setViewedClientWidgetApi(null, null, null); - } - }, - [viewedClientWidgetApi, viewedClientWidgetApiRoomId, setViewedClientWidgetApi] - ); - const hangUp = useCallback( - (nextRoom: string) => { - if (typeof nextRoom === 'string') { - logger.debug('1 Hangup'); - setActiveClientWidgetApi(null, null, null); - setActiveCallRoomIdState(null); - activeClientWidgetApi?.transport.send(`${WIDGET_HANGUP_ACTION}`, {}); - } else if (viewedRoomId !== activeCallRoomId) { - logger.debug('2 Hangup'); - setActiveClientWidgetApi(null, null, null); - setActiveCallRoomIdState(null); - activeClientWidgetApi?.transport.send(`${WIDGET_HANGUP_ACTION}`, {}); - } else if (activeClientWidget) { - logger.debug('3 Hangup'); - const iframeDoc = - activeClientWidget?.iframe?.contentDocument || - activeClientWidget?.iframe?.contentWindow.document; - const button = iframeDoc.querySelector('[data-testid="incall_leave"]'); - button.click(); - } + () => { + setActiveClientWidgetApi(null, null, null); + setActiveCallRoomIdState(null); + activeClientWidgetApi?.transport.send(`${WIDGET_HANGUP_ACTION}`, {}); setIsCallActive(false); logger.debug(`CallContext: Hang up called.`); }, [ - activeCallRoomId, - activeClientWidget, activeClientWidgetApi?.transport, setActiveClientWidgetApi, - viewedRoomId, ] ); @@ -240,22 +161,15 @@ export function CallProvider({ children }: CallProviderProps) { if (!activeCallRoomId && !viewedCallRoomId) { return; } - if (!lastViewedRoomDuringCall) { - if (activeCallRoomId) - setLastViewedRoomDuringCall((prevLastRoom) => prevLastRoom || activeCallRoomId); - } - if ( - lastViewedRoomDuringCall && - lastViewedRoomDuringCall !== viewedRoomId && - activeCallRoomId && - isCallActive - ) { - setLastViewedRoomDuringCall(activeCallRoomId); + + const api = activeClientWidgetApi; + if (!api) { + return; } const handleHangup = (ev: CustomEvent) => { ev.preventDefault(); - if (isCallActive && ev.detail.widgetId === activeClientWidgetApi?.widget.id) { + if (isActiveCallReady && ev.detail.widgetId === activeClientWidgetApi?.widget.id) { activeClientWidgetApi?.transport.reply(ev.detail, {}); } logger.debug( @@ -287,107 +201,46 @@ export function CallProvider({ children }: CallProviderProps) { const handleOnScreenStateUpdate = (ev: CustomEvent) => { ev.preventDefault(); - if (isPrimaryIframe) { - activeClientWidgetApi?.transport.reply(ev.detail, {}); - } else { - viewedClientWidgetApi?.transport.reply(ev.detail, {}); - } + api.transport.reply(ev.detail, {}); }; const handleOnTileLayout = (ev: CustomEvent) => { ev.preventDefault(); - if (isPrimaryIframe) { - activeClientWidgetApi?.transport.reply(ev.detail, {}); - } else { - viewedClientWidgetApi?.transport.reply(ev.detail, {}); - } + + api.transport.reply(ev.detail, {}); }; const handleJoin = (ev: CustomEvent) => { ev.preventDefault(); - const setViewedAsActive = () => { - if (viewedCallRoomId !== activeCallRoomId) setIsPrimaryIframe(!isPrimaryIframe); - setActiveClientWidgetApi(viewedClientWidgetApi, viewedClientWidget, viewedCallRoomId); - setActiveCallRoomIdState(viewedCallRoomId); - setIsCallActive(true); - const iframeDoc = - viewedClientWidget?.iframe?.contentDocument || - viewedClientWidget?.iframe?.contentWindow.document; - const observer = new MutationObserver(() => { - const button = iframeDoc.querySelector('[data-testid="incall_leave"]'); - if (button) { - button.addEventListener('click', () => { - setIsCallActive(false); - }); - } - observer.disconnect(); - }); - observer.observe(iframeDoc, { childList: true, subtree: true }); - }; - if (ev.detail.widgetId === activeClientWidgetApi?.widget.id) { - activeClientWidgetApi?.transport.reply(ev.detail, {}); - const iframeDoc = - activeClientWidget?.iframe?.contentDocument || - activeClientWidget?.iframe?.contentWindow.document; - const observer = new MutationObserver(() => { - const button = iframeDoc.querySelector('[data-testid="incall_leave"]'); - if (button) { - button.addEventListener('click', () => { - setIsCallActive(false); - }); - } - observer.disconnect(); - }); - logger.debug('1 Join'); - observer.observe(iframeDoc, { childList: true, subtree: true }); - setIsCallActive(true); - return; - } - if ( - lastViewedRoomDuringCall && - viewedRoomId === activeCallRoomId && - lastViewedRoomDuringCall === activeCallRoomId - ) { - logger.debug('2 Join'); - setIsCallActive(true); - return; - } - if (activeClientWidgetApi) { - if (isCallActive && viewedClientWidgetApi && viewedCallRoomId) { - activeClientWidgetApi?.removeAllListeners(); - activeClientWidgetApi?.transport.send(WIDGET_HANGUP_ACTION, {}).then(() => { - logger.debug('3 Join'); - setViewedAsActive(); + api.transport.reply(ev.detail, {}); + const iframeDoc = + api.iframe?.contentDocument || + api.iframe?.contentWindow.document; + const observer = new MutationObserver(() => { + const button = iframeDoc.querySelector('[data-testid="incall_leave"]'); + if (button) { + button.addEventListener('click', () => { + hangUp() }); - } else { - logger.debug('4 Join'); - setViewedAsActive(); - setIsCallActive(true); } - } else if (viewedCallRoomId !== viewedRoomId) { - logger.debug('5 Join'); - setIsCallActive(true); - } else { - logger.debug('6 Join'); - setViewedAsActive(); - } + observer.disconnect(); + }); + logger.debug('Join Call'); + observer.observe(iframeDoc, { childList: true, subtree: true }); + setIsCallActive(true); }; - + logger.debug( `CallContext: Setting up listeners for clientWidgetApi in room ${activeCallRoomId}` ); - activeClientWidgetApi?.on(`action:${WIDGET_HANGUP_ACTION}`, handleHangup); - activeClientWidgetApi?.on(`action:${WIDGET_MEDIA_STATE_UPDATE_ACTION}`, handleMediaStateUpdate); - viewedClientWidgetApi?.on(`action:${WIDGET_TILE_UPDATE}`, handleOnTileLayout); - activeClientWidgetApi?.on(`action:${WIDGET_ON_SCREEN_ACTION}`, handleOnScreenStateUpdate); - activeClientWidgetApi?.on(`action:${WIDGET_JOIN_ACTION}`, handleJoin); - viewedClientWidgetApi?.on(`action:${WIDGET_JOIN_ACTION}`, handleJoin); - viewedClientWidgetApi?.on(`action:${WIDGET_MEDIA_STATE_UPDATE_ACTION}`, handleMediaStateUpdate); - viewedClientWidgetApi?.on(`action:${WIDGET_TILE_UPDATE}`, handleOnTileLayout); - viewedClientWidgetApi?.on(`action:${WIDGET_ON_SCREEN_ACTION}`, handleOnScreenStateUpdate); - viewedClientWidgetApi?.on(`action:${WIDGET_HANGUP_ACTION}`, handleHangup); + api.on(`action:${WIDGET_HANGUP_ACTION}`, handleHangup); + api.on(`action:${WIDGET_MEDIA_STATE_UPDATE_ACTION}`, handleMediaStateUpdate); + api.on(`action:${WIDGET_TILE_UPDATE}`, handleOnTileLayout); + api.on(`action:${WIDGET_ON_SCREEN_ACTION}`, handleOnScreenStateUpdate); + api.on(`action:${WIDGET_JOIN_ACTION}`, handleJoin); + }, [ activeClientWidgetApi, activeCallRoomId, @@ -396,16 +249,10 @@ export function CallProvider({ children }: CallProviderProps) { isChatOpen, isAudioEnabled, isVideoEnabled, - isCallActive, + isActiveCallReady, viewedRoomId, - viewedClientWidgetApi, - isPrimaryIframe, viewedCallRoomId, - setViewedClientWidgetApi, - setActiveClientWidgetApi, - viewedClientWidget, setViewedCallRoomId, - lastViewedRoomDuringCall, activeClientWidget?.iframe?.contentDocument, activeClientWidget?.iframe?.contentWindow?.document, ]); @@ -424,6 +271,8 @@ export function CallProvider({ children }: CallProviderProps) { ); return Promise.reject(new Error('Mismatched active call clientWidgetApi')); } + + logger.debug( `CallContext: Sending action '${action}' via active clientWidgetApi (room: ${activeClientWidgetApiRoomId}) with data:`, data @@ -470,11 +319,6 @@ export function CallProvider({ children }: CallProviderProps) { setIsChatOpenState(newState); }, [isChatOpen]); - const toggleIframe = useCallback(async () => { - const newState = !isPrimaryIframe; - setIsPrimaryIframe(newState); - }, [isPrimaryIframe]); - const contextValue = useMemo( () => ({ activeCallRoomId, @@ -485,19 +329,14 @@ export function CallProvider({ children }: CallProviderProps) { activeClientWidgetApi, registerActiveClientWidgetApi, activeClientWidget, - viewedClientWidgetApi, - registerViewedClientWidgetApi, - viewedClientWidget, sendWidgetAction, isChatOpen, isAudioEnabled, isVideoEnabled, - isCallActive, - isPrimaryIframe, + isActiveCallReady, toggleAudio, toggleVideo, - toggleChat, - toggleIframe, + toggleChat }), [ activeCallRoomId, @@ -508,19 +347,14 @@ export function CallProvider({ children }: CallProviderProps) { activeClientWidgetApi, registerActiveClientWidgetApi, activeClientWidget, - viewedClientWidgetApi, - registerViewedClientWidgetApi, - viewedClientWidget, sendWidgetAction, isChatOpen, isAudioEnabled, isVideoEnabled, - isCallActive, - isPrimaryIframe, + isActiveCallReady, toggleAudio, toggleVideo, - toggleChat, - toggleIframe, + toggleChat ] ); diff --git a/src/app/pages/client/call/PersistentCallContainer.tsx b/src/app/pages/client/call/PersistentCallContainer.tsx index bf35b45e..eb8b3ff6 100644 --- a/src/app/pages/client/call/PersistentCallContainer.tsx +++ b/src/app/pages/client/call/PersistentCallContainer.tsx @@ -1,4 +1,4 @@ -import React, { createContext, ReactNode, useCallback, useEffect, useMemo, useRef } from 'react'; +import React, { createContext, ReactNode, useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { logger } from 'matrix-js-sdk/lib/logger'; import { ClientWidgetApi } from 'matrix-widget-api'; import { Box } from 'folds'; @@ -13,42 +13,38 @@ import { import { useMatrixClient } from '../../../hooks/useMatrixClient'; import { useClientConfig } from '../../../hooks/useClientConfig'; import { ScreenSize, useScreenSizeContext } from '../../../hooks/useScreenSize'; +import { ThemeKind, useTheme } from '../../../hooks/useTheme'; interface PersistentCallContainerProps { children: ReactNode; } export const PrimaryRefContext = createContext(null); -export const BackupRefContext = createContext(null); export function PersistentCallContainer({ children }: PersistentCallContainerProps) { const primaryIframeRef = useRef(null); const primaryWidgetApiRef = useRef(null); const primarySmallWidgetRef = useRef(null); - const backupIframeRef = useRef(null); - const backupWidgetApiRef = useRef(null); - const backupSmallWidgetRef = useRef(null); const { activeCallRoomId, viewedCallRoomId, isChatOpen, - isCallActive, - isPrimaryIframe, + isActiveCallReady, registerActiveClientWidgetApi, activeClientWidget, - registerViewedClientWidgetApi, - viewedClientWidget, } = useCallState(); const mx = useMatrixClient(); const clientConfig = useClientConfig(); 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 */ const setupWidget = useCallback( @@ -56,13 +52,16 @@ export function PersistentCallContainer({ children }: PersistentCallContainerPro widgetApiRef: { current: ClientWidgetApi }, smallWidgetRef: { current: SmallWidget }, iframeRef: { current: { src: string } }, - skipLobby: { toString: () => any } + skipLobby: { toString: () => any }, + themeKind: ThemeKind | null ) => { if (mx?.getUserId()) { + logger.debug(`CallContextJ: iframe src - ${iframeRef.current.src}`) + logger.debug(`CallContextJ: activeCallRoomId: ${activeCallRoomId} viewedId: ${viewedCallRoomId} isactive: ${isActiveCallReady}`) if ( - (activeCallRoomId !== viewedCallRoomId && isCallActive) || - (activeCallRoomId && !isCallActive) || - (!activeCallRoomId && viewedCallRoomId && !isCallActive) + (activeCallRoomId !== viewedCallRoomId && isActiveCallReady) || + (activeCallRoomId && !isActiveCallReady) || + (!activeCallRoomId && viewedCallRoomId && !isActiveCallReady) ) { const roomIdToSet = (skipLobby ? activeCallRoomId : viewedCallRoomId) ?? ''; if (roomIdToSet === '') { @@ -78,27 +77,18 @@ export function PersistentCallContainer({ children }: PersistentCallContainerPro skipLobby: skipLobby.toString(), returnToLobby: 'true', perParticipantE2EE: 'true', + theme: themeKind } ); if ( - (primarySmallWidgetRef.current?.roomId || backupSmallWidgetRef.current?.roomId) && - (skipLobby - ? activeClientWidget?.roomId && - //activeCallRoomId === activeClientWidget.roomId && - (activeClientWidget.roomId === primarySmallWidgetRef.current?.roomId || - activeClientWidget.roomId === backupSmallWidgetRef.current?.roomId) - : viewedClientWidget?.roomId && - viewedCallRoomId === viewedClientWidget.roomId && - (viewedClientWidget.roomId === primarySmallWidgetRef.current?.roomId || - viewedClientWidget.roomId === backupSmallWidgetRef.current?.roomId)) + primarySmallWidgetRef.current?.roomId && + (activeClientWidget?.roomId && activeClientWidget.roomId === primarySmallWidgetRef.current?.roomId) ) { return; } - if (iframeRef.current && iframeRef.current.src !== newUrl.toString()) { - iframeRef.current.src = newUrl.toString(); - } else if (iframeRef.current && !iframeRef.current.src) { + if (iframeRef.current && (!iframeRef.current.src || iframeRef.current.src !== newUrl.toString())) { iframeRef.current.src = newUrl.toString(); } @@ -125,12 +115,8 @@ export function PersistentCallContainer({ children }: PersistentCallContainerPro const widgetApiInstance = smallWidget.startMessaging(iframeElement); widgetApiRef.current = widgetApiInstance; - if (skipLobby) { - registerActiveClientWidgetApi(activeCallRoomId, widgetApiRef.current, smallWidget); - } else { - registerViewedClientWidgetApi(viewedCallRoomId, widgetApiRef.current, smallWidget); - } - + registerActiveClientWidgetApi(roomIdToSet, widgetApiRef.current, smallWidget); + widgetApiInstance.once('ready', () => { logger.info(`PersistentCallContainer: Widget for ${roomIdToSet} is ready.`); }); @@ -141,43 +127,37 @@ export function PersistentCallContainer({ children }: PersistentCallContainerPro mx, activeCallRoomId, viewedCallRoomId, - isCallActive, + isActiveCallReady, clientConfig.elementCallUrl, - viewedClientWidget, activeClientWidget, - viewedRoomId, registerActiveClientWidgetApi, - registerViewedClientWidgetApi, ] ); useEffect(() => { - if ((activeCallRoomId && !viewedCallRoomId) || (activeCallRoomId && viewedCallRoomId)) - setupWidget(primaryWidgetApiRef, primarySmallWidgetRef, primaryIframeRef, isPrimaryIframe); - if ((!activeCallRoomId && viewedCallRoomId) || (viewedCallRoomId && activeCallRoomId)) - setupWidget(backupWidgetApiRef, backupSmallWidgetRef, backupIframeRef, !isPrimaryIframe); + 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}`) + } }, [ + theme, setupWidget, primaryWidgetApiRef, primarySmallWidgetRef, primaryIframeRef, - backupWidgetApiRef, - backupSmallWidgetRef, - backupIframeRef, registerActiveClientWidgetApi, - registerViewedClientWidgetApi, activeCallRoomId, viewedCallRoomId, - isCallActive, - isPrimaryIframe, + isActiveCallReady ]); const memoizedIframeRef = useMemo(() => primaryIframeRef, [primaryIframeRef]); - const memoizedBackupIframeRef = useMemo(() => backupIframeRef, [backupIframeRef]); return ( - -