forked from github/cinny
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:
8
package-lock.json
generated
8
package-lock.json
generated
@@ -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": {
|
||||||
|
|||||||
@@ -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"
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
37
src/app/features/call/CallView.css.ts
Normal file
37
src/app/features/call/CallView.css.ts
Normal 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',
|
||||||
|
},
|
||||||
|
});
|
||||||
@@ -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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
69
src/app/features/call/CallViewUser.tsx
Normal file
69
src/app/features/call/CallViewUser.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -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() || [];
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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,
|
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)),
|
||||||
|
|||||||
Reference in New Issue
Block a user