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

This commit is contained in:
YoJames2019
2026-02-09 00:45:48 -05:00
parent 7bca8fb911
commit 9e1aab2973
12 changed files with 302 additions and 316 deletions

8
package-lock.json generated
View File

@@ -74,7 +74,7 @@
"ua-parser-js": "1.0.35" "ua-parser-js": "1.0.35"
}, },
"devDependencies": { "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", "@esbuild-plugins/node-globals-polyfill": "0.2.3",
"@rollup/plugin-inject": "5.0.3", "@rollup/plugin-inject": "5.0.3",
"@rollup/plugin-wasm": "6.1.1", "@rollup/plugin-wasm": "6.1.1",
@@ -1661,9 +1661,9 @@
} }
}, },
"node_modules/@element-hq/element-call-embedded": { "node_modules/@element-hq/element-call-embedded": {
"version": "0.12.2", "version": "0.16.3",
"resolved": "https://registry.npmjs.org/@element-hq/element-call-embedded/-/element-call-embedded-0.12.2.tgz", "resolved": "https://registry.npmjs.org/@element-hq/element-call-embedded/-/element-call-embedded-0.16.3.tgz",
"integrity": "sha512-2u5/bOARcjc5TFq4929x1R0tvsNbeVA58FBtiW05GlIJCapxzPSOeeGhbqEcJ1TW3/hLGpiKMcw0QwRBQVNzQA==", "integrity": "sha512-OViKJonDaDNVBUW9WdV9mk78/Ruh34C7XsEgt3O8D9z+64C39elbIgllHSoH5S12IRlv9RYrrV37FZLo6QWsDQ==",
"dev": true "dev": true
}, },
"node_modules/@emotion/hash": { "node_modules/@emotion/hash": {

View File

@@ -86,7 +86,7 @@
"ua-parser-js": "1.0.35" "ua-parser-js": "1.0.35"
}, },
"devDependencies": { "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", "@esbuild-plugins/node-globals-polyfill": "0.2.3",
"@rollup/plugin-inject": "5.0.3", "@rollup/plugin-inject": "5.0.3",
"@rollup/plugin-wasm": "6.1.1", "@rollup/plugin-wasm": "6.1.1",
@@ -119,5 +119,4 @@
"vite-plugin-static-copy": "1.0.4", "vite-plugin-static-copy": "1.0.4",
"vite-plugin-top-level-await": "1.4.4" "vite-plugin-top-level-await": "1.4.4"
} }
} }

View File

@@ -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',
},
});

View File

