Skip to content
This repository was archived by the owner on Jan 23, 2024. It is now read-only.

Commit a61720a

Browse files
committed
Merge pull request #20 from wmalik-mux/main
Chat using Custom Events, Display Names, and UI refresh
1 parent ef5d01d commit a61720a

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

54 files changed

+3825
-2415
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ yarn-debug.log*
2525
yarn-error.log*
2626

2727
# local env files
28+
.env
2829
.env.local
2930
.env.development.local
3031
.env.test.local

components/Controls.tsx

+8-8
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,7 @@ import ControlsLeft from "./controls/ControlsLeft";
99
import ControlsCenter from "./controls/ControlsCenter";
1010
import ACRScoreDialog from "./modals/ACRScoreDialog";
1111

12-
interface Props {
13-
renameCallback: (newName: string) => void;
14-
}
15-
16-
export default function Controls({ renameCallback }: Props): JSX.Element {
12+
export default function Controls(): JSX.Element {
1713
const router = useRouter();
1814
const { leaveSpace } = useSpace();
1915
const { isOpen: isACRScoreDialogOpen, onOpen: onACRScoreDialogOpen } =
@@ -38,7 +34,7 @@ export default function Controls({ renameCallback }: Props): JSX.Element {
3834
<ACRScoreDialog isOpen={isACRScoreDialogOpen} onClose={leaveSpacePage} />
3935
<Flex
4036
alignItems="center"
41-
backgroundColor="#383838"
37+
backgroundColor="#0a0a0b"
4238
bottom="0px"
4339
flexDirection="row"
4440
height={{ base: "60px", sm: "80px" }}
@@ -50,8 +46,12 @@ export default function Controls({ renameCallback }: Props): JSX.Element {
5046
zIndex={1000}
5147
>
5248
<ControlsLeft />
53-
<ControlsCenter onLeave={promptForACR} onRename={renameCallback} />
54-
<ControlsRight onLeave={promptForACR} />
49+
{!isACRScoreDialogOpen && (
50+
<>
51+
<ControlsCenter onLeave={promptForACR} />
52+
<ControlsRight onLeave={promptForACR} />
53+
</>
54+
)}
5555
</Flex>
5656
</>
5757
);

components/Gallery.tsx

+39-15
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
import React, { useCallback, useMemo, useState } from "react";
22
import { IconButton, Center, Flex } from "@chakra-ui/react";
33

4-
import { MAX_PARTICIPANTS_PER_PAGE } from "lib/constants";
5-
64
import UserContext from "context/User";
75

86
import { useSpace } from "hooks/useSpace";
@@ -13,6 +11,14 @@ import ParticipantAudio from "./ParticipantAudio";
1311
import ChevronLeftIcon from "components/icons/ChevronLeftIcon";
1412
import ChevronRightIcon from "components/icons/ChevronRightIcon";
1513

14+
function pushToFront<T>(array: T[], element: T) {
15+
const index = array.findIndex((el) => el === element);
16+
if (index) {
17+
array.splice(index, 1);
18+
array.unshift(element);
19+
}
20+
}
21+
1622
interface Props {
1723
gap: number;
1824
width: number;
@@ -24,23 +30,36 @@ export default function Gallery({
2430
gap,
2531
width,
2632
height,
27-
participantsPerPage = MAX_PARTICIPANTS_PER_PAGE,
33+
participantsPerPage,
2834
}: Props): JSX.Element {
2935
const [currentPage, setCurrentPage] = useState(1);
30-
const { connectionIds, participantCount } = useSpace();
36+
const {
37+
connectionIds,
38+
participantCount,
39+
localParticipantConnectionId,
40+
screenShareParticipantConnectionId,
41+
} = useSpace();
3142
const { pinnedConnectionId } = React.useContext(UserContext);
3243

33-
const sortedConnectionIds = useMemo(() => {
34-
return [...connectionIds].sort((a, b) => {
35-
if (a === pinnedConnectionId) {
36-
return -1;
37-
} else if (b === pinnedConnectionId) {
38-
return 1;
39-
} else {
40-
return 0;
44+
const orderedConnectionIds = useMemo(() => {
45+
const ids = [...connectionIds];
46+
[
47+
screenShareParticipantConnectionId,
48+
pinnedConnectionId,
49+
localParticipantConnectionId,
50+
].forEach((id) => {
51+
if (id) {
52+
pushToFront(ids, id);
4153
}
4254
});
43-
}, [connectionIds, pinnedConnectionId]);
55+
56+
return ids;
57+
}, [
58+
connectionIds,
59+
localParticipantConnectionId,
60+
pinnedConnectionId,
61+
screenShareParticipantConnectionId,
62+
]);
4463

4564
const numberPages = useMemo(() => {
4665
if (participantCount >= participantsPerPage) {
@@ -59,14 +78,19 @@ export default function Gallery({
5978
const paginatedConnectionIds = useMemo(() => {
6079
const startIndex = currentPage * participantsPerPage - participantsPerPage;
6180
const endIndex = startIndex + participantsPerPage;
62-
const pageParticipants = sortedConnectionIds.slice(startIndex, endIndex);
81+
const pageParticipants = orderedConnectionIds.slice(startIndex, endIndex);
6382
// if there are no participants, then only the local view will show up on the page
6483
// we need to go back to the previous page.
6584
if (pageParticipants.length === 0) {
6685
goToPreviousPage();
6786
}
6887
return pageParticipants;
69-
}, [sortedConnectionIds, currentPage, participantsPerPage, goToPreviousPage]);
88+
}, [
89+
orderedConnectionIds,
90+
currentPage,
91+
participantsPerPage,
92+
goToPreviousPage,
93+
]);
7094

7195
const hidePaginateCtrlRight = currentPage === numberPages;
7296

components/Header.tsx

+7-1
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,13 @@ export default function Header(): JSX.Element {
2121
target="_blank"
2222
rel="noreferrer"
2323
>
24-
<Image priority alt="logo" width={150} height={35} src={muxLogo} />
24+
<Image
25+
priority
26+
alt="logo"
27+
width={150}
28+
src={muxLogo}
29+
style={{ height: "auto" }}
30+
/>
2531
</a>
2632
</Flex>
2733
<Spacer />

components/Meeting.tsx

+37-25
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React from "react";
1+
import React, { useContext } from "react";
22
import { Center, Flex } from "@chakra-ui/react";
33

44
import { useSpace } from "hooks/useSpace";
@@ -7,8 +7,11 @@ import useWindowDimensions from "hooks/useWindowDimension";
77
import Gallery from "./Gallery";
88
import Timer from "./Timer";
99
import ScreenShareRenderer from "./renderers/ScreenShareRenderer";
10+
import ChatRenderer from "./renderers/ChatRenderer";
11+
import ChatContext from "context/Chat";
1012

1113
const headerHeight = 80;
14+
const chatWidth = 300;
1215

1316
export default function Meeting(): JSX.Element {
1417
let gap = 10;
@@ -18,32 +21,37 @@ export default function Meeting(): JSX.Element {
1821
isScreenShareActive,
1922
spaceEndsAt,
2023
} = useSpace();
24+
const { isChatOpen } = useContext(ChatContext);
2125
const { width = 0, height = 0 } = useWindowDimensions();
2226

27+
const availableWidth = width - (isChatOpen && width > 800 ? chatWidth : 0);
28+
2329
const paddingY = height < 600 ? 10 : 40;
24-
const paddingX = width < 800 ? 40 : 60;
30+
const paddingX = availableWidth < 800 ? 40 : 60;
2531

26-
let galleryWidth = width - paddingX * 2;
32+
let galleryWidth = availableWidth - paddingX * 2;
2733
if (isScreenShareActive) {
2834
if (participantCount < 6) {
29-
galleryWidth = width * 0.25 - paddingX;
35+
galleryWidth = availableWidth * 0.25 - paddingX;
3036
} else {
31-
galleryWidth = width * 0.33 - paddingX / 2;
37+
galleryWidth = availableWidth * 0.33 - paddingX / 2;
3238
}
3339
galleryWidth = Math.max(galleryWidth, 160);
3440
}
3541
let galleryHeight = height - headerHeight - paddingY * 2;
3642

37-
let screenShareWidth = isScreenShareActive ? width - galleryWidth : 0;
43+
let screenShareWidth = isScreenShareActive
44+
? availableWidth - galleryWidth
45+
: 0;
3846

3947
let direction: "row" | "column" = "row";
4048
if (width < height) {
4149
gap = 8;
42-
galleryWidth = width - paddingX * 2;
50+
galleryWidth = availableWidth - paddingX * 2;
4351
if (isScreenShareActive) {
4452
direction = "column";
45-
screenShareWidth = width;
46-
galleryHeight = height - headerHeight - (width / 4) * 3;
53+
screenShareWidth = availableWidth;
54+
galleryHeight = height - headerHeight - (availableWidth / 4) * 3;
4755
}
4856
}
4957

@@ -54,23 +62,27 @@ export default function Meeting(): JSX.Element {
5462
const participantsPerPage = Math.round(rows * columns);
5563

5664
return (
57-
<Flex
58-
width="100%"
59-
height="100%"
60-
alignItems="center"
61-
justifyContent="center"
62-
direction={direction}
63-
>
65+
<Flex width="100%" height="100%" direction="row" position="relative">
6466
{spaceEndsAt && <Timer />}
65-
<Center width={`${screenShareWidth}px`} maxHeight="100%">
66-
<ScreenShareRenderer attach={attachScreenShare} />
67-
</Center>
68-
<Gallery
69-
gap={gap}
70-
width={galleryWidth}
71-
height={galleryHeight}
72-
participantsPerPage={participantsPerPage}
73-
/>
67+
<Flex
68+
maxWidth={availableWidth}
69+
height="100%"
70+
alignItems="center"
71+
justifyContent="center"
72+
direction={direction}
73+
flexGrow={1}
74+
>
75+
<Center width={`${screenShareWidth}px`} maxHeight="100%">
76+
<ScreenShareRenderer attach={attachScreenShare} />
77+
</Center>
78+
<Gallery
79+
gap={gap}
80+
width={galleryWidth}
81+
height={galleryHeight}
82+
participantsPerPage={participantsPerPage}
83+
/>
84+
</Flex>
85+
<ChatRenderer show={width > 800 && isChatOpen} />
7486
</Flex>
7587
);
7688
}

components/Participant.tsx

+15-28
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
import React, { useMemo } from "react";
22
import { Box, Center, Flex } from "@chakra-ui/react";
33

4+
import UserContext from "context/User";
45
import { useParticipant } from "hooks/useParticipant";
56

67
import Pin from "./Pin";
78
import VideoRenderer from "./renderers/VideoRenderer";
89
import ParticipantInfoBar from "./ParticipantInfoBar";
10+
import ParticipantName from "./ParticipantName";
911

1012
interface Props {
1113
width?: number;
@@ -22,17 +24,15 @@ export default function Participant({
2224
id,
2325
isLocal,
2426
isSpeaking,
25-
isMicrophoneMuted,
27+
hasMicTrack,
28+
isMicTrackMuted,
2629
isCameraOff,
2730
cameraWidth,
2831
cameraHeight,
29-
attachCamera,
32+
displayName,
33+
attachVideoElement,
3034
} = useParticipant(connectionId);
3135

32-
const displayName = useMemo(() => {
33-
return id.split("|")[0];
34-
}, [id]);
35-
3636
const outlineWidth = 3;
3737

3838
return (
@@ -42,12 +42,9 @@ export default function Participant({
4242
minWidth="160px"
4343
minHeight="90px"
4444
background="black"
45-
outline={`${
46-
!isMicrophoneMuted && isSpeaking
47-
? `${outlineWidth}px solid`
48-
: "1px solid"
49-
}`}
50-
outlineColor={`${!isMicrophoneMuted && isSpeaking ? "#FB2491" : "black"}`}
45+
boxShadow={`0 0 0 ${
46+
!isMicTrackMuted && isSpeaking ? outlineWidth : 1
47+
}px ${!isMicTrackMuted && isSpeaking ? "#FA50B5" : "black"}`}
5148
borderRadius="5px"
5249
margin={`${outlineWidth}px`}
5350
overflow="hidden"
@@ -58,30 +55,20 @@ export default function Participant({
5855
local={isLocal}
5956
width={cameraWidth}
6057
height={cameraHeight}
61-
attach={attachCamera}
58+
attachFunc={attachVideoElement}
6259
connectionId={connectionId}
6360
/>
6461

6562
<ParticipantInfoBar
66-
name={displayName}
67-
isMuted={isMicrophoneMuted}
63+
name={displayName || id}
64+
isMuted={!hasMicTrack || isMicTrackMuted}
6865
parentHeight={height!}
6966
/>
7067

7168
{isCameraOff && (
72-
<Center
73-
background="black"
74-
color="white"
75-
fontSize="45px"
76-
h="100%"
77-
position="absolute"
78-
top="0"
79-
w="100%"
80-
>
81-
<Flex direction="column" textAlign="center">
82-
<Box>{displayName}</Box>
83-
</Flex>
84-
</Center>
69+
<ParticipantName isSmall={width! <= 400}>
70+
{displayName || id}
71+
</ParticipantName>
8572
)}
8673

8774
{!isLocal && <Pin connectionId={connectionId} />}

components/ParticipantAudio.tsx

+2-2
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,9 @@ interface Props {
99
}
1010

1111
const ParticipantAudio = ({ connectionId }: Props) => {
12-
const { isLocal, attachMicrophone } = useParticipant(connectionId);
12+
const { isLocal, attachAudioElement } = useParticipant(connectionId);
1313

14-
return !isLocal ? <AudioRenderer attach={attachMicrophone} /> : null;
14+
return !isLocal ? <AudioRenderer attachFunc={attachAudioElement} /> : null;
1515
};
1616

1717
export default ParticipantAudio;

components/ParticipantName.tsx

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import { memo } from "react";
2+
import { Box, Center, Flex } from "@chakra-ui/react";
3+
4+
interface Props {
5+
isSmall: boolean;
6+
children: string;
7+
}
8+
9+
function ParticipantName({ isSmall, children }: Props): JSX.Element {
10+
return (
11+
<Center
12+
background="black"
13+
color="white"
14+
fontSize={isSmall ? "20px" : "45px"}
15+
h="100%"
16+
position="absolute"
17+
top="0"
18+
w="100%"
19+
>
20+
<Flex width="100%" direction="column" textAlign="center">
21+
<Box overflowWrap="anywhere">{children}</Box>
22+
</Flex>
23+
</Center>
24+
);
25+
}
26+
27+
const MemoizedName = memo(ParticipantName);
28+
export default MemoizedName;

0 commit comments

Comments
 (0)