@100mslive/roomkit-react 0.2.3 → 0.2.4-alpha.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (84) hide show
  1. package/dist/{HLSView-2BP4GO3Q.js → HLSView-5I6UAYPZ.js} +6 -6
  2. package/dist/HLSView-5I6UAYPZ.js.map +7 -0
  3. package/dist/Prebuilt/common/constants.d.ts +1 -0
  4. package/dist/Prebuilt/components/Connection/TileConnection.d.ts +2 -3
  5. package/dist/Prebuilt/components/InsetTile.d.ts +3 -1
  6. package/dist/Prebuilt/components/LayoutModeSelector.d.ts +2 -0
  7. package/dist/Prebuilt/components/MoreSettings/ChangeNameContent.d.ts +10 -0
  8. package/dist/Prebuilt/components/MoreSettings/ChangeNameModal.d.ts +5 -0
  9. package/dist/Prebuilt/components/Polls/CreatePollQuiz/PollsQuizMenu.d.ts +2 -0
  10. package/dist/Prebuilt/components/Polls/CreateQuestions/SavedQuestion.d.ts +1 -2
  11. package/dist/Prebuilt/components/Polls/common/StatusIndicator.d.ts +4 -3
  12. package/dist/Prebuilt/components/Polls/common/constants.d.ts +5 -0
  13. package/dist/Prebuilt/components/ScreenshareDisplay.d.ts +2 -0
  14. package/dist/Prebuilt/components/ScreenshareTile.d.ts +7 -0
  15. package/dist/Prebuilt/components/SecondaryTiles.d.ts +1 -1
  16. package/dist/Prebuilt/components/Settings/LayoutSettings.d.ts +11 -0
  17. package/dist/Prebuilt/components/Settings/NotificationSettings.d.ts +2 -0
  18. package/dist/Prebuilt/components/Settings/SwitchWithLabel.d.ts +10 -0
  19. package/dist/Prebuilt/components/Settings/common.d.ts +878 -0
  20. package/dist/Prebuilt/components/TileMenu/TileMenu.d.ts +14 -0
  21. package/dist/Prebuilt/components/TileMenu/TileMenuContent.d.ts +7 -7
  22. package/dist/Prebuilt/components/VideoLayouts/ProminenceLayout.d.ts +4 -2
  23. package/dist/Prebuilt/components/VideoLayouts/interface.d.ts +1 -0
  24. package/dist/Prebuilt/components/VideoTile.d.ts +19 -0
  25. package/dist/Prebuilt/components/hooks/useDropdownList.d.ts +4 -0
  26. package/dist/{chunk-G25T3EBJ.js → chunk-OGCNZHHH.js} +1328 -1097
  27. package/dist/chunk-OGCNZHHH.js.map +7 -0
  28. package/dist/index.cjs.js +1826 -1575
  29. package/dist/index.cjs.js.map +4 -4
  30. package/dist/index.js +1 -1
  31. package/dist/meta.cjs.json +458 -293
  32. package/dist/meta.esbuild.json +470 -305
  33. package/package.json +6 -6
  34. package/src/Pagination/StyledPagination.tsx +1 -0
  35. package/src/Popover/index.tsx +8 -1
  36. package/src/Prebuilt/common/constants.ts +1 -0
  37. package/src/Prebuilt/components/AppData/AppData.tsx +2 -0
  38. package/src/Prebuilt/components/AppData/useUISettings.js +1 -1
  39. package/src/Prebuilt/components/Connection/TileConnection.tsx +13 -6
  40. package/src/Prebuilt/components/HlsStatsOverlay.jsx +2 -2
  41. package/src/Prebuilt/components/InsetTile.tsx +13 -6
  42. package/src/Prebuilt/components/LayoutModeSelector.tsx +106 -0
  43. package/src/Prebuilt/components/MoreSettings/{ChangeNameContent.jsx → ChangeNameContent.tsx} +10 -2
  44. package/src/Prebuilt/components/MoreSettings/{ChangeNameModal.jsx → ChangeNameModal.tsx} +14 -5
  45. package/src/Prebuilt/components/MoreSettings/SplitComponents/DesktopOptions.tsx +2 -2
  46. package/src/Prebuilt/components/MoreSettings/SplitComponents/MwebOptions.tsx +2 -2
  47. package/src/Prebuilt/components/Notifications/Notifications.tsx +0 -1
  48. package/src/Prebuilt/components/Playlist/VideoPlayer.jsx +1 -1
  49. package/src/Prebuilt/components/Polls/CreatePollQuiz/{PollsQuizMenu.jsx → PollsQuizMenu.tsx} +54 -26
  50. package/src/Prebuilt/components/Polls/CreateQuestions/CreateQuestions.jsx +21 -31
  51. package/src/Prebuilt/components/Polls/CreateQuestions/SavedQuestion.tsx +3 -17
  52. package/src/Prebuilt/components/Polls/Voting/LeaderboardSummary.tsx +1 -1
  53. package/src/Prebuilt/components/Polls/Voting/QuestionCard.jsx +1 -10
  54. package/src/Prebuilt/components/Polls/Voting/Voting.tsx +1 -3
  55. package/src/Prebuilt/components/Polls/common/StatusIndicator.tsx +12 -3
  56. package/src/Prebuilt/components/Polls/common/constants.ts +5 -0
  57. package/src/Prebuilt/components/Preview/PreviewForm.tsx +2 -2
  58. package/src/Prebuilt/components/PreviousRoleInMetadata.tsx +1 -1
  59. package/src/Prebuilt/components/{ScreenshareTile.jsx → ScreenshareTile.tsx} +39 -6
  60. package/src/Prebuilt/components/SecondaryTiles.tsx +36 -4
  61. package/src/Prebuilt/components/Settings/DeviceSettings.jsx +1 -1
  62. package/src/Prebuilt/components/Settings/{LayoutSettings.jsx → LayoutSettings.tsx} +58 -14
  63. package/src/Prebuilt/components/Settings/{NotificationSettings.jsx → NotificationSettings.tsx} +14 -3
  64. package/src/Prebuilt/components/Settings/SettingsModal.jsx +32 -6
  65. package/src/Prebuilt/components/Settings/{SwitchWithLabel.jsx → SwitchWithLabel.tsx} +15 -1
  66. package/src/Prebuilt/components/Settings/common.ts +16 -0
  67. package/src/Prebuilt/components/TileMenu/{TileMenu.jsx → TileMenu.tsx} +12 -4
  68. package/src/Prebuilt/components/TileMenu/TileMenuContent.tsx +12 -10
  69. package/src/Prebuilt/components/VideoLayouts/ProminenceLayout.tsx +29 -14
  70. package/src/Prebuilt/components/VideoLayouts/RoleProminence.tsx +12 -2
  71. package/src/Prebuilt/components/VideoLayouts/ScreenshareLayout.tsx +20 -5
  72. package/src/Prebuilt/components/VideoLayouts/interface.ts +1 -0
  73. package/src/Prebuilt/components/{VideoTile.jsx → VideoTile.tsx} +57 -44
  74. package/src/Prebuilt/components/VirtualBackground/VBPicker.tsx +2 -2
  75. package/src/Prebuilt/components/hooks/{useDropdownList.jsx → useDropdownList.ts} +2 -1
  76. package/src/Prebuilt/components/pdfAnnotator/shareScreenOptions.jsx +1 -1
  77. package/src/Prebuilt/layouts/HLSView.jsx +2 -2
  78. package/src/Prebuilt/layouts/SidePane.tsx +8 -4
  79. package/src/Prebuilt/layouts/VideoStreamingSection.tsx +1 -1
  80. package/src/VideoTile/StyledVideoTile.tsx +4 -4
  81. package/dist/HLSView-2BP4GO3Q.js.map +0 -7
  82. package/dist/chunk-G25T3EBJ.js.map +0 -7
  83. package/src/Prebuilt/components/Settings/common.js +0 -41
  84. /package/src/Prebuilt/components/{ScreenshareDisplay.jsx → ScreenshareDisplay.tsx} +0 -0
