* allow user to end call if error when loading * show call support missing error if livekit server is not provided * prevent joining from nav item double click if no livekit support
150 lines
4.8 KiB
TypeScript
150 lines
4.8 KiB
TypeScript
import React, { RefObject, useRef } from 'react';
|
||
import { Badge, Box, color, Header, Scroll, Text, toRem } from 'folds';
|
||
import { useCallEmbed, useCallJoined, useCallEmbedPlacementSync } from '../../hooks/useCallEmbed';
|
||
import { ContainerColor } from '../../styles/ContainerColor.css';
|
||
import { PrescreenControls } from './PrescreenControls';
|
||
import { usePowerLevelsContext } from '../../hooks/usePowerLevels';
|
||
import { useRoom } from '../../hooks/useRoom';
|
||
import { useRoomCreators } from '../../hooks/useRoomCreators';
|
||
import { useRoomPermissions } from '../../hooks/useRoomPermissions';
|
||
import { useMatrixClient } from '../../hooks/useMatrixClient';
|
||
import { StateEvent } from '../../../types/matrix/room';
|
||
import { useCallMembers, useCallSession } from '../../hooks/useCall';
|
||
import { CallMemberRenderer } from './CallMemberCard';
|
||
import * as css from './styles.css';
|
||
import { CallControls } from './CallControls';
|
||
import { useLivekitSupport } from '../../hooks/useLivekitSupport';
|
||
|
||
function LivekitServerMissingMessage() {
|
||
return (
|
||
<Text style={{ margin: 'auto', color: color.Critical.Main }} size="L400" align="Center">
|
||
Your homeserver does not support calling. But you can still join call started by others.
|
||
</Text>
|
||
);
|
||
}
|
||
|
||
function JoinMessage({
|
||
hasParticipant,
|
||
livekitSupported,
|
||
}: {
|
||
hasParticipant?: boolean;
|
||
livekitSupported?: boolean;
|
||
}) {
|
||
if (hasParticipant) return null;
|
||
|
||
if (livekitSupported === false) {
|
||
return <LivekitServerMissingMessage />;
|
||
}
|
||
|
||
return (
|
||
<Text style={{ margin: 'auto' }} size="L400" align="Center">
|
||
Voice chat’s empty — Be the first to hop in!
|
||
</Text>
|
||
);
|
||
}
|
||
|
||
function NoPermissionMessage() {
|
||
return (
|
||
<Text style={{ margin: 'auto' }} size="L400" align="Center">
|
||
You don't have permission to join!
|
||
</Text>
|
||
);
|
||
}
|
||
|
||
function AlreadyInCallMessage() {
|
||
return (
|
||
<Text style={{ margin: 'auto', color: color.Warning.Main }} size="L400" align="Center">
|
||
Already in another call — End the current call to join!
|
||
</Text>
|
||
);
|
||
}
|
||
|
||
function CallPrescreen() {
|
||
const mx = useMatrixClient();
|
||
const room = useRoom();
|
||
const livekitSupported = useLivekitSupport();
|
||
|
||
const powerLevels = usePowerLevelsContext();
|
||
const creators = useRoomCreators(room);
|
||
|
||
const permissions = useRoomPermissions(creators, powerLevels);
|
||
const hasPermission = permissions.event(StateEvent.GroupCallMemberPrefix, mx.getSafeUserId());
|
||
|
||
const callSession = useCallSession(room);
|
||
const callMembers = useCallMembers(room, callSession);
|
||
const hasParticipant = callMembers.length > 0;
|
||
|
||
const callEmbed = useCallEmbed();
|
||
const inOtherCall = callEmbed && callEmbed.roomId !== room.roomId;
|
||
|
||
const canJoin = hasPermission && (livekitSupported || hasParticipant);
|
||
|
||
return (
|
||
<Scroll variant="Surface" hideTrack>
|
||
<Box className={css.CallViewContent} alignItems="Center" justifyContent="Center">
|
||
<Box style={{ maxWidth: toRem(382), width: '100%' }} direction="Column" gap="100">
|
||
{hasParticipant && (
|
||
<Header size="300">
|
||
<Box grow="Yes" alignItems="Center">
|
||
<Text size="L400">Participant</Text>
|
||
</Box>
|
||
<Badge variant="Critical" fill="Solid" size="400">
|
||
<Text as="span" size="L400" truncate>
|
||
{callMembers.length} Live
|
||
</Text>
|
||
</Badge>
|
||
</Header>
|
||
)}
|
||
<CallMemberRenderer members={callMembers} />
|
||
<PrescreenControls canJoin={canJoin} />
|
||
<Header size="300">
|
||
{!inOtherCall &&
|
||
(hasPermission ? (
|
||
<JoinMessage hasParticipant={hasParticipant} livekitSupported={livekitSupported} />
|
||
) : (
|
||
<NoPermissionMessage />
|
||
))}
|
||
{inOtherCall && <AlreadyInCallMessage />}
|
||
</Header>
|
||
</Box>
|
||
</Box>
|
||
</Scroll>
|
||
);
|
||
}
|
||
|
||
type CallJoinedProps = {
|
||
containerRef: RefObject<HTMLDivElement>;
|
||
joined: boolean;
|
||
};
|
||
function CallJoined({ joined, containerRef }: CallJoinedProps) {
|
||
const callEmbed = useCallEmbed();
|
||
|
||
return (
|
||
<Box grow="Yes" direction="Column">
|
||
<Box grow="Yes" ref={containerRef} />
|
||
{callEmbed && joined && <CallControls callEmbed={callEmbed} />}
|
||
</Box>
|
||
);
|
||
}
|
||
|
||
export function CallView() {
|
||
const room = useRoom();
|
||
const callContainerRef = useRef<HTMLDivElement>(null);
|
||
useCallEmbedPlacementSync(callContainerRef);
|
||
|
||
const callEmbed = useCallEmbed();
|
||
const callJoined = useCallJoined(callEmbed);
|
||
|
||
const currentJoined = callEmbed?.roomId === room.roomId && callJoined;
|
||
|
||
return (
|
||
<Box
|
||
className={ContainerColor({ variant: 'Surface' })}
|
||
style={{ minWidth: toRem(280) }}
|
||
grow="Yes"
|
||
>
|
||
{!currentJoined && <CallPrescreen />}
|
||
<CallJoined joined={currentJoined} containerRef={callContainerRef} />
|
||
</Box>
|
||
);
|
||
}
|