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:
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 React, { useContext, useMemo, useCallback, useEffect, useRef } from 'react';
|
||||
import { Box } from 'folds';
|
||||
import React, { useContext, useMemo, useCallback, useEffect, useRef, MouseEventHandler, useState, ReactNode } from 'react';
|
||||
import { Box, Button, Spinner, Text } from 'folds';
|
||||
import { useCallState } from '../../pages/client/call/CallProvider';
|
||||
import { useCallMembers } from '../../hooks/useCallMemberships';
|
||||
|
||||
import {
|
||||
PrimaryRefContext,
|
||||
BackupRefContext,
|
||||
} from '../../pages/client/call/PersistentCallContainer';
|
||||
import { ScreenSize, useScreenSizeContext } from '../../hooks/useScreenSize';
|
||||
import { useDebounce } from '../../hooks/useDebounce';
|
||||
import { useMatrixClient } from '../../hooks/useMatrixClient';
|
||||
import { CallViewUser } from './CallViewUser';
|
||||
import { useRoomNavigate } from '../../hooks/useRoomNavigate';
|
||||
import { getMemberDisplayName } from '../../utils/room';
|
||||
import { getMxIdLocalPart } from '../../utils/matrix';
|
||||
import * as css from "./CallView.css"
|
||||
|
||||
|
||||
type OriginalStyles = {
|
||||
position?: string;
|
||||
@@ -22,27 +30,49 @@ type OriginalStyles = {
|
||||
border?: string;
|
||||
};
|
||||
|
||||
export function CallViewUserGrid({ children }: { children: ReactNode }) {
|
||||
return (
|
||||
<Box className={css.CallViewUserGrid} style={{
|
||||
maxWidth: React.Children.count(children) === 4 ? "336px" : "503px"
|
||||
}}>
|
||||
{children}
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
export function CallView({ room }: { room: Room }) {
|
||||
const primaryIframeRef = useContext(PrimaryRefContext);
|
||||
const backupIframeRef = useContext(BackupRefContext);
|
||||
const iframeHostRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const originalIframeStylesRef = useRef<OriginalStyles | null>(null);
|
||||
const { activeCallRoomId, isPrimaryIframe, isChatOpen } = useCallState();
|
||||
const isViewingActiveCall = useMemo(
|
||||
() => activeCallRoomId !== null && activeCallRoomId === room.roomId,
|
||||
[activeCallRoomId, room.roomId]
|
||||
);
|
||||
const mx = useMatrixClient();
|
||||
|
||||
const [visibleCallNames, setVisibleCallNames] = useState("")
|
||||
|
||||
const {
|
||||
isActiveCallReady,
|
||||
activeCallRoomId,
|
||||
isChatOpen,
|
||||
setActiveCallRoomId,
|
||||
hangUp,
|
||||
setViewedCallRoomId
|
||||
} = useCallState();
|
||||
|
||||
const isActiveCallRoom = activeCallRoomId === room.roomId
|
||||
const shouldDisplayCall = isActiveCallRoom && isActiveCallReady;
|
||||
const callMembers = useCallMembers(mx, room.roomId)
|
||||
|
||||
const getName = (userId: string) => getMemberDisplayName(room, userId) ?? getMxIdLocalPart(userId);
|
||||
|
||||
const memberDisplayNames = callMembers.map(callMembership => getName(callMembership.sender ?? ''))
|
||||
|
||||
const { navigateRoom } = useRoomNavigate();
|
||||
const screenSize = useScreenSizeContext();
|
||||
const isMobile = screenSize === ScreenSize.Mobile;
|
||||
|
||||
/* eslint-disable-next-line no-nested-ternary */
|
||||
const activeIframeDisplayRef = isPrimaryIframe
|
||||
? isViewingActiveCall
|
||||
? primaryIframeRef
|
||||
: backupIframeRef
|
||||
: isViewingActiveCall
|
||||
? backupIframeRef
|
||||
: primaryIframeRef;
|
||||
const activeIframeDisplayRef = primaryIframeRef
|
||||
|
||||
const applyFixedPositioningToIframe = useCallback(() => {
|
||||
const iframeElement = activeIframeDisplayRef?.current;
|
||||
@@ -88,7 +118,7 @@ export function CallView({ room }: { room: Room }) {
|
||||
const iframeElement = activeIframeDisplayRef?.current;
|
||||
const hostElement = iframeHostRef?.current;
|
||||
|
||||
if (room.isCallRoom() || (isViewingActiveCall && iframeElement && hostElement)) {
|
||||
if (room.isCallRoom() || (shouldDisplayCall && iframeElement && hostElement)) {
|
||||
applyFixedPositioningToIframe();
|
||||
|
||||
const resizeObserver = new ResizeObserver(debouncedApplyFixedPositioning);
|
||||
@@ -116,13 +146,36 @@ export function CallView({ room }: { room: Room }) {
|
||||
activeIframeDisplayRef,
|
||||
applyFixedPositioningToIframe,
|
||||
debouncedApplyFixedPositioning,
|
||||
isPrimaryIframe,
|
||||
isViewingActiveCall,
|
||||
shouldDisplayCall,
|
||||
room,
|
||||
]);
|
||||
|
||||
|
||||
const handleJoinVCClick: MouseEventHandler<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);
|
||||
|
||||
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 (
|
||||
<Box grow="Yes" direction="Column" style={{ display: isCallViewVisible ? 'flex' : 'none' }}>
|
||||
<div
|
||||
@@ -132,9 +185,38 @@ export function CallView({ room }: { room: Room }) {
|
||||
height: '100%',
|
||||
position: 'relative',
|
||||
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>
|
||||
);
|
||||
}
|
||||
|
||||
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',
|
||||
widgetId,
|
||||
appPrompt: 'false',
|
||||
preload: 'false',
|
||||
skipLobby: setParams.skipLobby ?? 'true',
|
||||
skipLobby: setParams.skipLobby ?? 'true', // TODO: skipLobby is deprecated, use intent instead (intent doesn't produce the same effect?)
|
||||
returnToLobby: setParams.returnToLobby ?? 'true',
|
||||
perParticipantE2EE: setParams.perParticipantE2EE ?? 'true',
|
||||
hideHeader: 'true',
|
||||
header: 'none',
|
||||
confineToRoom: 'true',
|
||||
theme: setParams.theme ?? 'dark',
|
||||
userId: mx.getUserId()!,
|
||||
deviceId: mx.getDeviceId()!,
|
||||
roomId,
|
||||
@@ -137,6 +138,7 @@ export class SmallWidget extends EventEmitter {
|
||||
// Populate the map of "read up to" events for this widget with the current event in every room.
|
||||
// This is a bit inefficient, but should be okay. We do this for all rooms in case the widget
|
||||
// requests timeline capabilities in other rooms down the road. It's just easier to manage here.
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
for (const room of this.client.getRooms()) {
|
||||
// Timelines are most recent last
|
||||
const events = room.getLiveTimeline()?.getEvents() || [];
|
||||
|
||||
@@ -9,7 +9,7 @@ export function CallNavStatus() {
|
||||
activeCallRoomId,
|
||||
isAudioEnabled,
|
||||
isVideoEnabled,
|
||||
isCallActive,
|
||||
isActiveCallReady,
|
||||
toggleAudio,
|
||||
toggleVideo,
|
||||
hangUp,
|
||||
@@ -21,7 +21,7 @@ export function CallNavStatus() {
|
||||
navigateRoom(activeCallRoomId);
|
||||
}
|
||||
};
|
||||
if (!isCallActive) {
|
||||
if (!isActiveCallReady) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@@ -227,7 +227,7 @@ export function RoomNavItem({
|
||||
const [menuAnchor, setMenuAnchor] = useState<RectCords>();
|
||||
const unread = useRoomUnread(room.roomId, roomToUnreadAtom);
|
||||
const {
|
||||
isCallActive,
|
||||
isActiveCallReady,
|
||||
activeCallRoomId,
|
||||
setActiveCallRoomId,
|
||||
setViewedCallRoomId,
|
||||
@@ -238,7 +238,7 @@ export function RoomNavItem({
|
||||
const typingMember = useRoomTypingMember(room.roomId).filter(
|
||||
(receipt) => receipt.userId !== mx.getUserId()
|
||||
);
|
||||
const isActiveCall = isCallActive && activeCallRoomId === room.roomId;
|
||||
const isActiveCall = isActiveCallReady && activeCallRoomId === room.roomId;
|
||||
const callMemberships = useCallMembers(mx, room.roomId);
|
||||
const { navigateRoom } = useRoomNavigate();
|
||||
const { roomIdOrAlias: viewedRoomId } = useParams();
|
||||
@@ -269,7 +269,7 @@ export function RoomNavItem({
|
||||
}
|
||||
if (room.isCallRoom()) {
|
||||
if (!isMobile) {
|
||||
if (activeCallRoomId !== room.roomId) {
|
||||
if (!isActiveCall) {
|
||||
if (mx.getRoom(viewedRoomId)?.isCallRoom()) {
|
||||
navigateRoom(room.roomId);
|
||||
}
|
||||
|
||||
@@ -18,8 +18,8 @@ type RoomNavUserProps = {
|
||||
export function RoomNavUser({ room, callMembership }: RoomNavUserProps) {
|
||||
const mx = useMatrixClient();
|
||||
const useAuthentication = useMediaAuthentication();
|
||||
const { isCallActive, activeCallRoomId } = useCallState();
|
||||
const isActiveCall = isCallActive && activeCallRoomId === room.roomId;
|
||||
const { isActiveCallReady, activeCallRoomId } = useCallState();
|
||||
const isActiveCall = isActiveCallReady && activeCallRoomId === room.roomId;
|
||||
const userId = callMembership.sender ?? '';
|
||||
const avatarMxcUrl = getMemberAvatarMxc(room, userId);
|
||||
const avatarUrl = avatarMxcUrl
|
||||
|
||||
Reference in New Issue
Block a user