@@ -11,30 +11,26 @@ import { usePollViewToggle } from '../../AppData/useSidepane';
11
11
  import { usePollViewState } from '../../AppData/useUISettings';
12
12
  import { POLL_VIEWS } from '../../../common/constants';
13
13
 
14
+ const getEditableFormat = questions => {
15
+ const editableQuestions = questions.map(question => {
16
+ return { ...question, saved: true, draftID: uuid() };
17
+ });
18
+ return editableQuestions;
19
+ };
20
+
14
21
  export function CreateQuestions() {
15
- const [questions, setQuestions] = useState([{ draftID: uuid() }]);
16
22
  const actions = useHMSActions();
17
23
  const { isHLSRunning } = useRecordingStreaming();
18
24
  const togglePollView = usePollViewToggle();
19
25
  const { pollInView: id, setPollView } = usePollViewState();
20
26
  const interaction = useHMSStore(selectPollByID(id));
21
-
22
- const isValidPoll = useMemo(
23
- () => questions.length > 0 && questions.every(question => isValidQuestion(question)),
24
- [questions],
27
+ const [questions, setQuestions] = useState(
28
+ interaction.questions?.length ? getEditableFormat(interaction.questions) : [{ draftID: uuid() }],
25
29
  );
26
30
 
31
+ const isValidPoll = useMemo(() => questions.length > 0 && questions.every(isValidQuestion), [questions]);
32
+
27
33
  const launchPoll = async () => {
28
- const validQuestions = questions
29
- .filter(question => isValidQuestion(question))
30
- .map(question => ({
31
- text: question.text,
32
- type: question.type,
33
- options: question.options,
34
- skippable: question.skippable,
35
- weight: question.weight,
36
- }));
37
- await actions.interactivityCenter.addQuestionsToPoll(id, validQuestions);
38
34
  await actions.interactivityCenter.startPoll(id);
39
35
  await sendTimedMetadata(id);
40
36
  setPollView(POLL_VIEWS.VOTE);
@@ -75,17 +71,17 @@ export function CreateQuestions() {
75
71
  question={question}
76
72
  index={index}
77
73
  length={questions.length}
78
- onSave={questionParams => {
79
- setQuestions(questions => [
80
- ...questions.slice(0, index),
81
- questionParams,
82
- ...questions.slice(index + 1),
83
- ]);
74
+ onSave={async questionParams => {
75
+ const updatedQuestions = [...questions.slice(0, index), questionParams, ...questions.slice(index + 1)];
76
+ setQuestions(updatedQuestions);
77
+ const validQuestions = updatedQuestions.filter(question => isValidQuestion(question));
78
+
79
+ await actions.interactivityCenter.addQuestionsToPoll(id, validQuestions);
84
80
  }}
85
81
  isQuiz={isQuiz}
86
82
  removeQuestion={questionID =>
87
83
  setQuestions(prev => {
88
- return prev.filter(questionFromSet => questionID !== questionFromSet.draftID);
84
+ return prev.filter(questionFromSet => questionID !== questionFromSet?.draftID);
89
85
  })
90
86
  }
91
87
  convertToDraft={questionID =>
@@ -117,8 +113,8 @@ export function CreateQuestions() {
117
113
  </Text>
118
114
  </Flex>
119
115
  <Flex css={{ w: '100%' }} justify="end">
120
- <Button disabled={!isValidPoll} onClick={launchPoll}>
121
- Launch {interaction.type}
116
+ <Button disabled={!isValidPoll} onClick={async () => launchPoll()}>
117
+ Launch {interaction?.type}
122
118
  </Button>
123
119
  </Flex>
124
120
  </Flex>
@@ -130,13 +126,7 @@ const QuestionCard = ({ question, onSave, index, length, removeQuestion, isQuiz,
130
126
  return (
131
127
  <Flex direction="column" css={{ p: '$md', bg: '$surface_default', r: '$1', mb: '$sm' }}>
132
128
  {question.saved ? (
133
- <SavedQuestion
134
- question={question}
135
- index={index}
136
- length={length}
137
- convertToDraft={convertToDraft}
138
- removeQuestion={removeQuestion}
139
- />
129
+ <SavedQuestion question={question} index={index} length={length} convertToDraft={convertToDraft} />
140
130
  ) : (
141
131
  <QuestionForm
142
132
  question={question}
@@ -1,10 +1,7 @@
1
- import React, { useState } from 'react';
1
+ import React from 'react';
2
2
  import { HMSPollQuestion } from '@100mslive/react-sdk';
3
- import { CheckCircleIcon, TrashIcon } from '@100mslive/react-icons';
3
+ import { CheckCircleIcon } from '@100mslive/react-icons';
4
4
  import { Button, Flex, Text } from '../../../../';
5
- // @ts-ignore
6
- import IconButton from '../../../IconButton';
7
- import { DeleteQuestionModal } from './DeleteQuestionModal';
8
5
  import { QUESTION_TYPE_TITLE } from '../../../common/constants';
9
6
 
10
7
  export const SavedQuestion = ({
@@ -12,15 +9,12 @@ export const SavedQuestion = ({
12
9
  index,
13
10
  length,
14
11
  convertToDraft,
15
- removeQuestion,
16
12
  }: {
17
13
  question: HMSPollQuestion & { draftID: number };
18
14
  index: number;
19
15
  length: number;
20
16
  convertToDraft: (draftID: number) => void;
21
- removeQuestion: (draftID: number) => void;
22
17
  }) => {
23
- const [openDeleteModal, setOpenDeleteModal] = useState(false);
24
18
  return (
25
19
  <>
26
20
  <Text variant="overline" css={{ c: '$on_surface_low', textTransform: 'uppercase' }}>
@@ -48,19 +42,11 @@ export const SavedQuestion = ({
48
42
  Not required to answer
49
43
  </Text>
50
44
  ) : null}
51
- <Flex justify="end" css={{ w: '100%', alignItems: 'center', gap: '$4' }}>
52
- <IconButton onClick={() => setOpenDeleteModal(true)} css={{ background: 'none' }}>
53
- <TrashIcon />
54
- </IconButton>
45
+ <Flex justify="end" css={{ w: '100%', alignItems: 'center' }}>
55
46
  <Button variant="standard" css={{ fontWeight: '$semiBold' }} onClick={() => convertToDraft(question.draftID)}>
56
47
  Edit
57
48
  </Button>
58
49
  </Flex>
59
- <DeleteQuestionModal
60
- removeQuestion={() => removeQuestion(question.draftID)}
61
- open={openDeleteModal}
62
- setOpen={setOpenDeleteModal}
63
- />
64
50
  </>
65
51
  );
66
52
  };
@@ -44,7 +44,7 @@ export const LeaderboardSummary = ({ pollID }: { pollID: string }) => {
44
44
  <Text variant="lg" css={{ fontWeight: '$semiBold' }}>
45
45
  {quiz.title}
46
46
  </Text>
47
- <StatusIndicator isLive={false} />
47
+ <StatusIndicator status={quiz.state} />
48
48
  </Flex>
49
49
  <Flex
50
50
  css={{ color: '$on_surface_medium', '&:hover': { color: '$on_surface_high', cursor: 'pointer' } }}
@@ -73,16 +73,7 @@ export const QuestionCard = ({
73
73
  },
74
74
  ]);
75
75
  startTime.current = Date.now();
76
- }, [
77
- isValidVote,
78
- actions.interactivityCenter,
79
- pollID,
80
- index,
81
- singleOptionAnswer,
82
- multipleOptionAnswer,
83
- totalQuestions,
84
- isQuiz,
85
- ]);
76
+ }, [isValidVote, actions.interactivityCenter, pollID, index, singleOptionAnswer, multipleOptionAnswer]);
86
77
 
87
78
  return (
88
79
  <Box
@@ -33,8 +33,6 @@ export const Voting = ({ id, toggleVoting }: { id: string; toggleVoting: () => v
33
33
 
34
34
  const canViewLeaderboard = poll.type === 'quiz' && poll.state === 'stopped' && !poll.anonymous;
35
35
 
36
- const isLive = poll.state === 'started';
37
-
38
36
  return (
39
37
  <Container rounded>
40
38
  <Flex
@@ -56,7 +54,7 @@ export const Voting = ({ id, toggleVoting }: { id: string; toggleVoting: () => v
56
54
  <ChevronLeftIcon />
57
55
  </Flex>
58
56
  <Text variant="h6">{poll.title}</Text>
59
- <StatusIndicator isLive={isLive} />
57
+ <StatusIndicator status={poll.state} />
60
58
  <Box
61
59
  css={{
62
60
  marginLeft: 'auto',
@@ -1,12 +1,21 @@
1
1
  import React from 'react';
2
+ import { HMSPollState } from '@100mslive/react-sdk';
2
3
  import { Flex, Text } from '../../../../';
4
+ import { PollStage } from './constants';
3
5
 
4
- export const StatusIndicator = ({ isLive }: { isLive: boolean }) => {
6
+ const statusMap: Record<HMSPollState, PollStage> = {
7
+ created: PollStage.DRAFT,
8
+ started: PollStage.LIVE,
9
+ stopped: PollStage.ENDED,
10
+ };
11
+
12
+ export const StatusIndicator = ({ status }: { status?: HMSPollState }) => {
13
+ if (!status) return null;
5
14
  return (
6
15
  <Flex align="center">
7
16
  <Flex
8
17
  css={{
9
- backgroundColor: isLive ? '$alert_error_default' : '$secondary_default',
18
+ backgroundColor: statusMap[status] === PollStage.LIVE ? '$alert_error_default' : '$secondary_default',
10
19
  p: '$2 $4',
11
20
  borderRadius: '$0',
12
21
  }}
@@ -18,7 +27,7 @@ export const StatusIndicator = ({ isLive }: { isLive: boolean }) => {
18
27
  color: '$on_primary_high',
19
28
  }}
20
29
  >
21
- {isLive ? 'LIVE' : 'ENDED'}
30
+ {statusMap[status]}
22
31
  </Text>
23
32
  </Flex>
24
33
  </Flex>
@@ -0,0 +1,5 @@
1
+ export enum PollStage {
2
+ DRAFT = 'DRAFT',
3
+ LIVE = 'LIVE',
4
+ ENDED = 'ENDED',
5
+ }
@@ -2,7 +2,7 @@ import React from 'react';
2
2
  import { useMedia } from 'react-use';
3
3
  import { JoinForm_JoinBtnType } from '@100mslive/types-prebuilt/elements/join_form';
4
4
  import { useRecordingStreaming } from '@100mslive/react-sdk';
5
- import { RadioIcon } from '@100mslive/react-icons';
5
+ import { GoLiveIcon } from '@100mslive/react-icons';
6
6
  import { Button, config as cssConfig, Flex, Input, styled } from '../../..';
7
7
  import { useRoomLayout } from '../../provider/roomLayoutProvider';
8
8
  // @ts-ignore: No implicit Any
@@ -72,7 +72,7 @@ const PreviewForm = ({
72
72
 
73
73
  <Button type="submit" icon disabled={!name || !enableJoin} onClick={onJoin}>
74
74
  {/* Conditions to show go live: The first broadcaster joins a streaming kit that is not live */}
75
- {showGoLive ? <RadioIcon height={18} width={18} /> : null}
75
+ {showGoLive ? <GoLiveIcon height={18} width={18} /> : null}
76
76
  {showGoLive ? joinForm.go_live_btn_label : joinForm.join_btn_label}
77
77
  </Button>
78
78
  </Form>
@@ -10,7 +10,7 @@ export const PreviousRoleInMetadata = () => {
10
10
  useEffect(() => {
11
11
  let previousRole = vanillaStore.getState(selectLocalPeerRoleName);
12
12
  const unsubscribe = vanillaStore.subscribe(currentRole => {
13
- if (previousRole !== currentRole) {
13
+ if (previousRole !== currentRole && currentRole) {
14
14
  updateMetaData({ prevRole: previousRole });
15
15
  previousRole = currentRole;
16
16
  }
@@ -10,11 +10,16 @@ import {
10
10
  } from '@100mslive/react-sdk';
11
11
  import { ExpandIcon, ShrinkIcon } from '@100mslive/react-icons';
12
12
  import TileMenu from './TileMenu/TileMenu';
13
+ import { Box } from '../../Layout';
13
14
  import { VideoTileStats } from '../../Stats';
15
+ import { useTheme } from '../../Theme';
14
16
  import { Video } from '../../Video';
15
17
  import { StyledVideoTile } from '../../VideoTile';
18
+ import { LayoutModeSelector } from './LayoutModeSelector';
19
+ // @ts-ignore: No implicit Any
16
20
  import { getVideoTileLabel } from './peerTileUtils';
17
21
  import { ScreenshareDisplay } from './ScreenshareDisplay';
22
+ // @ts-ignore: No implicit Any
18
23
  import { useUISettings } from './AppData/useUISettings';
19
24
  import { UI_SETTINGS } from '../common/constants';
20
25
 
@@ -27,14 +32,15 @@ const labelStyles = {
27
32
  flexShrink: 0,
28
33
  };
29
34
 
30
- const Tile = ({ peerId, width = '100%', height = '100%' }) => {
35
+ const Tile = ({ peerId, width = '100%', height = '100%' }: { peerId: string; width?: string; height?: string }) => {
31
36
  const isLocal = useHMSStore(selectLocalPeerID) === peerId;
32
37
  const track = useHMSStore(selectScreenShareByPeerID(peerId));
38
+ const { theme } = useTheme();
33
39
  const peer = useHMSStore(selectPeerByID(peerId));
34
40
  const isAudioOnly = useUISettings(UI_SETTINGS.isAudioOnly);
35
41
  const [isMouseHovered, setIsMouseHovered] = useState(false);
36
42
  const showStatsOnTiles = useUISettings(UI_SETTINGS.showStatsOnTiles);
37
- const fullscreenRef = useRef(null);
43
+ const fullscreenRef = useRef<HTMLDivElement | null>(null);
38
44
  // fullscreen is for desired state
39
45
  const [fullscreen, setFullscreen] = useState(false);
40
46
  // isFullscreen is for true state
@@ -44,7 +50,7 @@ const Tile = ({ peerId, width = '100%', height = '100%' }) => {
44
50
  const isFullScreenSupported = screenfull.isEnabled;
45
51
  const audioTrack = useHMSStore(selectScreenShareAudioByPeerID(peer?.id));
46
52
 
47
- if (isLocal && !['browser', 'window', 'application'].includes(track?.displaySurface)) {
53
+ if (isLocal && track?.displaySurface && !['browser', 'window', 'application'].includes(track.displaySurface)) {
48
54
  return <ScreenshareDisplay />;
49
55
  }
50
56
 
@@ -59,7 +65,15 @@ const Tile = ({ peerId, width = '100%', height = '100%' }) => {
59
65
  });
60
66
 
61
67
  return (
62
- <StyledVideoTile.Root css={{ width, height, p: 0, minHeight: 0 }} data-testid="screenshare_tile">
68
+ <StyledVideoTile.Root
69
+ css={{
70
+ width,
71
+ height,
72
+ p: 0,
73
+ minHeight: 0,
74
+ }}
75
+ data-testid="screenshare_tile"
76
+ >
63
77
  <StyledVideoTile.Container
64
78
  transparentBg
65
79
  ref={fullscreenRef}
@@ -73,14 +87,33 @@ const Tile = ({ peerId, width = '100%', height = '100%' }) => {
73
87
  <VideoTileStats audioTrackID={audioTrack?.id} videoTrackID={track?.id} peerID={peerId} isLocal={isLocal} />
74
88
  ) : null}
75
89
  {isFullScreenSupported && isMouseHovered ? (
76
- <StyledVideoTile.FullScreenButton onClick={() => setFullscreen(!fullscreen)}>
90
+ <StyledVideoTile.FullScreenButton
91
+ css={{ bg: `${theme.colors.background_dim.value}A3` }}
92
+ onClick={() => setFullscreen(!fullscreen)}
93
+ >
77
94
  {isFullscreen ? <ShrinkIcon /> : <ExpandIcon />}
78
95
  </StyledVideoTile.FullScreenButton>
79
96
  ) : null}
97
+ {isMouseHovered && (
98
+ <Box
99
+ css={{
100
+ position: 'absolute',
101
+ top: '$2',
102
+ r: '$1',
103
+ h: '$14',
104
+ right: isFullScreenSupported ? '$17' : '$2',
105
+ zIndex: 5,
106
+ bg: `${theme.colors.background_dim.value}A3`,
107
+ }}
108
+ >
109
+ <LayoutModeSelector />
110
+ </Box>
111
+ )}
112
+
80
113
  {track ? (
81
114
  <Video
82
115
  screenShare={true}
83
- mirror={peer.isLocal && track?.source === 'regular'}
116
+ mirror={peer.isLocal}
84
117
  attach={!isAudioOnly}
85
118
  trackId={track.id}
86
119
  css={{ minHeight: 0 }}
@@ -1,16 +1,39 @@
1
- import React, { useEffect, useState } from 'react';
1
+ import React, { useEffect, useRef, useState } from 'react';
2
2
  import { useMedia } from 'react-use';
3
+ import { selectAppData, selectSessionStore, selectTrackByID, useHMSStore } from '@100mslive/react-sdk';
3
4
  import { LayoutProps } from './VideoLayouts/interface';
4
5
  import { ProminenceLayout } from './VideoLayouts/ProminenceLayout';
5
6
  import { config as cssConfig } from '../../Theme';
6
7
  import { Pagination } from './Pagination';
7
8
  import { usePagesWithTiles } from './hooks/useTileLayout';
9
+ import { APP_DATA, SESSION_STORE_KEY } from '../common/constants';
8
10
 
9
- export const SecondaryTiles = ({ peers, onPageChange, onPageSize, edgeToEdge }: LayoutProps) => {
11
+ export const SecondaryTiles = ({ peers, onPageChange, onPageSize, edgeToEdge, hasSidebar }: LayoutProps) => {
10
12
  const isMobile = useMedia(cssConfig.media.md);
11
13
  const maxTileCount = isMobile ? 2 : 4;
12
- const pagesWithTiles = usePagesWithTiles({ peers, maxTileCount });
13
14
  const [page, setPage] = useState(0);
15
+ const pinnedTrackId = useHMSStore(selectAppData(APP_DATA.pinnedTrackId));
16
+ const spotlightPeerId = useHMSStore(selectSessionStore(SESSION_STORE_KEY.SPOTLIGHT));
17
+ const activeScreensharePeerId = useHMSStore(selectAppData(APP_DATA.activeScreensharePeerId));
18
+ const pinnedPeer = useHMSStore(selectTrackByID(pinnedTrackId))?.peerId;
19
+ const pageChangedAfterPinning = useRef(false);
20
+ const pagesWithTiles = usePagesWithTiles({
21
+ peers:
22
+ spotlightPeerId || pinnedPeer
23
+ ? [...peers].sort((p1, p2) => {
24
+ const peerIdList = [activeScreensharePeerId, pinnedPeer, spotlightPeerId];
25
+ // put active screenshare peer, pinned peer, spotlight peer at first
26
+ if (peerIdList.includes(p1)) {
27
+ return -1;
28
+ }
29
+ if (peerIdList.includes(p2)) {
30
+ return 1;
31
+ }
32
+ return 0;
33
+ })
34
+ : peers,
35
+ maxTileCount,
36
+ });
14
37
  const pageSize = pagesWithTiles[0]?.length || 0;
15
38
 
16
39
  useEffect(() => {
@@ -19,8 +42,17 @@ export const SecondaryTiles = ({ peers, onPageChange, onPageSize, edgeToEdge }:
19
42
  }
20
43
  }, [pageSize, onPageSize]);
21
44
 
45
+ useEffect(() => {
46
+ if ((pinnedPeer || spotlightPeerId) && page !== 0 && !pageChangedAfterPinning.current) {
47
+ setPage(0);
48
+ pageChangedAfterPinning.current = true;
49
+ } else if (!pinnedPeer && !spotlightPeerId) {
50
+ pageChangedAfterPinning.current = false;
51
+ }
52
+ }, [pinnedPeer, spotlightPeerId, page]);
53
+
22
54
  return (
23
- <ProminenceLayout.SecondarySection tiles={pagesWithTiles[page]} edgeToEdge={edgeToEdge}>
55
+ <ProminenceLayout.SecondarySection tiles={pagesWithTiles[page]} edgeToEdge={edgeToEdge} hasSidebar={hasSidebar}>
24
56
  {!edgeToEdge && (
25
57
  <Pagination
26
58
  page={page}
@@ -15,7 +15,7 @@ import { config as cssConfig } from '../../../Theme';
15
15
  import { DialogDropdownTrigger } from '../../primitives/DropdownTrigger';
16
16
  import { useUISettings } from '../AppData/useUISettings';
17
17
  import { useDropdownSelection } from '../hooks/useDropdownSelection';
18
- import { settingOverflow } from './common.js';
18
+ import { settingOverflow } from './common';
19
19
  import { UI_SETTINGS } from '../../common/constants';
20
20
 
21
21
  /**
@@ -1,18 +1,34 @@
1
1
  import React, { useCallback } from 'react';
2
2
  import { selectIsLocalScreenShared, selectIsLocalVideoEnabled, useHMSActions, useHMSStore } from '@100mslive/react-sdk';
3
- import { Box, Flex, Slider, Text } from '../../../';
3
+ import { GalleryIcon, PersonRectangleIcon, SidebarIcon } from '@100mslive/react-icons';
4
+ import { Box, Flex, Slider, Text } from '../../..';
4
5
  import SwitchWithLabel from './SwitchWithLabel';
6
+ // @ts-ignore: No implicit Any
5
7
  import { useSetUiSettings } from '../AppData/useUISettings';
6
- import { settingOverflow } from './common.js';
8
+ import { settingOverflow } from './common';
7
9
  import { UI_SETTINGS } from '../../common/constants';
8
10
 
11
+ export const LayoutMode = {
12
+ SIDEBAR: 'Sidebar',
13
+ GALLERY: 'Gallery',
14
+ SPOTLIGHT: 'Spotlight',
15
+ };
16
+
17
+ export type LayoutModeKeys = keyof typeof LayoutMode;
18
+
19
+ export const LayoutModeIconMapping = {
20
+ [LayoutMode.GALLERY]: <GalleryIcon />,
21
+ [LayoutMode.SIDEBAR]: <SidebarIcon />,
22
+ [LayoutMode.SPOTLIGHT]: <PersonRectangleIcon />,
23
+ };
24
+
9
25
  export const LayoutSettings = () => {
10
26
  const hmsActions = useHMSActions();
11
27
  const isLocalVideoEnabled = useHMSStore(selectIsLocalVideoEnabled);
12
28
  const isLocalScreenShared = useHMSStore(selectIsLocalScreenShared);
13
29
  const [{ isAudioOnly, maxTileCount, mirrorLocalVideo }, setUISettings] = useSetUiSettings();
14
30
  const toggleIsAudioOnly = useCallback(
15
- async isAudioOnlyModeOn => {
31
+ async (isAudioOnlyModeOn?: boolean) => {
16
32
  if (isAudioOnlyModeOn) {
17
33
  // turn off video and screen share if user switches to audio only mode
18
34
  isLocalVideoEnabled && (await hmsActions.setLocalVideoEnabled(false));
@@ -25,17 +41,34 @@ export const LayoutSettings = () => {
25
41
 
26
42
  return (
27
43
  <Box className={settingOverflow()}>
28
- <SwitchWithLabel label="Audio Only Mode" id="audioOnlyMode" checked={isAudioOnly} onChange={toggleIsAudioOnly} />
29
- <SwitchWithLabel
30
- label="Mirror Local Video"
31
- id="mirrorMode"
32
- checked={mirrorLocalVideo}
33
- onChange={value => {
34
- setUISettings({
35
- [UI_SETTINGS.mirrorLocalVideo]: value,
36
- });
37
- }}
38
- />
44
+ <Box>
45
+ {/* <Text variant="md" css={{ fontWeight: '$semiBold' }}>
46
+ Layout Modes
47
+ </Text>
48
+ <RadioGroup.Root
49
+ css={{ flexDirection: 'column', alignItems: 'start', gap: '$10', my: '$2', py: '$8' }}
50
+ value={layoutMode}
51
+ onValueChange={value =>
52
+ setUISettings({
53
+ [UI_SETTINGS.layoutMode]: value,
54
+ })
55
+ }
56
+ >
57
+ {Object.keys(LayoutMode).map(key => {
58
+ return (
59
+ <Flex align="center" key={key} css={{ mr: '$8', gap: '$8' }}>
60
+ <RadioGroup.Item value={LayoutMode[key as LayoutModeKeys]} id={`layoutMode-${key}`} css={{ mr: '$4' }}>
61
+ <RadioGroup.Indicator />
62
+ </RadioGroup.Item>
63
+ <Label htmlFor={`layoutMode-${key}`} css={{ display: 'flex', gap: '$8', cursor: 'pointer' }}>
64
+ {LayoutModeIconMapping[LayoutMode[key as LayoutModeKeys]]}
65
+ {LayoutMode[key as LayoutModeKeys]}
66
+ </Label>
67
+ </Flex>
68
+ );
69
+ })}
70
+ </RadioGroup.Root> */}
71
+ </Box>
39
72
  <Flex align="center" css={{ w: '100%', my: '$2', py: '$8', '@md': { display: 'none' } }}>
40
73
  <Text variant="md" css={{ fontWeight: '$semiBold' }}>
41
74
  Tiles In View({maxTileCount})
@@ -53,6 +86,17 @@ export const LayoutSettings = () => {
53
86
  />
54
87
  </Flex>
55
88
  </Flex>
89
+ <SwitchWithLabel label="Audio Only Mode" id="audioOnlyMode" checked={isAudioOnly} onChange={toggleIsAudioOnly} />
90
+ <SwitchWithLabel
91
+ label="Mirror Local Video"
92
+ id="mirrorMode"
93
+ checked={mirrorLocalVideo}
94
+ onChange={value => {
95
+ setUISettings({
96
+ [UI_SETTINGS.mirrorLocalVideo]: value,
97
+ });
98
+ }}
99
+ />
56
100
  </Box>
57
101
  );
58
102
  };
@@ -1,12 +1,23 @@
1
1
  import React from 'react';
2
2
  import { AlertOctagonIcon, HandIcon, PeopleAddIcon, PeopleRemoveIcon } from '@100mslive/react-icons';
3
- import { Box } from '../../../';
3
+ import { Box } from '../../..';
4
4
  import SwitchWithLabel from './SwitchWithLabel';
5
+ // @ts-ignore: No implicit Any
5
6
  import { useSetSubscribedNotifications, useSubscribedNotifications } from '../AppData/useUISettings';
6
- import { settingOverflow } from './common.js';
7
+ import { settingOverflow } from './common';
7
8
  import { SUBSCRIBED_NOTIFICATIONS } from '../../common/constants';
8
9
 
9
- const NotificationItem = ({ type, label, icon, checked }) => {
10
+ const NotificationItem = ({
11
+ type,
12
+ label,
13
+ icon,
14
+ checked,
15
+ }: {
16
+ type: string;
17
+ label: string;
18
+ icon: React.ReactNode;
19
+ checked: boolean;
20
+ }) => {
10
21
  const [, setSubscribedNotifications] = useSetSubscribedNotifications(type);
11
22
  return (
12
23
  <SwitchWithLabel
@@ -1,6 +1,6 @@
1
1
  import React, { useCallback, useEffect, useState } from 'react';
2
2
  import { useMedia } from 'react-use';
3
- import { ChevronLeftIcon, CrossIcon } from '@100mslive/react-icons';
3
+ import { ChevronLeftIcon, CrossIcon, GridFourIcon, NotificationsIcon, SettingsIcon } from '@100mslive/react-icons';
4
4
  import { HorizontalDivider } from '../../../Divider';
5
5
  import { IconButton } from '../../../IconButton';
6
6
  import { Box, Flex } from '../../../Layout';
@@ -9,7 +9,31 @@ import { Sheet } from '../../../Sheet';
9
9
  import { Tabs } from '../../../Tabs';
10
10
  import { Text } from '../../../Text';
11
11
  import { config as cssConfig } from '../../../Theme';
12
- import { settingContent, settingsList } from './common.js';
12
+ import DeviceSettings from './DeviceSettings';
13
+ import { LayoutSettings } from './LayoutSettings';
14
+ import { NotificationSettings } from './NotificationSettings';
15
+ import { settingContent } from './common';
16
+
17
+ const settingsList = [
18
+ {
19
+ tabName: 'devices',
20
+ title: 'Device Settings',
21
+ icon: SettingsIcon,
22
+ content: DeviceSettings,
23
+ },
24
+ {
25
+ tabName: 'notifications',
26
+ title: 'Notifications',
27
+ icon: NotificationsIcon,
28
+ content: NotificationSettings,
29
+ },
30
+ {
31
+ tabName: 'layout',
32
+ title: 'Layout',
33
+ icon: GridFourIcon,
34
+ content: LayoutSettings,
35
+ },
36
+ ];
13
37
 
14
38
  const SettingsModal = ({ open, onOpenChange, screenType, children = <></> }) => {
15
39
  const mediaQueryLg = cssConfig.media.md;
@@ -53,8 +77,9 @@ const SettingsModal = ({ open, onOpenChange, screenType, children = <></> }) =>
53
77
  showSetting={showSetting}
54
78
  hideSettingByTabName={hideSettingByTabName}
55
79
  resetSelection={resetSelection}
56
- children={children}
57
- />
80
+ >
81
+ {children}
82
+ </MobileSettingModal>
58
83
  ) : (
59
84
  <DesktopSettingModal
60
85
  open={open}
@@ -64,8 +89,9 @@ const SettingsModal = ({ open, onOpenChange, screenType, children = <></> }) =>
64
89
  showSetting={showSetting}
65
90
  hideSettingByTabName={hideSettingByTabName}
66
91
  resetSelection={resetSelection}
67
- children={children}
68
- />
92
+ >
93
+ {children}
94
+ </DesktopSettingModal>
69
95
  );
70
96
  };
71
97
 
@@ -3,7 +3,21 @@ import { Label } from '../../../Label';
3
3
  import { Flex } from '../../../Layout';
4
4
  import { Switch } from '../../../Switch';
5
5
 
6
- const SwitchWithLabel = ({ label, icon, id, onChange, checked, hide = false }) => {
6
+ const SwitchWithLabel = ({
7
+ label,
8
+ icon,
9
+ id,
10
+ onChange,
11
+ checked,
12
+ hide = false,
13
+ }: {
14
+ label: string;
15
+ icon?: React.ReactNode;
16
+ id: string;
17
+ onChange: (value: boolean) => void;
18
+ checked: boolean;
19
+ hide?: boolean;
20
+ }) => {
7
21
  return (
8
22
  <Flex
9
23
  align="center"
@@ -0,0 +1,16 @@
1
+ import { css } from '../../..';
2
+
3
+ export const settingOverflow = css({
4
+ flex: '1 1 0',
5
+ pr: '$12',
6
+ mr: '-$12',
7
+ overflowY: 'auto',
8
+ });
9
+
10
+ export const settingContent = css({
11
+ display: 'flex',
12
+ flexDirection: 'column',
13
+ '&[hidden]': {
14
+ display: 'none',
15
+ },
16
+ });