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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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
+ });