@@ -1,13 +1,21 @@
import { Room } from 'matrix-js-sdk'; import { Room } from 'matrix-js-sdk';
import React, { useContext, useMemo, useCallback, useEffect, useRef } from 'react'; import React, { useContext, useMemo, useCallback, useEffect, useRef, MouseEventHandler, useState, ReactNode } from 'react';
import { Box } from 'folds'; import { Box, Button, Spinner, Text } from 'folds';
import { useCallState } from '../../pages/client/call/CallProvider'; import { useCallState } from '../../pages/client/call/CallProvider';
import { useCallMembers } from '../../hooks/useCallMemberships';
import { import {
PrimaryRefContext, PrimaryRefContext,
BackupRefContext,
} from '../../pages/client/call/PersistentCallContainer'; } from '../../pages/client/call/PersistentCallContainer';
import { ScreenSize, useScreenSizeContext } from '../../hooks/useScreenSize'; import { ScreenSize, useScreenSizeContext } from '../../hooks/useScreenSize';
import { useDebounce } from '../../hooks/useDebounce'; 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 = { type OriginalStyles = {
position?: string; position?: string;
@@ -22,27 +30,49 @@ type OriginalStyles = {
border?: string; border?: string;
}; };
export function CallViewUserGrid({ children }: { children: ReactNode }) {
return (
<Box className={css.CallViewUserGrid} style={{
maxWidth: React.Children.count(children) === 4 ? "336px" : "503px"
}}>
{children}
</Box>
);
}
export function CallView({ room }: { room: Room }) { export function CallView({ room }: { room: Room }) {
const primaryIframeRef = useContext(PrimaryRefContext); const primaryIframeRef = useContext(PrimaryRefContext);
const backupIframeRef = useContext(BackupRefContext);
const iframeHostRef = useRef<HTMLDivElement>(null); const iframeHostRef = useRef<HTMLDivElement>(null);
const originalIframeStylesRef = useRef<OriginalStyles | null>(null); const originalIframeStylesRef = useRef<OriginalStyles | null>(null);
const { activeCallRoomId, isPrimaryIframe, isChatOpen } = useCallState(); const mx = useMatrixClient();
const isViewingActiveCall = useMemo(
() => activeCallRoomId !== null && activeCallRoomId === room.roomId, const [visibleCallNames, setVisibleCallNames] = useState("")
[activeCallRoomId, room.roomId]
);
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 screenSize = useScreenSizeContext();
const isMobile = screenSize === ScreenSize.Mobile;
/* eslint-disable-next-line no-nested-ternary */ /* eslint-disable-next-line no-nested-ternary */
const activeIframeDisplayRef = isPrimaryIframe const activeIframeDisplayRef = primaryIframeRef
? isViewingActiveCall
? primaryIframeRef
: backupIframeRef
: isViewingActiveCall
? backupIframeRef
: primaryIframeRef;
const applyFixedPositioningToIframe = useCallback(() => { const applyFixedPositioningToIframe = useCallback(() => {
const iframeElement = activeIframeDisplayRef?.current; const iframeElement = activeIframeDisplayRef?.current;
@@ -88,7 +118,7 @@ export function CallView({ room }: { room: Room }) {
const iframeElement = activeIframeDisplayRef?.current; const iframeElement = activeIframeDisplayRef?.current;
const hostElement = iframeHostRef?.current; const hostElement = iframeHostRef?.current;
if (room.isCallRoom() || (isViewingActiveCall && iframeElement && hostElement)) { if (room.isCallRoom() || (shouldDisplayCall && iframeElement && hostElement)) {
applyFixedPositioningToIframe(); applyFixedPositioningToIframe();
const resizeObserver = new ResizeObserver(debouncedApplyFixedPositioning); const resizeObserver = new ResizeObserver(debouncedApplyFixedPositioning);
@@ -116,13 +146,36 @@ export function CallView({ room }: { room: Room }) {
activeIframeDisplayRef, activeIframeDisplayRef,
applyFixedPositioningToIframe, applyFixedPositioningToIframe,
debouncedApplyFixedPositioning, debouncedApplyFixedPositioning,
isPrimaryIframe, shouldDisplayCall,
isViewingActiveCall,
room, room,
]); ]);
const handleJoinVCClick: MouseEventHandler<HTMLElement> = (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); 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 ( return (
<Box grow="Yes" direction="Column" style={{ display: isCallViewVisible ? 'flex' : 'none' }}> <Box grow="Yes" direction="Column" style={{ display: isCallViewVisible ? 'flex' : 'none' }}>
<div <div
@@ -132,9 +185,38 @@ export function CallView({ room }: { room: Room }) {
height: '100%', height: '100%',
position: 'relative', position: 'relative',
pointerEvents: 'none', pointerEvents: 'none',
display: 'flex', display: shouldDisplayCall ? 'flex' : 'none',
}} }}
/> />
<Box grow='Yes' justifyContent='Center' alignItems='Center' direction="Column" gap="300" style={{
display: shouldDisplayCall ? 'none' : 'flex',
}}>
<CallViewUserGrid>
{callMembers.map(callMember => (
<CallViewUser room={room} callMembership={callMember}/>
)).slice(0, 6)}
</CallViewUserGrid>
<Box direction="Column" alignItems="Center" style={{
paddingTop: "20px",
paddingBottom: "10px"
}}>
<Text size="H1" style={{
paddingBottom: "5px"
}}>{room.name}</Text>
<Text>{visibleCallNames !== "" ? visibleCallNames : "No one"} {memberDisplayNames.length > 1 ? "are" : "is"} currently in voice</Text>
</Box>
<Button variant='Secondary' disabled={isActiveCallRoom} onClick={handleJoinVCClick}>
{isActiveCallRoom ? (
<Box justifyContent='Center' alignItems='Center' gap='200'>
<Spinner />
<Text size="B500">{activeCallRoomId === room.roomId ? `Joining` : "Join Voice"}</Text>
</Box>
) : (
<Text size="B500">Join Voice</Text>
)}
</Button>
</Box>
</Box> </Box>
); );
} }

View File

@@ -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) => (
<AsUserProfileButton className={classNames(css.UserLink, className)} {...props} ref={ref} />
)
);
export const CallViewUserBase = as<'div'>(({ className, ...props }, ref) => (
<Box
direction="Column"
gap="300"
className={classNames(css.CallViewUser, className)}
{...props}
ref={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 (
<UserProfileButton onClick={handleUserClick} aria-label={getName}>
<CallViewUserBase>
<Box direction="Column" grow="Yes" alignItems="Center" gap="200" justifyContent="Center">
<Avatar size="200">
<UserAvatar
userId={userId}
src={avatarUrl ?? undefined}
alt={getName}
renderFallback={() => <Icon size="50" src={Icons.User} filled />}
/>
</Avatar>
<Text size="B400" priority="300" truncate>
{getName}
</Text>
</Box>
</CallViewUserBase>
</UserProfileButton>
);
}

View File

@@ -52,11 +52,12 @@ export const getWidgetUrl = (
embed: 'true', embed: 'true',
widgetId, widgetId,
appPrompt: 'false', appPrompt: 'false',
preload: 'false', skipLobby: setParams.skipLobby ?? 'true', // TODO: skipLobby is deprecated, use intent instead (intent doesn't produce the same effect?)
skipLobby: setParams.skipLobby ?? 'true',
returnToLobby: setParams.returnToLobby ?? 'true', returnToLobby: setParams.returnToLobby ?? 'true',
perParticipantE2EE: setParams.perParticipantE2EE ?? 'true', perParticipantE2EE: setParams.perParticipantE2EE ?? 'true',
hideHeader: 'true', header: 'none',
confineToRoom: 'true',
theme: setParams.theme ?? 'dark',
userId: mx.getUserId()!, userId: mx.getUserId()!,
deviceId: mx.getDeviceId()!, deviceId: mx.getDeviceId()!,
roomId, 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. // 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 // 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. // 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()) { for (const room of this.client.getRooms()) {
// Timelines are most recent last // Timelines are most recent last
const events = room.getLiveTimeline()?.getEvents() || []; const events = room.getLiveTimeline()?.getEvents() || [];

View File

@@ -9,7 +9,7 @@ export function CallNavStatus() {
activeCallRoomId, activeCallRoomId,
isAudioEnabled, isAudioEnabled,
isVideoEnabled, isVideoEnabled,
isCallActive, isActiveCallReady,
toggleAudio, toggleAudio,
toggleVideo, toggleVideo,
hangUp, hangUp,
@@ -21,7 +21,7 @@ export function CallNavStatus() {
navigateRoom(activeCallRoomId); navigateRoom(activeCallRoomId);
} }
}; };
if (!isCallActive) { if (!isActiveCallReady) {
return null; return null;
} }

View File

@@ -227,7 +227,7 @@ export function RoomNavItem({
const [menuAnchor, setMenuAnchor] = useState<RectCords>(); const [menuAnchor, setMenuAnchor] = useState<RectCords>();
const unread = useRoomUnread(room.roomId, roomToUnreadAtom); const unread = useRoomUnread(room.roomId, roomToUnreadAtom);
const { const {
isCallActive, isActiveCallReady,
activeCallRoomId, activeCallRoomId,
setActiveCallRoomId, setActiveCallRoomId,
setViewedCallRoomId, setViewedCallRoomId,
@@ -238,7 +238,7 @@ export function RoomNavItem({
const typingMember = useRoomTypingMember(room.roomId).filter( const typingMember = useRoomTypingMember(room.roomId).filter(
(receipt) => receipt.userId !== mx.getUserId() (receipt) => receipt.userId !== mx.getUserId()
); );
const isActiveCall = isCallActive && activeCallRoomId === room.roomId; const isActiveCall = isActiveCallReady && activeCallRoomId === room.roomId;
const callMemberships = useCallMembers(mx, room.roomId); const callMemberships = useCallMembers(mx, room.roomId);
const { navigateRoom } = useRoomNavigate(); const { navigateRoom } = useRoomNavigate();
const { roomIdOrAlias: viewedRoomId } = useParams(); const { roomIdOrAlias: viewedRoomId } = useParams();
@@ -269,7 +269,7 @@ export function RoomNavItem({
} }
if (room.isCallRoom()) { if (room.isCallRoom()) {
if (!isMobile) { if (!isMobile) {
if (activeCallRoomId !== room.roomId) { if (!isActiveCall) {
if (mx.getRoom(viewedRoomId)?.isCallRoom()) { if (mx.getRoom(viewedRoomId)?.isCallRoom()) {
navigateRoom(room.roomId); navigateRoom(room.roomId);
} }

View File

@@ -18,8 +18,8 @@ type RoomNavUserProps = {
export function RoomNavUser({ room, callMembership }: RoomNavUserProps) { export function RoomNavUser({ room, callMembership }: RoomNavUserProps) {
const mx = useMatrixClient(); const mx = useMatrixClient();
const useAuthentication = useMediaAuthentication(); const useAuthentication = useMediaAuthentication();
const { isCallActive, activeCallRoomId } = useCallState(); const { isActiveCallReady, activeCallRoomId } = useCallState();
const isActiveCall = isCallActive && activeCallRoomId === room.roomId; const isActiveCall = isActiveCallReady && activeCallRoomId === room.roomId;
const userId = callMembership.sender ?? ''; const userId = callMembership.sender ?? '';
const avatarMxcUrl = getMemberAvatarMxc(room, userId); const avatarMxcUrl = getMemberAvatarMxc(room, userId);
const avatarUrl = avatarMxcUrl const avatarUrl = avatarMxcUrl

View File

@@ -38,13 +38,6 @@ interface CallContextState {
clientWidgetApi: ClientWidgetApi | null, clientWidgetApi: ClientWidgetApi | null,
clientWidget: SmallWidget clientWidget: SmallWidget
) => void; ) => void;
viewedClientWidgetApi: ClientWidgetApi | null;
viewedClientWidget: SmallWidget | null;
registerViewedClientWidgetApi: (
roomId: string | null,
clientWidgetApi: ClientWidgetApi | null,
clientWidget: SmallWidget
) => void;
sendWidgetAction: <T = unknown>( sendWidgetAction: <T = unknown>(
action: WidgetApiToWidgetAction | string, action: WidgetApiToWidgetAction | string,
data: T data: T
@@ -52,12 +45,10 @@ interface CallContextState {
isAudioEnabled: boolean; isAudioEnabled: boolean;
isVideoEnabled: boolean; isVideoEnabled: boolean;
isChatOpen: boolean; isChatOpen: boolean;
isCallActive: boolean; isActiveCallReady: boolean;
isPrimaryIframe: boolean;
toggleAudio: () => Promise<void>; toggleAudio: () => Promise<void>;
toggleVideo: () => Promise<void>; toggleVideo: () => Promise<void>;
toggleChat: () => Promise<void>; toggleChat: () => Promise<void>;
toggleIframe: () => Promise<void>;
} }
const CallContext = createContext<CallContextState | undefined>(undefined); const CallContext = createContext<CallContextState | undefined>(undefined);
@@ -66,11 +57,10 @@ interface CallProviderProps {
children: ReactNode; children: ReactNode;
} }
const DEFAULT_AUDIO_ENABLED = true; const DEFAULT_AUDIO_ENABLED = false;
const DEFAULT_VIDEO_ENABLED = false; const DEFAULT_VIDEO_ENABLED = false;
const DEFAULT_CHAT_OPENED = false; const DEFAULT_CHAT_OPENED = false;
const DEFAULT_CALL_ACTIVE = false; const DEFAULT_CALL_ACTIVE = false;
const DEFAULT_PRIMARY_IFRAME = true;
export function CallProvider({ children }: CallProviderProps) { export function CallProvider({ children }: CallProviderProps) {
const [activeCallRoomId, setActiveCallRoomIdState] = useState<string | null>(null); const [activeCallRoomId, setActiveCallRoomIdState] = useState<string | null>(null);
@@ -82,22 +72,13 @@ export function CallProvider({ children }: CallProviderProps) {
const [activeClientWidgetApiRoomId, setActiveClientWidgetApiRoomId] = useState<string | null>( const [activeClientWidgetApiRoomId, setActiveClientWidgetApiRoomId] = useState<string | null>(
null null
); );
const [viewedClientWidgetApi, setViewedClientWidgetApiState] = useState<ClientWidgetApi | null>(
null
);
const [viewedClientWidget, setViewedClientWidget] = useState<SmallWidget | null>(null);
const [viewedClientWidgetApiRoomId, setViewedClientWidgetApiRoomId] = useState<string | null>(
null
);
const [isAudioEnabled, setIsAudioEnabledState] = useState<boolean>(DEFAULT_AUDIO_ENABLED); const [isAudioEnabled, setIsAudioEnabledState] = useState<boolean>(DEFAULT_AUDIO_ENABLED);
const [isVideoEnabled, setIsVideoEnabledState] = useState<boolean>(DEFAULT_VIDEO_ENABLED); const [isVideoEnabled, setIsVideoEnabledState] = useState<boolean>(DEFAULT_VIDEO_ENABLED);
const [isChatOpen, setIsChatOpenState] = useState<boolean>(DEFAULT_CHAT_OPENED); const [isChatOpen, setIsChatOpenState] = useState<boolean>(DEFAULT_CHAT_OPENED);
const [isCallActive, setIsCallActive] = useState<boolean>(DEFAULT_CALL_ACTIVE); const [isActiveCallReady, setIsCallActive] = useState<boolean>(DEFAULT_CALL_ACTIVE);
const [isPrimaryIframe, setIsPrimaryIframe] = useState<boolean>(DEFAULT_PRIMARY_IFRAME);
const { roomIdOrAlias: viewedRoomId } = useParams<{ roomIdOrAlias: string }>(); const { roomIdOrAlias: viewedRoomId } = useParams<{ roomIdOrAlias: string }>();
const [lastViewedRoomDuringCall, setLastViewedRoomDuringCall] = useState<string | null>(null);
const resetMediaState = useCallback(() => { const resetMediaState = useCallback(() => {
logger.debug('CallContext: Resetting media state to defaults.'); 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.`); logger.debug(`CallContext: Active call room changed, resetting media state.`);
resetMediaState(); 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( const setViewedCallRoomId = useCallback(
(roomId: string | null) => { (roomId: string | null) => {
logger.warn(`CallContext: Setting activeCallRoomId to ${roomId}`); logger.warn(`CallContext: Setting viewedCallRoomId to ${roomId}`);
setViewedCallRoomIdState(roomId); setViewedCallRoomIdState(roomId);
}, },
[setViewedCallRoomIdState] [setViewedCallRoomIdState]
@@ -167,72 +142,18 @@ export function CallProvider({ children }: CallProviderProps) {
[activeClientWidgetApi, activeClientWidgetApiRoomId, setActiveClientWidgetApi, resetMediaState] [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( const hangUp = useCallback(
(nextRoom: string) => { () => {
if (typeof nextRoom === 'string') { setActiveClientWidgetApi(null, null, null);
logger.debug('1 Hangup'); setActiveCallRoomIdState(null);
setActiveClientWidgetApi(null, null, null); activeClientWidgetApi?.transport.send(`${WIDGET_HANGUP_ACTION}`, {});
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();
}
setIsCallActive(false); setIsCallActive(false);
logger.debug(`CallContext: Hang up called.`); logger.debug(`CallContext: Hang up called.`);
}, },
[ [
activeCallRoomId,
activeClientWidget,
activeClientWidgetApi?.transport, activeClientWidgetApi?.transport,
setActiveClientWidgetApi, setActiveClientWidgetApi,
viewedRoomId,
] ]
); );
@@ -240,22 +161,15 @@ export function CallProvider({ children }: CallProviderProps) {
if (!activeCallRoomId && !viewedCallRoomId) { if (!activeCallRoomId && !viewedCallRoomId) {
return; return;
} }
if (!lastViewedRoomDuringCall) {
if (activeCallRoomId) const api = activeClientWidgetApi;
setLastViewedRoomDuringCall((prevLastRoom) => prevLastRoom || activeCallRoomId); if (!api) {
} return;
if (
lastViewedRoomDuringCall &&
lastViewedRoomDuringCall !== viewedRoomId &&
activeCallRoomId &&
isCallActive
) {
setLastViewedRoomDuringCall(activeCallRoomId);
} }
const handleHangup = (ev: CustomEvent) => { const handleHangup = (ev: CustomEvent) => {
ev.preventDefault(); ev.preventDefault();
if (isCallActive && ev.detail.widgetId === activeClientWidgetApi?.widget.id) { if (isActiveCallReady && ev.detail.widgetId === activeClientWidgetApi?.widget.id) {
activeClientWidgetApi?.transport.reply(ev.detail, {}); activeClientWidgetApi?.transport.reply(ev.detail, {});
} }
logger.debug( logger.debug(
@@ -287,107 +201,46 @@ export function CallProvider({ children }: CallProviderProps) {
const handleOnScreenStateUpdate = (ev: CustomEvent) => { const handleOnScreenStateUpdate = (ev: CustomEvent) => {
ev.preventDefault(); ev.preventDefault();
if (isPrimaryIframe) { api.transport.reply(ev.detail, {});
activeClientWidgetApi?.transport.reply(ev.detail, {});
} else {
viewedClientWidgetApi?.transport.reply(ev.detail, {});
}
}; };
const handleOnTileLayout = (ev: CustomEvent) => { const handleOnTileLayout = (ev: CustomEvent) => {
ev.preventDefault(); ev.preventDefault();
if (isPrimaryIframe) {
activeClientWidgetApi?.transport.reply(ev.detail, {}); api.transport.reply(ev.detail, {});
} else {
viewedClientWidgetApi?.transport.reply(ev.detail, {});
}
}; };
const handleJoin = (ev: CustomEvent) => { const handleJoin = (ev: CustomEvent) => {
ev.preventDefault(); 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) { api.transport.reply(ev.detail, {});
activeClientWidgetApi?.transport.reply(ev.detail, {}); const iframeDoc =
const iframeDoc = api.iframe?.contentDocument ||
activeClientWidget?.iframe?.contentDocument || api.iframe?.contentWindow.document;
activeClientWidget?.iframe?.contentWindow.document; const observer = new MutationObserver(() => {
const observer = new MutationObserver(() => { const button = iframeDoc.querySelector('[data-testid="incall_leave"]');
const button = iframeDoc.querySelector('[data-testid="incall_leave"]'); if (button) {
if (button) { button.addEventListener('click', () => {
button.addEventListener('click', () => { hangUp()
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();
}); });
} else {
logger.debug('4 Join');
setViewedAsActive();
setIsCallActive(true);
} }
} else if (viewedCallRoomId !== viewedRoomId) { observer.disconnect();
logger.debug('5 Join'); });
setIsCallActive(true); logger.debug('Join Call');
} else { observer.observe(iframeDoc, { childList: true, subtree: true });
logger.debug('6 Join'); setIsCallActive(true);
setViewedAsActive();
}
}; };
logger.debug( logger.debug(
`CallContext: Setting up listeners for clientWidgetApi in room ${activeCallRoomId}` `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); api.on(`action:${WIDGET_HANGUP_ACTION}`, handleHangup);
viewedClientWidgetApi?.on(`action:${WIDGET_MEDIA_STATE_UPDATE_ACTION}`, handleMediaStateUpdate); api.on(`action:${WIDGET_MEDIA_STATE_UPDATE_ACTION}`, handleMediaStateUpdate);
viewedClientWidgetApi?.on(`action:${WIDGET_TILE_UPDATE}`, handleOnTileLayout); api.on(`action:${WIDGET_TILE_UPDATE}`, handleOnTileLayout);
viewedClientWidgetApi?.on(`action:${WIDGET_ON_SCREEN_ACTION}`, handleOnScreenStateUpdate); api.on(`action:${WIDGET_ON_SCREEN_ACTION}`, handleOnScreenStateUpdate);
viewedClientWidgetApi?.on(`action:${WIDGET_HANGUP_ACTION}`, handleHangup); api.on(`action:${WIDGET_JOIN_ACTION}`, handleJoin);
}, [ }, [
activeClientWidgetApi, activeClientWidgetApi,
activeCallRoomId, activeCallRoomId,
@@ -396,16 +249,10 @@ export function CallProvider({ children }: CallProviderProps) {
isChatOpen, isChatOpen,
isAudioEnabled, isAudioEnabled,
isVideoEnabled, isVideoEnabled,
isCallActive, isActiveCallReady,
viewedRoomId, viewedRoomId,
viewedClientWidgetApi,
isPrimaryIframe,
viewedCallRoomId, viewedCallRoomId,
setViewedClientWidgetApi,
setActiveClientWidgetApi,
viewedClientWidget,
setViewedCallRoomId, setViewedCallRoomId,
lastViewedRoomDuringCall,
activeClientWidget?.iframe?.contentDocument, activeClientWidget?.iframe?.contentDocument,
activeClientWidget?.iframe?.contentWindow?.document, activeClientWidget?.iframe?.contentWindow?.document,
]); ]);
@@ -424,6 +271,8 @@ export function CallProvider({ children }: CallProviderProps) {
); );
return Promise.reject(new Error('Mismatched active call clientWidgetApi')); return Promise.reject(new Error('Mismatched active call clientWidgetApi'));
} }
logger.debug( logger.debug(
`CallContext: Sending action '${action}' via active clientWidgetApi (room: ${activeClientWidgetApiRoomId}) with data:`, `CallContext: Sending action '${action}' via active clientWidgetApi (room: ${activeClientWidgetApiRoomId}) with data:`,
data data
@@ -470,11 +319,6 @@ export function CallProvider({ children }: CallProviderProps) {
setIsChatOpenState(newState); setIsChatOpenState(newState);
}, [isChatOpen]); }, [isChatOpen]);
const toggleIframe = useCallback(async () => {
const newState = !isPrimaryIframe;
setIsPrimaryIframe(newState);
}, [isPrimaryIframe]);
const contextValue = useMemo<CallContextState>( const contextValue = useMemo<CallContextState>(
() => ({ () => ({
activeCallRoomId, activeCallRoomId,
@@ -485,19 +329,14 @@ export function CallProvider({ children }: CallProviderProps) {
activeClientWidgetApi, activeClientWidgetApi,
registerActiveClientWidgetApi, registerActiveClientWidgetApi,
activeClientWidget, activeClientWidget,
viewedClientWidgetApi,
registerViewedClientWidgetApi,
viewedClientWidget,
sendWidgetAction, sendWidgetAction,
isChatOpen, isChatOpen,
isAudioEnabled, isAudioEnabled,
isVideoEnabled, isVideoEnabled,
isCallActive, isActiveCallReady,
isPrimaryIframe,
toggleAudio, toggleAudio,
toggleVideo, toggleVideo,
toggleChat, toggleChat
toggleIframe,
}), }),
[ [
activeCallRoomId, activeCallRoomId,
@@ -508,19 +347,14 @@ export function CallProvider({ children }: CallProviderProps) {
activeClientWidgetApi, activeClientWidgetApi,
registerActiveClientWidgetApi, registerActiveClientWidgetApi,
activeClientWidget, activeClientWidget,
viewedClientWidgetApi,
registerViewedClientWidgetApi,
viewedClientWidget,
sendWidgetAction, sendWidgetAction,
isChatOpen, isChatOpen,
isAudioEnabled, isAudioEnabled,
isVideoEnabled, isVideoEnabled,
isCallActive, isActiveCallReady,
isPrimaryIframe,
toggleAudio, toggleAudio,
toggleVideo, toggleVideo,
toggleChat, toggleChat
toggleIframe,
] ]
); );

View File

@@ -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 { logger } from 'matrix-js-sdk/lib/logger';
import { ClientWidgetApi } from 'matrix-widget-api'; import { ClientWidgetApi } from 'matrix-widget-api';
import { Box } from 'folds'; import { Box } from 'folds';
@@ -13,42 +13,38 @@ import {
import { useMatrixClient } from '../../../hooks/useMatrixClient'; import { useMatrixClient } from '../../../hooks/useMatrixClient';
import { useClientConfig } from '../../../hooks/useClientConfig'; import { useClientConfig } from '../../../hooks/useClientConfig';
import { ScreenSize, useScreenSizeContext } from '../../../hooks/useScreenSize'; import { ScreenSize, useScreenSizeContext } from '../../../hooks/useScreenSize';
import { ThemeKind, useTheme } from '../../../hooks/useTheme';
interface PersistentCallContainerProps { interface PersistentCallContainerProps {
children: ReactNode; children: ReactNode;
} }
export const PrimaryRefContext = createContext(null); export const PrimaryRefContext = createContext(null);
export const BackupRefContext = createContext(null);
export function PersistentCallContainer({ children }: PersistentCallContainerProps) { export function PersistentCallContainer({ children }: PersistentCallContainerProps) {
const primaryIframeRef = useRef<HTMLIFrameElement | null>(null); const primaryIframeRef = useRef<HTMLIFrameElement | null>(null);
const primaryWidgetApiRef = useRef<ClientWidgetApi | null>(null); const primaryWidgetApiRef = useRef<ClientWidgetApi | null>(null);
const primarySmallWidgetRef = useRef<SmallWidget | null>(null); const primarySmallWidgetRef = useRef<SmallWidget | null>(null);
const backupIframeRef = useRef<HTMLIFrameElement | null>(null);
const backupWidgetApiRef = useRef<ClientWidgetApi | null>(null);
const backupSmallWidgetRef = useRef<SmallWidget | null>(null);
const { const {
activeCallRoomId, activeCallRoomId,
viewedCallRoomId, viewedCallRoomId,
isChatOpen, isChatOpen,
isCallActive, isActiveCallReady,
isPrimaryIframe,
registerActiveClientWidgetApi, registerActiveClientWidgetApi,
activeClientWidget, activeClientWidget,
registerViewedClientWidgetApi,
viewedClientWidget,
} = useCallState(); } = useCallState();
const mx = useMatrixClient(); const mx = useMatrixClient();
const clientConfig = useClientConfig(); const clientConfig = useClientConfig();
const screenSize = useScreenSizeContext(); const screenSize = useScreenSizeContext();
const theme = useTheme()
const isMobile = screenSize === ScreenSize.Mobile; const isMobile = screenSize === ScreenSize.Mobile;
const { roomIdOrAlias: viewedRoomId } = useParams(); const { roomIdOrAlias: viewedRoomId } = useParams();
const isViewingActiveCall = useMemo( const isViewingActiveCall = useMemo(
() => activeCallRoomId !== null && activeCallRoomId === viewedRoomId, () => activeCallRoomId !== null && activeCallRoomId === viewedRoomId,
[activeCallRoomId, viewedRoomId] [activeCallRoomId, viewedRoomId]
); );
/* eslint-disable no-param-reassign */ /* eslint-disable no-param-reassign */
const setupWidget = useCallback( const setupWidget = useCallback(
@@ -56,13 +52,16 @@ export function PersistentCallContainer({ children }: PersistentCallContainerPro
widgetApiRef: { current: ClientWidgetApi }, widgetApiRef: { current: ClientWidgetApi },
smallWidgetRef: { current: SmallWidget }, smallWidgetRef: { current: SmallWidget },
iframeRef: { current: { src: string } }, iframeRef: { current: { src: string } },
skipLobby: { toString: () => any } skipLobby: { toString: () => any },
themeKind: ThemeKind | null
) => { ) => {
if (mx?.getUserId()) { if (mx?.getUserId()) {
logger.debug(`CallContextJ: iframe src - ${iframeRef.current.src}`)
logger.debug(`CallContextJ: activeCallRoomId: ${activeCallRoomId} viewedId: ${viewedCallRoomId} isactive: ${isActiveCallReady}`)
if ( if (
(activeCallRoomId !== viewedCallRoomId && isCallActive) || (activeCallRoomId !== viewedCallRoomId && isActiveCallReady) ||
(activeCallRoomId && !isCallActive) || (activeCallRoomId && !isActiveCallReady) ||
(!activeCallRoomId && viewedCallRoomId && !isCallActive) (!activeCallRoomId && viewedCallRoomId && !isActiveCallReady)
) { ) {
const roomIdToSet = (skipLobby ? activeCallRoomId : viewedCallRoomId) ?? ''; const roomIdToSet = (skipLobby ? activeCallRoomId : viewedCallRoomId) ?? '';
if (roomIdToSet === '') { if (roomIdToSet === '') {
@@ -78,27 +77,18 @@ export function PersistentCallContainer({ children }: PersistentCallContainerPro
skipLobby: skipLobby.toString(), skipLobby: skipLobby.toString(),
returnToLobby: 'true', returnToLobby: 'true',
perParticipantE2EE: 'true', perParticipantE2EE: 'true',
theme: themeKind
} }
); );
if ( if (
(primarySmallWidgetRef.current?.roomId || backupSmallWidgetRef.current?.roomId) && primarySmallWidgetRef.current?.roomId &&
(skipLobby (activeClientWidget?.roomId && activeClientWidget.roomId === primarySmallWidgetRef.current?.roomId)
? 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))
) { ) {
return; return;
} }
if (iframeRef.current && iframeRef.current.src !== newUrl.toString()) { if (iframeRef.current && (!iframeRef.current.src || iframeRef.current.src !== newUrl.toString())) {
iframeRef.current.src = newUrl.toString();
} else 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); const widgetApiInstance = smallWidget.startMessaging(iframeElement);
widgetApiRef.current = widgetApiInstance; widgetApiRef.current = widgetApiInstance;
if (skipLobby) { registerActiveClientWidgetApi(roomIdToSet, widgetApiRef.current, smallWidget);
registerActiveClientWidgetApi(activeCallRoomId, widgetApiRef.current, smallWidget);
} else {
registerViewedClientWidgetApi(viewedCallRoomId, widgetApiRef.current, smallWidget);
}
widgetApiInstance.once('ready', () => { widgetApiInstance.once('ready', () => {
logger.info(`PersistentCallContainer: Widget for ${roomIdToSet} is ready.`); logger.info(`PersistentCallContainer: Widget for ${roomIdToSet} is ready.`);
}); });
@@ -141,43 +127,37 @@ export function PersistentCallContainer({ children }: PersistentCallContainerPro
mx, mx,
activeCallRoomId, activeCallRoomId,
viewedCallRoomId, viewedCallRoomId,
isCallActive, isActiveCallReady,
clientConfig.elementCallUrl, clientConfig.elementCallUrl,
viewedClientWidget,
activeClientWidget, activeClientWidget,
viewedRoomId,
registerActiveClientWidgetApi, registerActiveClientWidgetApi,
registerViewedClientWidgetApi,
] ]
); );
useEffect(() => { useEffect(() => {
if ((activeCallRoomId && !viewedCallRoomId) || (activeCallRoomId && viewedCallRoomId)) logger.debug(`CallContextJ: ${isActiveCallReady} ${isViewingActiveCall}`)
setupWidget(primaryWidgetApiRef, primarySmallWidgetRef, primaryIframeRef, isPrimaryIframe); }, [isActiveCallReady, isViewingActiveCall])
if ((!activeCallRoomId && viewedCallRoomId) || (viewedCallRoomId && activeCallRoomId)) useEffect(() => {
setupWidget(backupWidgetApiRef, backupSmallWidgetRef, backupIframeRef, !isPrimaryIframe); 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, setupWidget,
primaryWidgetApiRef, primaryWidgetApiRef,
primarySmallWidgetRef, primarySmallWidgetRef,
primaryIframeRef, primaryIframeRef,
backupWidgetApiRef,
backupSmallWidgetRef,
backupIframeRef,
registerActiveClientWidgetApi, registerActiveClientWidgetApi,
registerViewedClientWidgetApi,
activeCallRoomId, activeCallRoomId,
viewedCallRoomId, viewedCallRoomId,
isCallActive, isActiveCallReady
isPrimaryIframe,
]); ]);
const memoizedIframeRef = useMemo(() => primaryIframeRef, [primaryIframeRef]); const memoizedIframeRef = useMemo(() => primaryIframeRef, [primaryIframeRef]);
const memoizedBackupIframeRef = useMemo(() => backupIframeRef, [backupIframeRef]);
return ( return (
<PrimaryRefContext.Provider value={memoizedIframeRef}> <PrimaryRefContext.Provider value={memoizedIframeRef}>
<BackupRefContext.Provider value={memoizedBackupIframeRef}>
<Box grow="No"> <Box grow="No">
<Box <Box
direction="Column" direction="Column"
@@ -201,7 +181,7 @@ export function PersistentCallContainer({ children }: PersistentCallContainerPro
position: 'absolute', position: 'absolute',
top: 0, top: 0,
left: 0, left: 0,
display: isPrimaryIframe || isViewingActiveCall ? 'flex' : 'none', display: 'flex',
width: '100%', width: '100%',
height: '100%', height: '100%',
border: 'none', border: 'none',
@@ -211,27 +191,10 @@ export function PersistentCallContainer({ children }: PersistentCallContainerPro
allow="microphone; camera; display-capture; autoplay; clipboard-write;" allow="microphone; camera; display-capture; autoplay; clipboard-write;"
src="about:blank" src="about:blank"
/> />
<iframe
ref={backupIframeRef}
style={{
position: 'absolute',
top: 0,
left: 0,
width: '100%',
height: '100%',
border: 'none',
display: !isPrimaryIframe || isViewingActiveCall ? 'flex' : 'none',
}}
title="Persistent Element Call"
sandbox="allow-forms allow-scripts allow-same-origin allow-popups allow-modals allow-downloads"
allow="microphone; camera; display-capture; autoplay; clipboard-write;"
src="about:blank"
/>
</Box> </Box>
</Box> </Box>
</Box> </Box>
{children} {children}
</BackupRefContext.Provider>
</PrimaryRefContext.Provider> </PrimaryRefContext.Provider>
); );
} }

View File

@@ -300,7 +300,7 @@ export function Space() {
const selectedRoomId = useSelectedRoom(); const selectedRoomId = useSelectedRoom();
const lobbySelected = useSpaceLobbySelected(spaceIdOrAlias); const lobbySelected = useSpaceLobbySelected(spaceIdOrAlias);
const searchSelected = useSpaceSearchSelected(spaceIdOrAlias); const searchSelected = useSpaceSearchSelected(spaceIdOrAlias);
const { isCallActive, activeCallRoomId } = useCallState(); const { isActiveCallReady, activeCallRoomId } = useCallState();
const [closedCategories, setClosedCategories] = useAtom(useClosedNavCategoriesAtom()); const [closedCategories, setClosedCategories] = useAtom(useClosedNavCategoriesAtom());
@@ -325,10 +325,10 @@ export function Space() {
const showRoomAnyway = const showRoomAnyway =
roomToUnread.has(roomId) || roomToUnread.has(roomId) ||
roomId === selectedRoomId || roomId === selectedRoomId ||
(isCallActive && activeCallRoomId === roomId); (isActiveCallReady && activeCallRoomId === roomId);
return !showRoomAnyway; return !showRoomAnyway;
}, },
[space.roomId, closedCategories, roomToUnread, selectedRoomId, activeCallRoomId, isCallActive] [space.roomId, closedCategories, roomToUnread, selectedRoomId, activeCallRoomId, isActiveCallReady]
), ),
useCallback( useCallback(
(sId) => closedCategories.has(makeNavCategoryId(space.roomId, sId)), (sId) => closedCategories.has(makeNavCategoryId(space.roomId, sId)),