diff --git a/apps/100ms-web/src/components/Header/ParticipantList.jsx b/apps/100ms-web/src/components/Header/ParticipantList.jsx
index bb1c7a0577..06cf5204d3 100644
--- a/apps/100ms-web/src/components/Header/ParticipantList.jsx
+++ b/apps/100ms-web/src/components/Header/ParticipantList.jsx
@@ -373,8 +373,8 @@ export const ParticipantSearch = ({ onSearch, placeholder }) => {
{
event.stopPropagation();
diff --git a/packages/roomkit-react/src/Dropdown/Dropdown.tsx b/packages/roomkit-react/src/Dropdown/Dropdown.tsx
index 9a382a8c12..141d16af8a 100644
--- a/packages/roomkit-react/src/Dropdown/Dropdown.tsx
+++ b/packages/roomkit-react/src/Dropdown/Dropdown.tsx
@@ -53,7 +53,7 @@ const DropdownItem = styled(Item, {
display: 'flex',
alignItems: 'center',
outline: 'none',
- backgroundColor: '$surface_default',
+ backgroundColor: '$surface_dim',
'&:hover': {
cursor: 'pointer',
bg: '$surface_bright',
@@ -74,7 +74,7 @@ const DropdownContent = styled(Content, {
maxHeight: '$64',
r: '$1',
py: '$4',
- backgroundColor: '$surface_default',
+ backgroundColor: '$surface_dim',
overflowY: 'auto',
boxShadow: '0 10px 20px rgba(0,0,0,0.19), 0 6px 6px rgba(0,0,0,0.23)',
zIndex: 20,
diff --git a/packages/roomkit-react/src/Popover/index.tsx b/packages/roomkit-react/src/Popover/index.tsx
index e2768ed96d..9b4325a029 100644
--- a/packages/roomkit-react/src/Popover/index.tsx
+++ b/packages/roomkit-react/src/Popover/index.tsx
@@ -1,4 +1,4 @@
-import { Arrow, Content, Popover as Root, Portal, Trigger } from '@radix-ui/react-popover';
+import { Arrow, Close, Content, Popover as Root, Portal, Trigger } from '@radix-ui/react-popover';
import { styled } from '../Theme';
import { popoverAnimation } from '../utils/animations';
@@ -23,4 +23,5 @@ export const Popover = {
Trigger: StyledTrigger,
Portal: Portal,
Arrow: StyledArrow,
+ Close: Close,
};
diff --git a/packages/roomkit-react/src/Prebuilt/Prebuilt.stories.tsx b/packages/roomkit-react/src/Prebuilt/Prebuilt.stories.tsx
index 3468752840..b8920ca71a 100644
--- a/packages/roomkit-react/src/Prebuilt/Prebuilt.stories.tsx
+++ b/packages/roomkit-react/src/Prebuilt/Prebuilt.stories.tsx
@@ -6,7 +6,7 @@ export default {
title: 'UI Components/Prebuilt',
component: HMSPrebuilt,
argTypes: {
- roomCode: { control: { type: 'text' }, defaultValue: 'tsj-obqh-lwx' },
+ roomCode: { control: { type: 'text' }, defaultValue: 'cuf-wywo-trf' },
logo: { control: { type: 'object' }, defaultValue: undefined },
typography: { control: { type: 'object' }, defaultValue: 'Roboto' },
},
@@ -18,7 +18,7 @@ const PrebuiltRoomCodeStory: StoryFn = ({ roomCode = '', log
export const Example = PrebuiltRoomCodeStory.bind({});
Example.args = {
- roomCode: 'tsj-obqh-lwx',
+ roomCode: 'cuf-wywo-trf',
options: {
endpoints: {
roomLayout: 'https://demo8271564.mockable.io/v2/layouts/ui',
diff --git a/packages/roomkit-react/src/Prebuilt/common/hooks.js b/packages/roomkit-react/src/Prebuilt/common/hooks.js
index 60fc49c1d0..82df0d3278 100644
--- a/packages/roomkit-react/src/Prebuilt/common/hooks.js
+++ b/packages/roomkit-react/src/Prebuilt/common/hooks.js
@@ -1,6 +1,8 @@
// @ts-check
import { useEffect, useRef, useState } from 'react';
+import { JoinForm_JoinBtnType } from '@100mslive/types-prebuilt/elements/join_form';
import { selectAvailableRoleNames, selectIsConnectedToRoom, selectPeerCount, useHMSStore } from '@100mslive/react-sdk';
+import { useRoomLayout } from '../provider/roomLayoutProvider';
import { isInternalRole } from './utils';
/**
@@ -45,3 +47,9 @@ export const useFilteredRoles = () => {
const roles = useHMSStore(selectAvailableRoleNames).filter(role => !isInternalRole(role));
return roles;
};
+
+export const useShowStreamingUI = () => {
+ const layout = useRoomLayout();
+ const { join_form } = layout?.screens?.preview?.default?.elements || {};
+ return join_form?.join_btn_type === JoinForm_JoinBtnType.JOIN_BTN_TYPE_JOIN_AND_GO_LIVE;
+};
diff --git a/packages/roomkit-react/src/Prebuilt/common/utils.js b/packages/roomkit-react/src/Prebuilt/common/utils.js
index b3a876c703..819f001fda 100644
--- a/packages/roomkit-react/src/Prebuilt/common/utils.js
+++ b/packages/roomkit-react/src/Prebuilt/common/utils.js
@@ -83,3 +83,18 @@ export const getUpdatedHeight = (e, MINIMUM_HEIGHT) => {
const sheetHeightInVH = Math.max(MINIMUM_HEIGHT, heightToPercentage > 80 ? 100 : heightToPercentage);
return `${sheetHeightInVH}vh`;
};
+
+export const getFormattedCount = num => {
+ const formatter = new Intl.NumberFormat('en', { notation: 'compact', maximumFractionDigits: 2 });
+ const formattedNum = formatter.format(num);
+ return formattedNum;
+};
+
+export const formatTime = timeInSeconds => {
+ timeInSeconds = Math.floor(timeInSeconds / 1000);
+ const hours = Math.floor(timeInSeconds / 3600);
+ const minutes = Math.floor((timeInSeconds % 3600) / 60);
+ const seconds = timeInSeconds % 60;
+ const hour = hours !== 0 ? `${hours < 10 ? '0' : ''}${hours}:` : '';
+ return `${hour}${minutes < 10 ? '0' : ''}${minutes}:${seconds < 10 ? '0' : ''}${seconds}`;
+};
diff --git a/packages/roomkit-react/src/Prebuilt/components/AudioVideoToggle.jsx b/packages/roomkit-react/src/Prebuilt/components/AudioVideoToggle.jsx
index 4b68f82746..50b1bfd3ba 100644
--- a/packages/roomkit-react/src/Prebuilt/components/AudioVideoToggle.jsx
+++ b/packages/roomkit-react/src/Prebuilt/components/AudioVideoToggle.jsx
@@ -18,7 +18,7 @@ import { isMacOS } from '../common/constants';
const optionsCSS = { fontWeight: '$semiBold', color: '$on_surface_high', w: '100%', p: '$8' };
-export const AudioVideoToggle = () => {
+export const AudioVideoToggle = ({ hideOptions = false }) => {
const { allDevices, selectedDeviceIDs, updateDevice } = useDevices();
const { videoInput, audioInput } = allDevices;
@@ -68,33 +68,61 @@ export const AudioVideoToggle = () => {
return (
{toggleAudio ? (
- :
- }
- active={isLocalAudioEnabled}
- onClick={toggleAudio}
- key="toggleAudio"
- />
+ hideOptions ? (
+
+
+ {!isLocalAudioEnabled ? (
+
+ ) : (
+
+ )}
+
+
+ ) : (
+
+ ) : (
+
+ )
+ }
+ active={isLocalAudioEnabled}
+ onClick={toggleAudio}
+ key="toggleAudio"
+ />
+ )
) : null}
{toggleVideo ? (
-
- ) : (
-
- )
- }
- key="toggleVideo"
- active={isLocalVideoEnabled}
- onClick={toggleVideo}
- />
+ hideOptions ? (
+
+
+ {!isLocalVideoEnabled ? (
+
+ ) : (
+
+ )}
+
+
+ ) : (
+
+ ) : (
+
+ )
+ }
+ key="toggleVideo"
+ active={isLocalVideoEnabled}
+ onClick={toggleVideo}
+ />
+ )
) : null}
{localVideoTrack?.facingMode ? (
diff --git a/packages/roomkit-react/src/Prebuilt/components/BottomActionSheet/BottomActionSheet.jsx b/packages/roomkit-react/src/Prebuilt/components/BottomActionSheet/BottomActionSheet.jsx
deleted file mode 100644
index 8f339d15f8..0000000000
--- a/packages/roomkit-react/src/Prebuilt/components/BottomActionSheet/BottomActionSheet.jsx
+++ /dev/null
@@ -1,96 +0,0 @@
-import React, { useEffect, useRef, useState } from 'react';
-import { CrossIcon } from '@100mslive/react-icons';
-import { Box, Flex, Popover, Text } from '../../..';
-import { getUpdatedHeight } from '../../common/utils';
-
-const BottomActionSheet = ({
- title = '',
- children = <>>,
- triggerContent,
- containerCSS = {},
- // By default the component starts just above the trigger.
- // A negative offset allows it to start from the bottom of the screen.
- sideOffset = -50,
- defaultHeight = 50,
-}) => {
- const MINIMUM_HEIGHT = 40; // vh
- const [sheetOpen, setSheetOpen] = useState(false);
- const [sheetHeight, setSheetHeight] = useState(`${Math.min(Math.max(MINIMUM_HEIGHT, defaultHeight), 100)}vh`);
- const closeRef = useRef(null);
-
- // Close the sheet if height goes under MINIMUM_HEIGHT
- useEffect(() => {
- if (closeRef?.current && parseFloat(sheetHeight.slice(0, -2)) <= MINIMUM_HEIGHT) {
- setSheetOpen(false);
- // Delay for showing the opacity animation, can be removed if not needed
- setTimeout(() => closeRef.current?.click(), 200);
- }
- }, [sheetHeight]);
-
- return (
- <>
- {
- if (!open) {
- setSheetHeight('0');
- }
- setSheetOpen(open);
- }}
- >
- {triggerContent}
-
-
-
- {
- const updatedSheetHeight = getUpdatedHeight(e, MINIMUM_HEIGHT);
- setSheetHeight(updatedSheetHeight);
- }}
- css={{
- borderBottom: '1px solid $border_bright',
- px: '$8',
- pb: '$4',
- mb: '$4',
- w: '100%',
- }}
- >
-
- {title}
-
-
-
-
-
-
-
- {children}
-
-
-
-
- >
- );
-};
-
-export default BottomActionSheet;
diff --git a/packages/roomkit-react/src/Prebuilt/components/BottomActionSheet/BottomActionSheet.stories.tsx b/packages/roomkit-react/src/Prebuilt/components/BottomActionSheet/BottomActionSheet.stories.tsx
deleted file mode 100644
index 1c2caa4bcb..0000000000
--- a/packages/roomkit-react/src/Prebuilt/components/BottomActionSheet/BottomActionSheet.stories.tsx
+++ /dev/null
@@ -1,46 +0,0 @@
-import React, { ReactElement } from 'react';
-import { Meta } from '@storybook/react';
-import { Button } from '../../../Button';
-import { Box } from '../../../Layout';
-import { Text } from '../../../Text';
-import { CSS } from '../../../Theme';
-import BottomActionSheet from './BottomActionSheet';
-
-// WIP
-
-export default {
- title: 'Components/BottomActionSheet',
- component: BottomActionSheet,
- argTypes: {
- title: { control: 'text' },
- triggerContent: { control: 'jsx' },
- containerCSS: { control: 'object' },
- sideOffset: { control: 'number' },
- defaultHeight: { control: 'number' },
- },
-} as Meta;
-
-interface BottomActionSheetProps {
- title: string;
- triggerContent: ReactElement;
- children: ReactElement;
- containerCSS: CSS;
- sideOffset: number;
- defaultHeight: number;
-}
-
-const Template = (args: BottomActionSheetProps) => (
-
-
- This is the content of the BottomActionSheet.
- You can put any content you like here.
-
-
-);
-
-// Example story with default props
-export const Default = Template.bind({});
-Default.args = {
- title: 'Example BottomActionSheet',
- triggerContent: ,
-};
diff --git a/packages/roomkit-react/src/Prebuilt/components/Chat/ChatSelector.jsx b/packages/roomkit-react/src/Prebuilt/components/Chat/ChatSelector.jsx
index 5513c124d6..fe99845ce5 100644
--- a/packages/roomkit-react/src/Prebuilt/components/Chat/ChatSelector.jsx
+++ b/packages/roomkit-react/src/Prebuilt/components/Chat/ChatSelector.jsx
@@ -10,7 +10,7 @@ import {
} from '@100mslive/react-sdk';
import { CheckIcon } from '@100mslive/react-icons';
import { Box, Dropdown, Flex, HorizontalDivider, Text, Tooltip } from '../../../';
-import { ParticipantSearch } from '../Header/ParticipantList';
+import { ParticipantSearch } from '../Footer/ParticipantList';
import { useFilteredRoles } from '../../common/hooks';
const ChatDotIcon = () => {
diff --git a/packages/roomkit-react/src/Prebuilt/components/EmojiReaction.jsx b/packages/roomkit-react/src/Prebuilt/components/EmojiReaction.jsx
index 2024d5ec9b..2fa9c8751b 100644
--- a/packages/roomkit-react/src/Prebuilt/components/EmojiReaction.jsx
+++ b/packages/roomkit-react/src/Prebuilt/components/EmojiReaction.jsx
@@ -1,76 +1,25 @@
-import React, { Fragment, useCallback, useMemo, useState } from 'react';
+import React, { Fragment, useState } from 'react';
import data from '@emoji-mart/data/sets/14/apple.json';
import { init } from 'emoji-mart';
-import {
- selectAvailableRoleNames,
- selectIsConnectedToRoom,
- selectLocalPeerID,
- selectLocalPeerRoleName,
- useCustomEvent,
- useHMSActions,
- useHMSStore,
- useRecordingStreaming,
-} from '@100mslive/react-sdk';
+import { selectIsConnectedToRoom, useHMSStore } from '@100mslive/react-sdk';
import { EmojiIcon } from '@100mslive/react-icons';
+import { EmojiCard } from './Footer/EmojiCard';
import { Dropdown } from '../../Dropdown';
-import { Flex } from '../../Layout';
-import { Text } from '../../Text';
-import { styled } from '../../Theme';
import { Tooltip } from '../../Tooltip';
import IconButton from '../IconButton';
-import { useHLSViewerRole } from './AppData/useUISettings';
import { useDropdownList } from './hooks/useDropdownList';
import { useIsFeatureEnabled } from './hooks/useFeatures';
-import { EMOJI_REACTION_TYPE, FEATURE_LIST, HLS_TIMED_METADATA_DOC_URL } from '../common/constants';
+import { FEATURE_LIST } from '../common/constants';
init({ data });
-// When changing emojis in the grid, keep in mind that the payload used in sendHLSTimedMetadata has a limit of 100 characters. Using bigger emoji Ids can cause the limit to be exceeded.
-const emojiReactionList = [
- [{ emojiId: '+1' }, { emojiId: '-1' }, { emojiId: 'wave' }, { emojiId: 'clap' }, { emojiId: 'fire' }],
- [{ emojiId: 'tada' }, { emojiId: 'heart_eyes' }, { emojiId: 'joy' }, { emojiId: 'open_mouth' }, { emojiId: 'sob' }],
-];
-
export const EmojiReaction = () => {
const [open, setOpen] = useState(false);
- const hmsActions = useHMSActions();
const isConnected = useHMSStore(selectIsConnectedToRoom);
- const roles = useHMSStore(selectAvailableRoleNames);
- const localPeerRole = useHMSStore(selectLocalPeerRoleName);
- const localPeerId = useHMSStore(selectLocalPeerID);
- const hlsViewerRole = useHLSViewerRole();
- const { isStreamingOn } = useRecordingStreaming();
const isFeatureEnabled = useIsFeatureEnabled(FEATURE_LIST.EMOJI_REACTION);
- const filteredRoles = useMemo(() => roles.filter(role => role !== hlsViewerRole), [roles, hlsViewerRole]);
useDropdownList({ open: open, name: 'EmojiReaction' });
- const onEmojiEvent = useCallback(data => {
- window.showFlyingEmoji(data?.emojiId, data?.senderId);
- }, []);
-
- const { sendEvent } = useCustomEvent({
- type: EMOJI_REACTION_TYPE,
- onEvent: onEmojiEvent,
- });
-
- const sendReaction = async emojiId => {
- const data = {
- type: EMOJI_REACTION_TYPE,
- emojiId: emojiId,
- senderId: localPeerId,
- };
- sendEvent(data, { roleNames: filteredRoles });
- if (isStreamingOn) {
- await hmsActions.sendHLSTimedMetadata([
- {
- payload: JSON.stringify(data),
- duration: 2,
- },
- ]);
- }
- };
-
- if (!isConnected || localPeerRole === hlsViewerRole || !isFeatureEnabled) {
+ if (!isConnected || !isFeatureEnabled) {
return null;
}
return (
@@ -84,58 +33,9 @@ export const EmojiReaction = () => {
- {emojiReactionList.map((emojiLine, index) => (
-
- {emojiLine.map(emoji => (
- sendReaction(emoji.emojiId)}>
-
-
- ))}
-
- ))}
-
-
- Reactions will be timed for Live Streaming viewers.{' '}
-
-
-
- Learn more.
-
-
-
+
);
};
-
-const EmojiContainer = styled('span', {
- position: 'relative',
- cursor: 'pointer',
- width: '$16',
- height: '$16',
- p: '$4',
- '&:hover': {
- p: '7px',
- bg: '$surface_brighter',
- borderRadius: '$1',
- },
-});
diff --git a/packages/roomkit-react/src/Prebuilt/components/EndSessionContent.jsx b/packages/roomkit-react/src/Prebuilt/components/EndSessionContent.jsx
new file mode 100644
index 0000000000..865c79104b
--- /dev/null
+++ b/packages/roomkit-react/src/Prebuilt/components/EndSessionContent.jsx
@@ -0,0 +1,48 @@
+import React from 'react';
+import { AlertTriangleIcon, CrossIcon } from '@100mslive/react-icons';
+import { Button } from '../../Button';
+import { Box, Flex } from '../../Layout';
+import { Text } from '../../Text';
+import { useShowStreamingUI } from '../common/hooks';
+
+export const EndSessionContent = ({ setShowEndRoomAlert, endRoom, isModal = false }) => {
+ const showStreamingUI = useShowStreamingUI();
+ return (
+
+
+
+
+ End {showStreamingUI ? 'Stream' : 'Session'}
+
+ {isModal ? null : (
+ setShowEndRoomAlert(false)}>
+
+
+ )}
+
+
+ The {showStreamingUI ? 'stream' : 'session'} will end for everyone and all the activities will stop. You can't
+ undo this action.
+
+
+
+
+
+
+ );
+};
diff --git a/packages/roomkit-react/src/Prebuilt/components/Footer.jsx b/packages/roomkit-react/src/Prebuilt/components/Footer.jsx
index 03f8f564b7..c205d31b12 100644
--- a/packages/roomkit-react/src/Prebuilt/components/Footer.jsx
+++ b/packages/roomkit-react/src/Prebuilt/components/Footer.jsx
@@ -1,8 +1,9 @@
import React from 'react';
import { ConferencingFooter } from './Footer/ConferencingFooter';
import { StreamingFooter } from './Footer/StreamingFooter';
-import { isStreamingKit } from '../common/utils';
+import { useShowStreamingUI } from '../common/hooks';
export const Footer = () => {
- return isStreamingKit() ? : ;
+ const showStreamingUI = useShowStreamingUI();
+ return showStreamingUI ? : ;
};
diff --git a/packages/roomkit-react/src/Prebuilt/components/Footer/ConferencingFooter.jsx b/packages/roomkit-react/src/Prebuilt/components/Footer/ConferencingFooter.jsx
index fa169876e6..51c3d28cf4 100644
--- a/packages/roomkit-react/src/Prebuilt/components/Footer/ConferencingFooter.jsx
+++ b/packages/roomkit-react/src/Prebuilt/components/Footer/ConferencingFooter.jsx
@@ -1,101 +1,57 @@
-import React, { Fragment, Suspense, useState } from 'react';
+import React, { Suspense } from 'react';
import { useMedia } from 'react-use';
-import { selectIsAllowedToPublish, useHMSStore, useScreenShare } from '@100mslive/react-sdk';
-import { MusicIcon } from '@100mslive/react-icons';
-import { config as cssConfig, Flex, Footer as AppFooter, Tooltip } from '../../../';
-import IconButton from '../../IconButton';
+import { selectIsLocalVideoEnabled, useHMSStore } from '@100mslive/react-sdk';
+import { config as cssConfig, Footer as AppFooter } from '../../../';
import { AudioVideoToggle } from '../AudioVideoToggle';
import { EmojiReaction } from '../EmojiReaction';
import { LeaveRoom } from '../LeaveRoom';
-import MetaActions from '../MetaActions';
import { MoreSettings } from '../MoreSettings/MoreSettings';
-import { PIP } from '../PIP';
import { ScreenshareToggle } from '../ScreenShare';
-import { ScreenShareHintModal } from '../ScreenshareHintModal';
import { ChatToggle } from './ChatToggle';
-import { useIsFeatureEnabled } from '../hooks/useFeatures';
-import { isScreenshareSupported } from '../../common/utils';
+import { ParticipantCount } from './ParticipantList';
import { FeatureFlags } from '../../services/FeatureFlags';
-import { FEATURE_LIST } from '../../common/constants';
const TranscriptionButton = React.lazy(() => import('../../plugins/transcription'));
const VirtualBackground = React.lazy(() => import('../../plugins/VirtualBackground/VirtualBackground'));
-const ScreenshareAudio = () => {
- const {
- amIScreenSharing,
- screenShareVideoTrackId: video,
- screenShareAudioTrackId: audio,
- toggleScreenShare,
- } = useScreenShare();
- const isAllowedToPublish = useHMSStore(selectIsAllowedToPublish);
- const isAudioScreenshare = amIScreenSharing && !video && !!audio;
- const [showModal, setShowModal] = useState(false);
- const isFeatureEnabled = useIsFeatureEnabled(FEATURE_LIST.AUDIO_ONLY_SCREENSHARE);
- if (!isFeatureEnabled || !isAllowedToPublish.screen || !isScreenshareSupported()) {
- return null;
- }
- return (
-
-
- {
- if (amIScreenSharing) {
- toggleScreenShare();
- } else {
- setShowModal(true);
- }
- }}
- data-testid="screenshare_audio"
- >
-
-
-
- {showModal && setShowModal(false)} />}
-
- );
-};
-
export const ConferencingFooter = () => {
const isMobile = useMedia(cssConfig.media.md);
+ const isVideoOn = useHMSStore(selectIsLocalVideoEnabled);
+
return (
-
-
-
-
-
-
- {FeatureFlags.enableTranscription ? : null}
-
- {isMobile && }
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {!isMobile && }
-
-
-
+
+ {isMobile ? (
+ <>
+
+
+
+
+
+
+ >
+ ) : (
+ <>
+
+ {isVideoOn ? (
+
+
+
+ ) : null}
+ {FeatureFlags.enableTranscription ? : null}
+
+
+
+
+
+
+
+
+
+
+
+
+ >
+ )}
);
};
diff --git a/packages/roomkit-react/src/Prebuilt/components/Footer/EmojiCard.jsx b/packages/roomkit-react/src/Prebuilt/components/Footer/EmojiCard.jsx
new file mode 100644
index 0000000000..09a2a0896b
--- /dev/null
+++ b/packages/roomkit-react/src/Prebuilt/components/Footer/EmojiCard.jsx
@@ -0,0 +1,80 @@
+import React, { useCallback, useMemo } from 'react';
+import data from '@emoji-mart/data/sets/14/apple.json';
+import { init } from 'emoji-mart';
+import {
+ selectAvailableRoleNames,
+ selectLocalPeerID,
+ useCustomEvent,
+ useHMSActions,
+ useHMSStore,
+ useRecordingStreaming,
+} from '@100mslive/react-sdk';
+import { Flex } from '../../../Layout';
+import { styled } from '../../../Theme';
+import { useHLSViewerRole } from '../AppData/useUISettings';
+import { EMOJI_REACTION_TYPE } from '../../common/constants';
+
+init({ data });
+
+// When changing emojis in the grid, keep in mind that the payload used in sendHLSTimedMetadata has a limit of 100 characters. Using bigger emoji Ids can cause the limit to be exceeded.
+const emojiReactionList = [
+ [{ emojiId: '+1' }, { emojiId: '-1' }, { emojiId: 'wave' }, { emojiId: 'clap' }, { emojiId: 'fire' }],
+ [{ emojiId: 'tada' }, { emojiId: 'heart_eyes' }, { emojiId: 'joy' }, { emojiId: 'open_mouth' }, { emojiId: 'sob' }],
+];
+
+export const EmojiCard = () => {
+ const hmsActions = useHMSActions();
+ const roles = useHMSStore(selectAvailableRoleNames);
+ const localPeerId = useHMSStore(selectLocalPeerID);
+ const hlsViewerRole = useHLSViewerRole();
+ const { isStreamingOn } = useRecordingStreaming();
+ const filteredRoles = useMemo(() => roles.filter(role => role !== hlsViewerRole), [roles, hlsViewerRole]);
+
+ const onEmojiEvent = useCallback(data => {
+ window.showFlyingEmoji(data?.emojiId, data?.senderId);
+ }, []);
+
+ const { sendEvent } = useCustomEvent({
+ type: EMOJI_REACTION_TYPE,
+ onEvent: onEmojiEvent,
+ });
+
+ const sendReaction = async emojiId => {
+ const data = {
+ type: EMOJI_REACTION_TYPE,
+ emojiId: emojiId,
+ senderId: localPeerId,
+ };
+ sendEvent(data, { roleNames: filteredRoles });
+ if (isStreamingOn) {
+ await hmsActions.sendHLSTimedMetadata([
+ {
+ payload: JSON.stringify(data),
+ duration: 2,
+ },
+ ]);
+ }
+ };
+ return emojiReactionList.map((emojiLine, index) => (
+
+ {emojiLine.map(emoji => (
+ sendReaction(emoji.emojiId)}>
+
+
+ ))}
+
+ ));
+};
+
+const EmojiContainer = styled('span', {
+ position: 'relative',
+ cursor: 'pointer',
+ width: '$16',
+ height: '$16',
+ p: '$4',
+ '&:hover': {
+ p: '7px',
+ bg: '$surface_brighter',
+ borderRadius: '$1',
+ },
+});
diff --git a/packages/roomkit-react/src/Prebuilt/components/Header/ParticipantList.jsx b/packages/roomkit-react/src/Prebuilt/components/Footer/ParticipantList.jsx
similarity index 97%
rename from packages/roomkit-react/src/Prebuilt/components/Header/ParticipantList.jsx
rename to packages/roomkit-react/src/Prebuilt/components/Footer/ParticipantList.jsx
index eaed314876..c528cc08d9 100644
--- a/packages/roomkit-react/src/Prebuilt/components/Header/ParticipantList.jsx
+++ b/packages/roomkit-react/src/Prebuilt/components/Footer/ParticipantList.jsx
@@ -21,11 +21,11 @@ import {
SpeakerIcon,
VerticalMenuIcon,
} from '@100mslive/react-icons';
-import { Avatar, Box, Dropdown, Flex, Input, Slider, Text, textEllipsis } from '../../../';
+import { Avatar, Box, Dropdown, Flex, Input, Slider, Text, textEllipsis } from '../../..';
import IconButton from '../../IconButton';
import { ConnectionIndicator } from '../Connection/ConnectionIndicator';
+import { ParticipantFilter } from '../Header/ParticipantFilter';
import { RoleChangeModal } from '../RoleChangeModal';
-import { ParticipantFilter } from './ParticipantFilter';
import { useIsSidepaneTypeOpen, useSidepaneToggle } from '../AppData/useSidepane';
import { isInternalRole } from '../../common/utils';
import { SIDE_PANE_OPTIONS } from '../../common/constants';
@@ -320,8 +320,8 @@ export const ParticipantSearch = ({ onSearch, placeholder }) => {
{
event.stopPropagation();
diff --git a/packages/roomkit-react/src/Prebuilt/components/Footer/StreamingFooter.jsx b/packages/roomkit-react/src/Prebuilt/components/Footer/StreamingFooter.jsx
index a1addf548f..83708e75c9 100644
--- a/packages/roomkit-react/src/Prebuilt/components/Footer/StreamingFooter.jsx
+++ b/packages/roomkit-react/src/Prebuilt/components/Footer/StreamingFooter.jsx
@@ -1,22 +1,23 @@
import React from 'react';
-import { Box, Flex, Footer as AppFooter } from '../../../';
+import { useMedia } from 'react-use';
+import { config as cssConfig, Footer as AppFooter } from '../../../';
import { AudioVideoToggle } from '../AudioVideoToggle';
import { EmojiReaction } from '../EmojiReaction';
-import { StreamActions } from '../Header/StreamActions';
import { LeaveRoom } from '../LeaveRoom';
-import MetaActions from '../MetaActions';
import { MoreSettings } from '../MoreSettings/MoreSettings';
-import { PIP } from '../PIP';
import { ScreenshareToggle } from '../ScreenShare';
import { ChatToggle } from './ChatToggle';
+import { ParticipantCount } from './ParticipantList';
export const StreamingFooter = () => {
+ const isMobile = useMedia(cssConfig.media.md);
return (
@@ -25,46 +26,38 @@ export const StreamingFooter = () => {
'@md': {
w: 'unset',
p: '0',
+ gap: '$10',
},
}}
>
-
+ {isMobile ? : null}
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+ {isMobile ? (
+ <>
+
+
+ >
+ ) : (
+ <>
+
+
+
+ >
+ )}
-
-
+
+
);
diff --git a/packages/roomkit-react/src/Prebuilt/components/Header/ConferencingHeader.jsx b/packages/roomkit-react/src/Prebuilt/components/Header/ConferencingHeader.jsx
index 6f68d1570f..a5cc671816 100644
--- a/packages/roomkit-react/src/Prebuilt/components/Header/ConferencingHeader.jsx
+++ b/packages/roomkit-react/src/Prebuilt/components/Header/ConferencingHeader.jsx
@@ -1,16 +1,42 @@
import React from 'react';
-import { Flex } from '../../../';
-import { SpeakerTag } from './HeaderComponents';
-import { ParticipantCount } from './ParticipantList';
+import { useMedia } from 'react-use';
+import { HMSRoomState, selectRoomState, useHMSStore } from '@100mslive/react-sdk';
+import { config as cssConfig, Flex, VerticalDivider } from '../../../';
+import { Logo, SpeakerTag } from './HeaderComponents';
import { StreamActions } from './StreamActions';
+import { AudioOutputActions, CamaraFlipActions } from './common';
export const ConferencingHeader = () => {
+ const roomState = useHMSStore(selectRoomState);
+ const isMobile = useMedia(cssConfig.media.md);
+ const isPreview = roomState === HMSRoomState.Preview;
+
+ if (isPreview) {
+ return (
+
+
+
+
+
+
+
+
+ );
+ }
return (
+
+
-
{
}}
>
-
+ {isMobile && (
+ <>
+
+ {' '}
+ >
+ )}
);
diff --git a/packages/roomkit-react/src/Prebuilt/components/Header/StreamActions.jsx b/packages/roomkit-react/src/Prebuilt/components/Header/StreamActions.jsx
index 3813f9ac7f..a062b0d6ec 100644
--- a/packages/roomkit-react/src/Prebuilt/components/Header/StreamActions.jsx
+++ b/packages/roomkit-react/src/Prebuilt/components/Header/StreamActions.jsx
@@ -1,7 +1,8 @@
-import React, { Fragment, useState } from 'react';
+import React, { useCallback, useEffect, useRef, useState } from 'react';
import { useMedia } from 'react-use';
import {
HMSRoomState,
+ selectHLSState,
selectIsConnectedToRoom,
selectPermissions,
selectRoomState,
@@ -9,30 +10,69 @@ import {
useHMSStore,
useRecordingStreaming,
} from '@100mslive/react-sdk';
-import { RecordIcon, WrenchIcon } from '@100mslive/react-icons';
-import { Box, Button, config as cssConfig, Flex, Loading, Popover, Text, Tooltip } from '../../../';
-import GoLiveButton from '../GoLiveButton';
+import { AlertTriangleIcon, CrossIcon, RecordIcon } from '@100mslive/react-icons';
+import { Box, Button, config as cssConfig, Flex, HorizontalDivider, Loading, Popover, Text, Tooltip } from '../../../';
+import { Sheet } from '../../../Sheet';
import { ResolutionInput } from '../Streaming/ResolutionInput';
import { getResolution } from '../Streaming/RTMPStreaming';
import { ToastManager } from '../Toast/ToastManager';
import { AdditionalRoomState, getRecordingText } from './AdditionalRoomState';
-import { useSidepaneToggle } from '../AppData/useSidepane';
import { useSetAppDataByKey } from '../AppData/useUISettings';
-import { APP_DATA, RTMP_RECORD_DEFAULT_RESOLUTION, SIDE_PANE_OPTIONS } from '../../common/constants';
+import { formatTime } from '../../common/utils';
+import { APP_DATA, RTMP_RECORD_DEFAULT_RESOLUTION } from '../../common/constants';
export const LiveStatus = () => {
const { isHLSRunning, isRTMPRunning } = useRecordingStreaming();
+ const hlsState = useHMSStore(selectHLSState);
+ const isMobile = useMedia(cssConfig.media.md);
+ const intervalRef = useRef(null);
+
+ const [liveTime, setLiveTime] = useState(0);
+
+ const startTimer = useCallback(() => {
+ intervalRef.current = setInterval(() => {
+ if (hlsState?.running) {
+ setLiveTime(Date.now() - hlsState?.variants[0]?.startedAt.getTime());
+ }
+ }, 1000);
+ }, [hlsState?.running, hlsState?.variants]);
+
+ useEffect(() => {
+ if (hlsState?.running && !isMobile) {
+ startTimer();
+ }
+ if (!hlsState?.running && intervalRef.current) {
+ clearInterval(intervalRef.current);
+ }
+ return () => {
+ if (intervalRef.current) {
+ clearInterval(intervalRef.current);
+ }
+ };
+ }, [hlsState.running, isMobile, startTimer]);
+
if (!isHLSRunning && !isRTMPRunning) {
return null;
}
return (
-
-
-
- Live
-
- with {isHLSRunning ? 'HLS' : 'RTMP'}
+
+
+ {isMobile ? (
+
+ Live
+ ) : (
+ LIVE
+ )}
+
+ {hlsState?.variants?.length > 0 ? formatTime(liveTime) : ''}
);
@@ -41,6 +81,7 @@ export const LiveStatus = () => {
export const RecordingStatus = () => {
const { isBrowserRecordingOn, isServerRecordingOn, isHLSRecordingOn, isRecordingOn } = useRecordingStreaming();
const permissions = useHMSStore(selectPermissions);
+ const isMobile = useMedia(cssConfig.media.md);
if (
!isRecordingOn ||
@@ -50,8 +91,10 @@ export const RecordingStatus = () => {
value => !!value,
)
) {
- return null;
+ // show recording icon in mobile without popover
+ if (!(isMobile && isRecordingOn)) return null;
}
+
return (
{
isHLSRecordingOn,
})}
>
-
-
+
);
};
-const EndStream = () => {
- const toggleStreaming = useSidepaneToggle(SIDE_PANE_OPTIONS.STREAMING);
-
- return (
-
- );
-};
-
const StartRecording = () => {
const permissions = useHMSStore(selectPermissions);
const [resolution, setResolution] = useState(RTMP_RECORD_DEFAULT_RESOLUTION);
@@ -192,24 +225,57 @@ const StartRecording = () => {
);
};
+/**
+ * @description only start recording button will be shown.
+ */
export const StreamActions = () => {
const isConnected = useHMSStore(selectIsConnectedToRoom);
- const permissions = useHMSStore(selectPermissions);
const isMobile = useMedia(cssConfig.media.md);
- const { isStreamingOn } = useRecordingStreaming();
const roomState = useHMSStore(selectRoomState);
return (
-
- {roomState !== HMSRoomState.Preview ? : null}
+
+ {roomState !== HMSRoomState.Preview ? : null}
{isConnected && !isMobile ? : null}
- {isConnected && (permissions.hlsStreaming || permissions.rtmpStreaming) && (
- {isStreamingOn ? : }
- )}
);
};
+
+export const StopRecordingInSheet = ({ onStopRecording, onClose }) => {
+ return (
+
+
+
+
+
+
+ Stop Recording
+
+
+
+
+
+
+
+
+
+ Are you sure you want to stop recording? You can’t undo this action.
+
+
+
+
+
+ );
+};
diff --git a/packages/roomkit-react/src/Prebuilt/components/Header/StreamingHeader.jsx b/packages/roomkit-react/src/Prebuilt/components/Header/StreamingHeader.jsx
index 71dd67fb2c..de16f09399 100644
--- a/packages/roomkit-react/src/Prebuilt/components/Header/StreamingHeader.jsx
+++ b/packages/roomkit-react/src/Prebuilt/components/Header/StreamingHeader.jsx
@@ -2,10 +2,10 @@ import React from 'react';
import { useMedia } from 'react-use';
import { config as cssConfig, Flex } from '../../../';
import { EmojiReaction } from '../EmojiReaction';
+import { ParticipantCount } from '../Footer/ParticipantList';
import { LeaveRoom } from '../LeaveRoom';
import MetaActions from '../MetaActions';
import { SpeakerTag } from './HeaderComponents';
-import { ParticipantCount } from './ParticipantList';
import { LiveStatus, RecordingStatus, StreamActions } from './StreamActions';
export const StreamingHeader = () => {
diff --git a/packages/roomkit-react/src/Prebuilt/components/Header/common.jsx b/packages/roomkit-react/src/Prebuilt/components/Header/common.jsx
new file mode 100644
index 0000000000..bf2c575c0d
--- /dev/null
+++ b/packages/roomkit-react/src/Prebuilt/components/Header/common.jsx
@@ -0,0 +1,164 @@
+import React from 'react';
+import {
+ DeviceType,
+ selectIsLocalVideoEnabled,
+ selectLocalVideoTrackID,
+ selectVideoTrackByID,
+ useDevices,
+ useHMSActions,
+ useHMSStore,
+} from '@100mslive/react-sdk';
+import { CameraFlipIcon, CheckIcon, CrossIcon, SpeakerIcon } from '@100mslive/react-icons';
+import { HorizontalDivider } from '../../../Divider';
+import { Label } from '../../../Label';
+import { Box, Flex } from '../../../Layout';
+import { Sheet } from '../../../Sheet';
+import { Text } from '../../../Text';
+import IconButton from '../../IconButton';
+import { ToastManager } from '../Toast/ToastManager';
+
+export const CamaraFlipActions = () => {
+ const actions = useHMSActions();
+ const { allDevices } = useDevices();
+ const { videoInput } = allDevices;
+ const isVideoOn = useHMSStore(selectIsLocalVideoEnabled);
+
+ const videoTrackId = useHMSStore(selectLocalVideoTrackID);
+ const localVideoTrack = useHMSStore(selectVideoTrackByID(videoTrackId));
+
+ return (
+
+ {
+ try {
+ await actions.switchCamera();
+ } catch (e) {
+ ToastManager.addToast({
+ title: `Error while flipping camera ${e.message || ''}`,
+ variant: 'error',
+ });
+ }
+ }}
+ >
+
+
+
+ );
+};
+
+export const AudioOutputActions = () => {
+ const { allDevices, selectedDeviceIDs, updateDevice } = useDevices();
+ const { audioOutput } = allDevices;
+ // don't show speaker selector where the API is not supported, and use
+ // a generic word("Audio") for Mic. In some cases(Chrome Android for e.g.) this changes both mic and speaker keeping them in sync.
+ const shouldShowAudioOutput = 'setSinkId' in HTMLMediaElement.prototype;
+
+ /**
+ * Chromium browsers return an audioOutput with empty label when no permissions are given
+ */
+ const audioOutputFiltered = audioOutput?.filter(item => !!item.label) ?? [];
+ if (!shouldShowAudioOutput || !audioOutputFiltered?.length > 0) {
+ return null;
+ }
+ return (
+ {
+ try {
+ await updateDevice({
+ deviceId,
+ deviceType: DeviceType.audioOutput,
+ });
+ } catch (e) {
+ ToastManager.addToast({
+ title: `Error while changing audio output ${e.message || ''}`,
+ variant: 'error',
+ });
+ }
+ }}
+ >
+
+
+
+
+
+
+ );
+};
+
+const AudioOutputSelectionSheet = ({ outputDevices, outputSelected, onChange, children }) => {
+ return (
+
+ {children}
+
+
+
+
+ Audio Output
+
+
+
+
+
+
+
+
+
+
+ {outputDevices.map(audioDevice => {
+ return (
+ onChange(audioDevice.deviceId)}
+ />
+ );
+ })}
+
+
+
+ );
+};
+
+const SelectWithLabel = ({ label, icon = <>>, checked, id, onChange }) => {
+ return (
+
+
+ {checked && }
+
+ );
+};
diff --git a/packages/roomkit-react/src/Prebuilt/components/LeaveCard.jsx b/packages/roomkit-react/src/Prebuilt/components/LeaveCard.jsx
new file mode 100644
index 0000000000..32beacc46e
--- /dev/null
+++ b/packages/roomkit-react/src/Prebuilt/components/LeaveCard.jsx
@@ -0,0 +1,19 @@
+import React from 'react';
+import { Box, Flex } from '../../Layout';
+import { Text } from '../../Text';
+
+export const LeaveCard = ({ icon, title, subtitle, onClick, bg, titleColor, subtitleColor, css = {} }) => {
+ return (
+
+ {icon}
+
+
+ {title}
+
+
+ {subtitle}
+
+
+
+ );
+};
diff --git a/packages/roomkit-react/src/Prebuilt/components/LeaveRoom.jsx b/packages/roomkit-react/src/Prebuilt/components/LeaveRoom.jsx
index c7d2c508ce..d851cf3f5c 100644
--- a/packages/roomkit-react/src/Prebuilt/components/LeaveRoom.jsx
+++ b/packages/roomkit-react/src/Prebuilt/components/LeaveRoom.jsx
@@ -1,33 +1,24 @@
-import React, { Fragment, useState } from 'react';
+import React from 'react';
import { useParams } from 'react-router-dom';
+import { useMedia } from 'react-use';
import { selectIsConnectedToRoom, selectPermissions, useHMSActions, useHMSStore } from '@100mslive/react-sdk';
-import { AlertTriangleIcon, ExitIcon, HangUpIcon, VerticalMenuIcon } from '@100mslive/react-icons';
+import { DesktopLeaveRoom } from './MoreSettings/SplitComponents/DesktopLeaveRoom';
+import { MwebLeaveRoom } from './MoreSettings/SplitComponents/MwebLeaveRoom';
import { ToastManager } from './Toast/ToastManager';
-import { Button } from '../../Button';
-import { Dropdown } from '../../Dropdown';
import { IconButton } from '../../IconButton';
-import { Box, Flex } from '../../Layout';
-import { Dialog } from '../../Modal';
-import { Text } from '../../Text';
-import { styled } from '../../Theme';
-import { Tooltip } from '../../Tooltip';
+import { config as cssConfig, styled } from '../../Theme';
import { useHMSPrebuiltContext } from '../AppContext';
-import { DialogCheckbox, DialogContent, DialogRow } from '../primitives/DialogContent';
-import { useDropdownList } from './hooks/useDropdownList';
import { useNavigation } from './hooks/useNavigation';
-import { isStreamingKit } from '../common/utils';
export const LeaveRoom = () => {
const navigate = useNavigation();
const params = useParams();
- const [open, setOpen] = useState(false);
- const [showEndRoomModal, setShowEndRoomModal] = useState(false);
- const [lockRoom, setLockRoom] = useState(false);
const isConnected = useHMSStore(selectIsConnectedToRoom);
const permissions = useHMSStore(selectPermissions);
+ const isMobile = useMedia(cssConfig.media.md);
+
const hmsActions = useHMSActions();
const { showLeave, onLeave } = useHMSPrebuiltContext();
- useDropdownList({ open, name: 'LeaveRoom' });
const redirectToLeavePage = () => {
if (showLeave) {
@@ -47,129 +38,22 @@ export const LeaveRoom = () => {
};
const endRoom = () => {
- hmsActions.endRoom(lockRoom, 'End Room');
+ hmsActions.endRoom(false, 'End Room');
redirectToLeavePage();
};
- const isStreamKit = isStreamingKit();
if (!permissions || !isConnected) {
return null;
}
-
- return (
-
- {permissions.endRoom ? (
-
-
-
- {!isStreamKit ? (
-
-
-
- ) : (
-
-
-
-
-
- Leave Studio
-
-
- )}
-
-
-
-
-
-
-
-
-
- {
- setShowEndRoomModal(true);
- }}
- data-testid="end_room_btn"
- >
-
-
-
-
-
-
- End Room for All
-
-
- Warning: You can’t undo this action
-
-
-
-
-
-
-
-
-
-
- Leave {isStreamKit ? 'Studio' : 'Room'}
-
- You can always rejoin later
-
-
-
-
-
-
-
- ) : (
-
-
-
- {isStreamKit ? (
-
-
-
- ) : (
-
- )}
-
-
-
- )}
-
- {
- if (!value) {
- setLockRoom(false);
- }
- setShowEndRoomModal(value);
- }}
- >
-
-
-
-
-
-
-
-
+ return isMobile ? (
+
+ ) : (
+
);
};
@@ -180,7 +64,7 @@ const LeaveIconButton = styled(IconButton, {
r: '$1',
bg: '$alert_error_default',
'&:not([disabled]):hover': {
- bg: '$alert_error_default',
+ bg: '$alert_error_bright',
},
'&:not([disabled]):active': {
bg: '$alert_error_default',
diff --git a/packages/roomkit-react/src/Prebuilt/components/MoreSettings/ActionTile.jsx b/packages/roomkit-react/src/Prebuilt/components/MoreSettings/ActionTile.jsx
new file mode 100644
index 0000000000..962ac2b85a
--- /dev/null
+++ b/packages/roomkit-react/src/Prebuilt/components/MoreSettings/ActionTile.jsx
@@ -0,0 +1,33 @@
+import React from 'react';
+import { Flex } from '../../../Layout';
+import { Text } from '../../../Text';
+
+export const ActionTile = ({ icon, title, active, onClick, disabled = false, setOpenOptionsSheet }) => {
+ return (
+ {
+ if (!disabled) {
+ onClick();
+ setOpenOptionsSheet(false);
+ }
+ }}
+ css={{
+ p: '$4 $2',
+ bg: active ? '$surface_bright' : '',
+ color: disabled ? '$on_surface_low' : '$on_surface_high',
+ gap: '$4',
+ r: '$1',
+ '&:hover': {
+ bg: '$surface_bright',
+ },
+ }}
+ >
+ {icon}
+
+ {title}
+
+
+ );
+};
diff --git a/packages/roomkit-react/src/Prebuilt/components/MoreSettings/ChangeNameContent.jsx b/packages/roomkit-react/src/Prebuilt/components/MoreSettings/ChangeNameContent.jsx
new file mode 100644
index 0000000000..af1b3dc6ff
--- /dev/null
+++ b/packages/roomkit-react/src/Prebuilt/components/MoreSettings/ChangeNameContent.jsx
@@ -0,0 +1,90 @@
+import React from 'react';
+import { ChevronLeftIcon, CrossIcon } from '@100mslive/react-icons';
+import { Button } from '../../../Button';
+import { Input } from '../../../Input';
+import { Box, Flex } from '../../../Layout';
+import { Text } from '../../../Text';
+
+export const ChangeNameContent = ({
+ changeName,
+ setCurrentName,
+ currentName,
+ localPeerName,
+ isMobile,
+ onExit,
+ onBackClick,
+}) => {
+ return (
+
+ );
+};
diff --git a/packages/roomkit-react/src/Prebuilt/components/MoreSettings/ChangeNameModal.jsx b/packages/roomkit-react/src/Prebuilt/components/MoreSettings/ChangeNameModal.jsx
index d0e99e667b..bcdeb9eae3 100644
--- a/packages/roomkit-react/src/Prebuilt/components/MoreSettings/ChangeNameModal.jsx
+++ b/packages/roomkit-react/src/Prebuilt/components/MoreSettings/ChangeNameModal.jsx
@@ -1,14 +1,18 @@
import React, { useState } from 'react';
+import { useMedia } from 'react-use';
import { selectLocalPeerName, useHMSActions, useHMSStore } from '@100mslive/react-sdk';
-import { Box, Button, Dialog, Flex, Input, Text } from '../../../';
+import { config as cssConfig, Dialog } from '../../../';
+import { Sheet } from '../../../Sheet';
import { ToastManager } from '../Toast/ToastManager';
+import { ChangeNameContent } from './ChangeNameContent';
import { UserPreferencesKeys, useUserPreferences } from '../hooks/useUserPreferences';
-export const ChangeNameModal = ({ onOpenChange }) => {
+export const ChangeNameModal = ({ onOpenChange, openParentSheet = null }) => {
const [previewPreference, setPreviewPreference] = useUserPreferences(UserPreferencesKeys.PREVIEW);
const hmsActions = useHMSActions();
const localPeerName = useHMSStore(selectLocalPeerName);
const [currentName, setCurrentName] = useState(localPeerName);
+ const isMobile = useMedia(cssConfig.media.md);
const changeName = async () => {
const name = currentName.trim();
@@ -29,62 +33,35 @@ export const ChangeNameModal = ({ onOpenChange }) => {
}
};
+ const props = {
+ changeName,
+ setCurrentName,
+ currentName,
+ localPeerName,
+ isMobile,
+ onExit: () => onOpenChange(false),
+ onBackClick: () => {
+ onOpenChange(false);
+ openParentSheet();
+ },
+ };
+
+ if (isMobile) {
+ return (
+
+
+
+
+
+ );
+ }
+
return (
-
-
- Change Name
-
-
+
+
diff --git a/packages/roomkit-react/src/Prebuilt/components/MoreSettings/EmbedUrl.jsx b/packages/roomkit-react/src/Prebuilt/components/MoreSettings/EmbedUrl.jsx
index 577a82edff..7e7ba13350 100644
--- a/packages/roomkit-react/src/Prebuilt/components/MoreSettings/EmbedUrl.jsx
+++ b/packages/roomkit-react/src/Prebuilt/components/MoreSettings/EmbedUrl.jsx
@@ -1,7 +1,6 @@
import React, { useState } from 'react';
-import { ViewIcon } from '@100mslive/react-icons';
-import { Button, Dialog, Dropdown, Text } from '../../../';
-import { DialogContent, DialogInput, DialogRow } from '../../primitives/DialogContent';
+import { LinkIcon } from '@100mslive/react-icons';
+import { Button, Dialog, Dropdown, Flex, Input, Text } from '../../../';
import { useSetAppDataByKey } from '../AppData/useUISettings';
import { APP_DATA } from '../../common/constants';
@@ -17,7 +16,7 @@ export const EmbedUrl = ({ setShowOpenUrl }) => {
}}
data-testid="embed_url_btn"
>
-
+
Embed URL
@@ -29,78 +28,54 @@ export function EmbedUrlModal({ onOpenChange }) {
const [embedConfig, setEmbedConfig] = useSetAppDataByKey(APP_DATA.embedConfig);
const [url, setUrl] = useState(embedConfig?.url || '');
- const isAnythingEmbedded = !!embedConfig?.url;
- const isModifying = isAnythingEmbedded && url && url !== embedConfig.url;
-
return (
-
-
-
-
- Embed a url and share with everyone in the room. Ensure that you're sharing the current tab when the prompt
- opens. Note that not all websites support being embedded.
+
+
+
+
+ Embed URL
+
+
+ Ensure that you're sharing the current tab when the prompt opens. Note that not all websites support being
+ embedded.
+
+
+ URL
-
-
- {isAnythingEmbedded ? (
- <>
-
-
- >
- ) : (
- <>
-
-
- >
- )}
-
-
+ setUrl(e.target.value)}
+ type="url"
+ />
+
+
+
+
+
+
);
}
diff --git a/packages/roomkit-react/src/Prebuilt/components/MoreSettings/MoreSettings.jsx b/packages/roomkit-react/src/Prebuilt/components/MoreSettings/MoreSettings.jsx
index 5eb7c4c588..6ab8112628 100644
--- a/packages/roomkit-react/src/Prebuilt/components/MoreSettings/MoreSettings.jsx
+++ b/packages/roomkit-react/src/Prebuilt/components/MoreSettings/MoreSettings.jsx
@@ -1,226 +1,10 @@
-import React, { Fragment, useState } from 'react';
+import React from 'react';
import { useMedia } from 'react-use';
-import Hls from 'hls.js';
-import {
- selectAppData,
- selectIsAllowedToPublish,
- selectLocalPeerID,
- selectLocalPeerRoleName,
- selectPermissions,
- useHMSActions,
- useHMSStore,
- useRecordingStreaming,
-} from '@100mslive/react-sdk';
-import {
- ChangeRoleIcon,
- CheckIcon,
- InfoIcon,
- MicOffIcon,
- PencilIcon,
- RecordIcon,
- SettingsIcon,
- VerticalMenuIcon,
-} from '@100mslive/react-icons';
-import { Box, Checkbox, config as cssConfig, Dropdown, Flex, Text, Tooltip } from '../../../';
-import IconButton from '../../IconButton';
-import { RoleChangeModal } from '../RoleChangeModal';
-import SettingsModal from '../Settings/SettingsModal';
-import StartRecording from '../Settings/StartRecording';
-import { StatsForNerds } from '../StatsForNerds';
-import { BulkRoleChangeModal } from './BulkRoleChangeModal';
-import { ChangeNameModal } from './ChangeNameModal';
-import { ChangeSelfRole } from './ChangeSelfRole';
-import { EmbedUrl, EmbedUrlModal } from './EmbedUrl';
-import { FullScreenItem } from './FullScreenItem';
-import { MuteAllModal } from './MuteAllModal';
-import { useDropdownList } from '../hooks/useDropdownList';
-import { useIsFeatureEnabled } from '../hooks/useFeatures';
-import { FeatureFlags } from '../../services/FeatureFlags';
-import { APP_DATA, FEATURE_LIST, isAndroid, isIOS, isMacOS } from '../../common/constants';
-
-const isMobileOS = isAndroid || isIOS;
-
-const MODALS = {
- CHANGE_NAME: 'changeName',
- SELF_ROLE_CHANGE: 'selfRoleChange',
- MORE_SETTINGS: 'moreSettings',
- START_RECORDING: 'startRecording',
- DEVICE_SETTINGS: 'deviceSettings',
- STATS_FOR_NERDS: 'statsForNerds',
- BULK_ROLE_CHANGE: 'bulkRoleChange',
- MUTE_ALL: 'muteAll',
- EMBED_URL: 'embedUrl',
-};
+import { DesktopOptions } from './SplitComponents/DesktopOptions';
+import { MwebOptions } from './SplitComponents/MwebOptions';
+import { config as cssConfig } from '../../../';
export const MoreSettings = () => {
- const permissions = useHMSStore(selectPermissions);
- const isAllowedToPublish = useHMSStore(selectIsAllowedToPublish);
- const localPeerId = useHMSStore(selectLocalPeerID);
- const localPeerRole = useHMSStore(selectLocalPeerRoleName);
- const hmsActions = useHMSActions();
- const enablHlsStats = useHMSStore(selectAppData(APP_DATA.hlsStats));
const isMobile = useMedia(cssConfig.media.md);
- const { isBrowserRecordingOn } = useRecordingStreaming();
- const isChangeNameEnabled = useIsFeatureEnabled(FEATURE_LIST.CHANGE_NAME);
- const isEmbedEnabled = useIsFeatureEnabled(FEATURE_LIST.EMBED_URL);
- const isSFNEnabled = useIsFeatureEnabled(FEATURE_LIST.STARTS_FOR_NERDS);
- const [openModals, setOpenModals] = useState(new Set());
- useDropdownList({ open: openModals.size > 0, name: 'MoreSettings' });
-
- const updateState = (modalName, value) => {
- setOpenModals(modals => {
- const copy = new Set(modals);
- if (value) {
- copy.add(modalName);
- } else {
- copy.delete(modalName);
- }
- return copy;
- });
- };
-
- return (
-
- updateState(MODALS.MORE_SETTINGS, value)}
- >
-
-
-
-
-
-
-
-
-
-
-
- {isMobile && permissions?.browserRecording ? (
- <>
- updateState(MODALS.START_RECORDING, true)}>
-
-
- {isBrowserRecordingOn ? 'Stop' : 'Start'} Recording
-
-
-
- >
- ) : null}
- {isChangeNameEnabled && (
- updateState(MODALS.CHANGE_NAME, true)} data-testid="change_name_btn">
-
-
- Change Name
-
-
- )}
- updateState(MODALS.SELF_ROLE_CHANGE, true)} />
- {permissions?.changeRole && (
- updateState(MODALS.BULK_ROLE_CHANGE, true)}
- data-testid="bulk_role_change_btn"
- >
-
-
- Bulk Role Change
-
-
- )}
-
- {isAllowedToPublish.screen && isEmbedEnabled && (
- updateState(MODALS.EMBED_URL, true)} />
- )}
- {permissions.mute && (
- updateState(MODALS.MUTE_ALL, true)} data-testid="mute_all_btn">
-
-
- Mute All
-
-
- )}
-
- updateState(MODALS.DEVICE_SETTINGS, true)} data-testid="device_settings_btn">
-
-
- Settings
-
-
- {FeatureFlags.enableStatsForNerds &&
- isSFNEnabled &&
- (localPeerRole === 'hls-viewer' ? (
- Hls.isSupported() ? (
- hmsActions.setAppData(APP_DATA.hlsStats, !enablHlsStats)}
- data-testid="hls_stats"
- >
- hmsActions.setAppData(APP_DATA.hlsStats, !enablHlsStats)}
- >
-
-
-
-
-
-
- Show HLS Stats
-
- {!isMobileOS ? (
-
- {`${isMacOS ? '⌘' : 'ctrl'} + ]`}
-
- ) : null}
-
-
- ) : null
- ) : (
- updateState(MODALS.STATS_FOR_NERDS, true)}
- data-testid="stats_for_nreds_btn"
- >
-
-
- Stats for Nerds
-
-
- ))}
-
-
- {openModals.has(MODALS.BULK_ROLE_CHANGE) && (
- updateState(MODALS.BULK_ROLE_CHANGE, value)} />
- )}
- {openModals.has(MODALS.MUTE_ALL) && updateState(MODALS.MUTE_ALL, value)} />}
- {openModals.has(MODALS.CHANGE_NAME) && (
- updateState(MODALS.CHANGE_NAME, value)} />
- )}
- {openModals.has(MODALS.DEVICE_SETTINGS) && (
- updateState(MODALS.DEVICE_SETTINGS, value)} />
- )}
- {FeatureFlags.enableStatsForNerds && openModals.has(MODALS.STATS_FOR_NERDS) && (
- updateState(MODALS.STATS_FOR_NERDS, value)} />
- )}
- {openModals.has(MODALS.SELF_ROLE_CHANGE) && (
- updateState(MODALS.SELF_ROLE_CHANGE, value)} />
- )}
- {openModals.has(MODALS.START_RECORDING) && (
- updateState(MODALS.START_RECORDING, value)} />
- )}
- {openModals.has(MODALS.EMBED_URL) && (
- updateState(MODALS.EMBED_URL, value)} />
- )}
-
- );
+ return isMobile ? : ;
};
diff --git a/packages/roomkit-react/src/Prebuilt/components/MoreSettings/MuteAllContent.jsx b/packages/roomkit-react/src/Prebuilt/components/MoreSettings/MuteAllContent.jsx
new file mode 100644
index 0000000000..d7af0a9080
--- /dev/null
+++ b/packages/roomkit-react/src/Prebuilt/components/MoreSettings/MuteAllContent.jsx
@@ -0,0 +1,61 @@
+import React from 'react';
+import { Button } from '../../../Button';
+import { Label } from '../../../Label';
+import { Flex } from '../../../Layout';
+import { RadioGroup } from '../../../RadioGroup';
+import { Text } from '../../../Text';
+import { DialogRow, DialogSelect } from '../../primitives/DialogContent';
+
+export const MuteAllContent = props => {
+ const roles = props.roles || [];
+ return (
+ <>
+ ({ label: role, value: role }))]}
+ selected={props.selectedRole}
+ keyField="value"
+ labelField="label"
+ onChange={props.setRole}
+ />
+
+
+
+ Track status
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ >
+ );
+};
diff --git a/packages/roomkit-react/src/Prebuilt/components/MoreSettings/MuteAllModal.jsx b/packages/roomkit-react/src/Prebuilt/components/MoreSettings/MuteAllModal.jsx
index c6b7e0252b..aca0d15517 100644
--- a/packages/roomkit-react/src/Prebuilt/components/MoreSettings/MuteAllModal.jsx
+++ b/packages/roomkit-react/src/Prebuilt/components/MoreSettings/MuteAllModal.jsx
@@ -1,8 +1,10 @@
import React, { useCallback, useState } from 'react';
import { useHMSActions } from '@100mslive/react-sdk';
import { MicOffIcon } from '@100mslive/react-icons';
-import { Button, Dialog, Flex, Label, RadioGroup, Text } from '../../../';
-import { DialogContent, DialogRow, DialogSelect } from '../../primitives/DialogContent';
+import { Dialog } from '../../../';
+import { Sheet } from '../../../Sheet';
+import { DialogContent } from '../../primitives/DialogContent';
+import { MuteAllContent } from './MuteAllContent';
import { useFilteredRoles } from '../../common/hooks';
const trackSourceOptions = [
@@ -17,7 +19,7 @@ const trackTypeOptions = [
{ label: 'audio', value: 'audio' },
{ label: 'video', value: 'video' },
];
-export const MuteAllModal = ({ onOpenChange }) => {
+export const MuteAllModal = ({ onOpenChange, isMobile = false }) => {
const roles = useFilteredRoles();
const hmsActions = useHMSActions();
const [enabled, setEnabled] = useState(false);
@@ -35,55 +37,36 @@ export const MuteAllModal = ({ onOpenChange }) => {
onOpenChange(false);
}, [selectedRole, enabled, trackType, selectedSource, hmsActions, onOpenChange]);
+ const props = {
+ muteAll,
+ roles,
+ enabled,
+ setEnabled,
+ trackType,
+ setTrackType,
+ selectedRole,
+ setRole,
+ selectedSource,
+ setSource,
+ trackSourceOptions,
+ trackTypeOptions,
+ isMobile,
+ };
+
+ if (isMobile) {
+ return (
+
+
+
+
+
+ );
+ }
+
return (
- ({ label: role, value: role }))]}
- selected={selectedRole}
- keyField="value"
- labelField="label"
- onChange={setRole}
- />
-
-
-
- Track status
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
);
diff --git a/packages/roomkit-react/src/Prebuilt/components/MoreSettings/SplitComponents/DesktopLeaveRoom.jsx b/packages/roomkit-react/src/Prebuilt/components/MoreSettings/SplitComponents/DesktopLeaveRoom.jsx
new file mode 100644
index 0000000000..66b29aaa50
--- /dev/null
+++ b/packages/roomkit-react/src/Prebuilt/components/MoreSettings/SplitComponents/DesktopLeaveRoom.jsx
@@ -0,0 +1,116 @@
+import React, { Fragment, useState } from 'react';
+import { selectIsConnectedToRoom, selectPermissions, useHMSStore } from '@100mslive/react-sdk';
+import { ExitIcon, HangUpIcon, StopIcon, VerticalMenuIcon } from '@100mslive/react-icons';
+import { Dropdown } from '../../../../Dropdown';
+import { Box, Flex } from '../../../../Layout';
+import { Dialog } from '../../../../Modal';
+import { Tooltip } from '../../../../Tooltip';
+import { EndSessionContent } from '../../EndSessionContent';
+import { LeaveCard } from '../../LeaveCard';
+import { useDropdownList } from '../../hooks/useDropdownList';
+import { useShowStreamingUI } from '../../../common/hooks';
+
+export const DesktopLeaveRoom = ({
+ menuTriggerButton: MenuTriggerButton,
+ leaveIconButton: LeaveIconButton,
+ leaveRoom,
+ endRoom,
+}) => {
+ const [open, setOpen] = useState(false);
+ const [showEndRoomAlert, setShowEndRoomAlert] = useState(false);
+ const isConnected = useHMSStore(selectIsConnectedToRoom);
+ const permissions = useHMSStore(selectPermissions);
+ const showStreamingUI = useShowStreamingUI();
+ useDropdownList({ open, name: 'LeaveRoom' });
+
+ if (!permissions || !isConnected) {
+ return null;
+ }
+
+ return (
+
+ {permissions.endRoom ? (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ }
+ onClick={leaveRoom}
+ css={{ p: 0 }}
+ />
+
+
+ }
+ onClick={() => {
+ setOpen(false);
+ setShowEndRoomAlert(true);
+ }}
+ css={{ p: 0 }}
+ />
+
+
+
+
+ ) : (
+
+
+ {showStreamingUI ? : }
+
+
+ )}
+
+
+
+
+
+
+
+
+
+
+ );
+};
diff --git a/packages/roomkit-react/src/Prebuilt/components/MoreSettings/SplitComponents/DesktopOptions.jsx b/packages/roomkit-react/src/Prebuilt/components/MoreSettings/SplitComponents/DesktopOptions.jsx
new file mode 100644
index 0000000000..b809e9d08f
--- /dev/null
+++ b/packages/roomkit-react/src/Prebuilt/components/MoreSettings/SplitComponents/DesktopOptions.jsx
@@ -0,0 +1,249 @@
+import React, { Fragment, useState } from 'react';
+import Hls from 'hls.js';
+import {
+ selectAppData,
+ selectIsAllowedToPublish,
+ selectLocalPeerID,
+ selectLocalPeerRoleName,
+ selectPermissions,
+ useHMSActions,
+ useHMSStore,
+} from '@100mslive/react-sdk';
+import {
+ BrbIcon,
+ CheckIcon,
+ DragHandleIcon,
+ HandIcon,
+ InfoIcon,
+ MicOffIcon,
+ PencilIcon,
+ PipIcon,
+ SettingsIcon,
+} from '@100mslive/react-icons';
+import { Checkbox, Dropdown, Flex, Text, Tooltip } from '../../../../';
+import IconButton from '../../../IconButton';
+import { PIP } from '../../PIP';
+import { RoleChangeModal } from '../../RoleChangeModal';
+import SettingsModal from '../../Settings/SettingsModal';
+import StartRecording from '../../Settings/StartRecording';
+import { StatsForNerds } from '../../StatsForNerds';
+import { BulkRoleChangeModal } from '.././BulkRoleChangeModal';
+import { ChangeNameModal } from '.././ChangeNameModal';
+import { ChangeSelfRole } from '.././ChangeSelfRole';
+import { EmbedUrl, EmbedUrlModal } from '.././EmbedUrl';
+import { FullScreenItem } from '.././FullScreenItem';
+import { MuteAllModal } from '.././MuteAllModal';
+import { useDropdownList } from '../../hooks/useDropdownList';
+import { useIsFeatureEnabled } from '../../hooks/useFeatures';
+import { useMyMetadata } from '../../hooks/useMetadata';
+import { FeatureFlags } from '../../../services/FeatureFlags';
+import { APP_DATA, FEATURE_LIST, isMacOS } from '../../../common/constants';
+
+const MODALS = {
+ CHANGE_NAME: 'changeName',
+ SELF_ROLE_CHANGE: 'selfRoleChange',
+ MORE_SETTINGS: 'moreSettings',
+ START_RECORDING: 'startRecording',
+ DEVICE_SETTINGS: 'deviceSettings',
+ STATS_FOR_NERDS: 'statsForNerds',
+ BULK_ROLE_CHANGE: 'bulkRoleChange',
+ MUTE_ALL: 'muteAll',
+ EMBED_URL: 'embedUrl',
+};
+
+export const DesktopOptions = ({ showStreamingUI = false }) => {
+ const permissions = useHMSStore(selectPermissions);
+ const isAllowedToPublish = useHMSStore(selectIsAllowedToPublish);
+ const localPeerId = useHMSStore(selectLocalPeerID);
+ const localPeerRole = useHMSStore(selectLocalPeerRoleName);
+ const hmsActions = useHMSActions();
+ const enablHlsStats = useHMSStore(selectAppData(APP_DATA.hlsStats));
+ const isChangeNameEnabled = useIsFeatureEnabled(FEATURE_LIST.CHANGE_NAME);
+ const isEmbedEnabled = useIsFeatureEnabled(FEATURE_LIST.EMBED_URL);
+ const isSFNEnabled = useIsFeatureEnabled(FEATURE_LIST.STARTS_FOR_NERDS);
+ const [openModals, setOpenModals] = useState(new Set());
+ const { isHandRaised, isBRBOn, toggleHandRaise, toggleBRB } = useMyMetadata();
+ const isHandRaiseEnabled = useIsFeatureEnabled(FEATURE_LIST.HAND_RAISE);
+ const isBRBEnabled = useIsFeatureEnabled(FEATURE_LIST.BRB);
+ const isPIPEnabled = useIsFeatureEnabled(FEATURE_LIST.PICTURE_IN_PICTURE);
+
+ useDropdownList({ open: openModals.size > 0, name: 'MoreSettings' });
+
+ const updateState = (modalName, value) => {
+ setOpenModals(modals => {
+ const copy = new Set(modals);
+ if (value) {
+ copy.add(modalName);
+ } else {
+ copy.delete(modalName);
+ }
+ return copy;
+ });
+ };
+
+ return (
+
+ updateState(MODALS.MORE_SETTINGS, value)}
+ >
+
+
+
+
+
+
+
+
+
+ {isHandRaiseEnabled && !showStreamingUI ? (
+
+
+
+ Raise Hand
+
+
+ {isHandRaised ? : null}
+
+
+ ) : null}
+
+ {isBRBEnabled && !showStreamingUI ? (
+
+
+
+ Be Right Back
+
+
+ {isBRBOn ? : null}
+
+
+ ) : null}
+
+ {(isBRBEnabled || isHandRaiseEnabled) && !showStreamingUI ? (
+
+ ) : null}
+
+ {isPIPEnabled ? (
+
+
+
+
+ Picture in picture mode
+
+
+ }
+ />
+
+ ) : null}
+
+ {isChangeNameEnabled && (
+ updateState(MODALS.CHANGE_NAME, true)} data-testid="change_name_btn">
+
+
+ Change Name
+
+
+ )}
+ updateState(MODALS.SELF_ROLE_CHANGE, true)} />
+
+ {isAllowedToPublish.screen && isEmbedEnabled && (
+ updateState(MODALS.EMBED_URL, true)} />
+ )}
+
+ {permissions.mute && (
+ updateState(MODALS.MUTE_ALL, true)} data-testid="mute_all_btn">
+
+
+ Mute All
+
+
+ )}
+
+
+ updateState(MODALS.DEVICE_SETTINGS, true)} data-testid="device_settings_btn">
+
+
+ Settings
+
+
+
+ {FeatureFlags.enableStatsForNerds &&
+ isSFNEnabled &&
+ (localPeerRole === 'hls-viewer' ? (
+ Hls.isSupported() ? (
+ hmsActions.setAppData(APP_DATA.hlsStats, !enablHlsStats)}
+ data-testid="hls_stats"
+ >
+ hmsActions.setAppData(APP_DATA.hlsStats, !enablHlsStats)}
+ >
+
+
+
+
+
+
+ Show HLS Stats
+
+
+
+ {`${isMacOS ? '⌘' : 'ctrl'} + ]`}
+
+
+
+ ) : null
+ ) : (
+ updateState(MODALS.STATS_FOR_NERDS, true)}
+ data-testid="stats_for_nreds_btn"
+ >
+
+
+ Stats for Nerds
+
+
+ ))}
+
+
+ {openModals.has(MODALS.BULK_ROLE_CHANGE) && (
+ updateState(MODALS.BULK_ROLE_CHANGE, value)} />
+ )}
+ {openModals.has(MODALS.MUTE_ALL) && updateState(MODALS.MUTE_ALL, value)} />}
+ {openModals.has(MODALS.CHANGE_NAME) && (
+ updateState(MODALS.CHANGE_NAME, value)} />
+ )}
+ {openModals.has(MODALS.START_RECORDING) && (
+ updateState(MODALS.START_RECORDING, value)} />
+ )}
+ {openModals.has(MODALS.DEVICE_SETTINGS) && (
+ updateState(MODALS.DEVICE_SETTINGS, value)} />
+ )}
+ {FeatureFlags.enableStatsForNerds && openModals.has(MODALS.STATS_FOR_NERDS) && (
+ updateState(MODALS.STATS_FOR_NERDS, value)} />
+ )}
+ {openModals.has(MODALS.SELF_ROLE_CHANGE) && (
+ updateState(MODALS.SELF_ROLE_CHANGE, value)} />
+ )}
+ {openModals.has(MODALS.EMBED_URL) && (
+ updateState(MODALS.EMBED_URL, value)} />
+ )}
+
+ );
+};
diff --git a/packages/roomkit-react/src/Prebuilt/components/MoreSettings/SplitComponents/MwebLeaveRoom.jsx b/packages/roomkit-react/src/Prebuilt/components/MoreSettings/SplitComponents/MwebLeaveRoom.jsx
new file mode 100644
index 0000000000..c62c144d93
--- /dev/null
+++ b/packages/roomkit-react/src/Prebuilt/components/MoreSettings/SplitComponents/MwebLeaveRoom.jsx
@@ -0,0 +1,87 @@
+import React, { Fragment, useState } from 'react';
+import { selectIsConnectedToRoom, selectPermissions, useHMSStore } from '@100mslive/react-sdk';
+import { ExitIcon, HangUpIcon, StopIcon } from '@100mslive/react-icons';
+import { Box } from '../../../../Layout';
+import { Sheet } from '../../../../Sheet';
+import { Tooltip } from '../../../../Tooltip';
+import { EndSessionContent } from '../../EndSessionContent';
+import { LeaveCard } from '../../LeaveCard';
+import { useDropdownList } from '../../hooks/useDropdownList';
+import { useShowStreamingUI } from '../../../common/hooks';
+
+export const MwebLeaveRoom = ({ leaveIconButton: LeaveIconButton, endRoom, leaveRoom }) => {
+ const [open, setOpen] = useState(false);
+ const [showEndRoomAlert, setShowEndRoomAlert] = useState(false);
+ const isConnected = useHMSStore(selectIsConnectedToRoom);
+ const permissions = useHMSStore(selectPermissions);
+
+ const showStreamingUI = useShowStreamingUI();
+ useDropdownList({ open, name: 'LeaveRoom' });
+
+ if (!permissions || !isConnected) {
+ return null;
+ }
+
+ return (
+
+ {permissions.endRoom ? (
+
+
+
+
+ {showStreamingUI ? : }
+
+
+
+
+ }
+ onClick={leaveRoom}
+ css={{ pt: 0, mt: '$10' }}
+ />
+ }
+ onClick={() => {
+ setOpen(false);
+ setShowEndRoomAlert(true);
+ }}
+ />
+
+
+ ) : (
+
+
+ {showStreamingUI ? : }
+
+
+ )}
+
+
+
+
+
+
+ );
+};
diff --git a/packages/roomkit-react/src/Prebuilt/components/MoreSettings/SplitComponents/MwebOptions.jsx b/packages/roomkit-react/src/Prebuilt/components/MoreSettings/SplitComponents/MwebOptions.jsx
new file mode 100644
index 0000000000..b98b6376e0
--- /dev/null
+++ b/packages/roomkit-react/src/Prebuilt/components/MoreSettings/SplitComponents/MwebOptions.jsx
@@ -0,0 +1,260 @@
+import React, { Suspense, useRef, useState } from 'react';
+import { useClickAway } from 'react-use';
+import {
+ selectIsConnectedToRoom,
+ selectIsLocalVideoEnabled,
+ selectPermissions,
+ useHMSActions,
+ useHMSStore,
+ useRecordingStreaming,
+} from '@100mslive/react-sdk';
+import {
+ BrbIcon,
+ CrossIcon,
+ DragHandleIcon,
+ EmojiIcon,
+ HandIcon,
+ MicOffIcon,
+ PencilIcon,
+ RecordIcon,
+ SettingsIcon,
+} from '@100mslive/react-icons';
+import { Box, Tooltip } from '../../../../';
+import { Sheet } from '../../../../Sheet';
+import IconButton from '../../../IconButton';
+import { EmojiCard } from '../../Footer/EmojiCard';
+import { StopRecordingInSheet } from '../../Header/StreamActions';
+import SettingsModal from '../../Settings/SettingsModal';
+import { ToastManager } from '../../Toast/ToastManager';
+import { ActionTile } from '.././ActionTile';
+import { ChangeNameModal } from '.././ChangeNameModal';
+import { MuteAllModal } from '.././MuteAllModal';
+import { useDropdownList } from '../../hooks/useDropdownList';
+import { useIsFeatureEnabled } from '../../hooks/useFeatures';
+import { useMyMetadata } from '../../hooks/useMetadata';
+import { FEATURE_LIST } from '../../../common/constants';
+
+const VirtualBackground = React.lazy(() => import('../../../plugins/VirtualBackground/VirtualBackground'));
+
+const MODALS = {
+ CHANGE_NAME: 'changeName',
+ SELF_ROLE_CHANGE: 'selfRoleChange',
+ MORE_SETTINGS: 'moreSettings',
+ START_RECORDING: 'startRecording',
+ DEVICE_SETTINGS: 'deviceSettings',
+ STATS_FOR_NERDS: 'statsForNerds',
+ BULK_ROLE_CHANGE: 'bulkRoleChange',
+ MUTE_ALL: 'muteAll',
+ EMBED_URL: 'embedUrl',
+};
+
+export const MwebOptions = () => {
+ const hmsActions = useHMSActions();
+ const permissions = useHMSStore(selectPermissions);
+ const isConnected = useHMSStore(selectIsConnectedToRoom);
+ const { isBrowserRecordingOn, isStreamingOn, isHLSRunning } = useRecordingStreaming();
+
+ const [openModals, setOpenModals] = useState(new Set());
+ const { isHandRaised, isBRBOn, toggleHandRaise, toggleBRB } = useMyMetadata();
+ const isHandRaiseEnabled = useIsFeatureEnabled(FEATURE_LIST.HAND_RAISE);
+ const isBRBEnabled = useIsFeatureEnabled(FEATURE_LIST.BRB);
+
+ const [openOptionsSheet, setOpenOptionsSheet] = useState(false);
+ const [openSettingsSheet, setOpenSettingsSheet] = useState(false);
+ const [showEmojiCard, setShowEmojiCard] = useState(false);
+ const [showRecordingOn, setShowRecordingOn] = useState(false);
+
+ const emojiCardRef = useRef(null);
+ const isVideoOn = useHMSStore(selectIsLocalVideoEnabled);
+
+ useDropdownList({ open: openModals.size > 0, name: 'MoreSettings' });
+
+ const updateState = (modalName, value) => {
+ setOpenModals(modals => {
+ const copy = new Set(modals);
+ if (value) {
+ copy.add(modalName);
+ } else {
+ copy.delete(modalName);
+ }
+ return copy;
+ });
+ };
+
+ useClickAway(emojiCardRef, () => setShowEmojiCard(false));
+
+ return (
+ <>
+
+
+
+
+
+
+
+
+
+
+ Options
+
+
+
+
+
+
+
+ {isHandRaiseEnabled ? (
+ }
+ onClick={toggleHandRaise}
+ active={isHandRaised}
+ setOpenOptionsSheet={setOpenOptionsSheet}
+ />
+ ) : null}
+ {isBRBEnabled ? (
+ }
+ onClick={toggleBRB}
+ active={isBRBOn}
+ setOpenOptionsSheet={setOpenOptionsSheet}
+ />
+ ) : null}
+ {permissions.mute ? (
+ }
+ onClick={() => updateState(MODALS.MUTE_ALL, true)}
+ setOpenOptionsSheet={setOpenOptionsSheet}
+ />
+ ) : null}
+ }
+ onClick={() => updateState(MODALS.CHANGE_NAME, true)}
+ setOpenOptionsSheet={setOpenOptionsSheet}
+ />
+ {isVideoOn ? (
+
+
+
+ ) : null}
+ }
+ onClick={() => setShowEmojiCard(true)}
+ setOpenOptionsSheet={setOpenOptionsSheet}
+ />
+ } onClick={() => setOpenSettingsSheet(true)} />
+ {isConnected && permissions?.browserRecording && (
+ }
+ onClick={async () => {
+ if (isBrowserRecordingOn || isStreamingOn) {
+ setShowRecordingOn(true);
+ } else {
+ // start recording
+ setOpenOptionsSheet(false);
+ try {
+ await hmsActions.startRTMPOrRecording({
+ record: true,
+ });
+ } catch (error) {
+ if (error.message.includes('stream already running')) {
+ ToastManager.addToast({
+ title: 'Recording already running',
+ variant: 'error',
+ });
+ } else {
+ ToastManager.addToast({
+ title: error.message,
+ variant: 'error',
+ });
+ }
+ }
+ }
+ }}
+ setOpenOptionsSheet={setOpenOptionsSheet}
+ />
+ )}
+
+
+
+
+ {openModals.has(MODALS.MUTE_ALL) && (
+ updateState(MODALS.MUTE_ALL, value)} isMobile />
+ )}
+ {openModals.has(MODALS.CHANGE_NAME) && (
+ updateState(MODALS.CHANGE_NAME, value)}
+ openParentSheet={() => setOpenOptionsSheet(true)}
+ />
+ )}
+
+ {showEmojiCard && (
+ setShowEmojiCard(false)}
+ ref={emojiCardRef}
+ css={{
+ maxWidth: '100%',
+ w: '100%',
+ position: 'absolute',
+ left: 0,
+ right: 0,
+ bottom: '$18',
+ bg: '$surface_default',
+ zIndex: '10',
+ p: '$8',
+ pb: 0,
+ r: '$1',
+ mx: '$4',
+ }}
+ >
+
+
+ )}
+ {showRecordingOn && (
+ setShowRecordingOn(false)}
+ onStopRecording={async () => {
+ try {
+ await hmsActions.stopRTMPAndRecording();
+ setShowRecordingOn(false);
+ } catch (error) {
+ ToastManager.addToast({
+ title: error.message,
+ variant: 'error',
+ });
+ }
+ }}
+ />
+ )}
+ >
+ );
+};
diff --git a/packages/roomkit-react/src/Prebuilt/components/PIP/PIPComponent.jsx b/packages/roomkit-react/src/Prebuilt/components/PIP/PIPComponent.jsx
index 17c8c04ba7..3a6a7368cc 100644
--- a/packages/roomkit-react/src/Prebuilt/components/PIP/PIPComponent.jsx
+++ b/packages/roomkit-react/src/Prebuilt/components/PIP/PIPComponent.jsx
@@ -9,7 +9,7 @@ import {
useHMSVanillaStore,
} from '@100mslive/react-sdk';
import { PipIcon } from '@100mslive/react-icons';
-import { Tooltip } from '../../../';
+import { Flex, Tooltip } from '../../../';
import IconButton from '../../IconButton';
import { PictureInPicture } from './PIPManager';
import { MediaSession } from './SetupMediaSession';
@@ -20,7 +20,7 @@ import { DEFAULT_HLS_VIEWER_ROLE, FEATURE_LIST } from '../../common/constants';
* shows a button which when clicked shows some videos in PIP, clicking
* again turns it off.
*/
-const PIPComponent = ({ peers, showLocalPeer }) => {
+const PIPComponent = ({ peers, showLocalPeer, content = null }) => {
const localPeerRole = useHMSStore(selectLocalPeerRoleName);
const [isPipOn, setIsPipOn] = useState(PictureInPicture.isOn());
const hmsActions = useHMSActions();
@@ -48,11 +48,17 @@ const PIPComponent = ({ peers, showLocalPeer }) => {
}
return (
<>
-
- onPipToggle()} data-testid="pip_btn">
-
-
-
+ {content ? (
+ onPipToggle()} data-testid="pip_btn">
+ {content}
+
+ ) : (
+
+ onPipToggle()} data-testid="pip_btn">
+
+
+
+ )}
{isPipOn && }
>
);
diff --git a/packages/roomkit-react/src/Prebuilt/components/PIP/index.jsx b/packages/roomkit-react/src/Prebuilt/components/PIP/index.jsx
index f0a62b5396..e17018678c 100644
--- a/packages/roomkit-react/src/Prebuilt/components/PIP/index.jsx
+++ b/packages/roomkit-react/src/Prebuilt/components/PIP/index.jsx
@@ -2,10 +2,14 @@ import React from 'react';
import PIPComponent from './PIPComponent';
import { usePinnedTrack } from '../AppData/useUISettings';
-export const PIP = () => {
+export const PIP = ({ content = null }) => {
const pinnedTrack = usePinnedTrack();
return (
-
+
);
};
diff --git a/packages/roomkit-react/src/Prebuilt/components/Preview/PreviewForm.jsx b/packages/roomkit-react/src/Prebuilt/components/Preview/PreviewForm.jsx
index 819ac92604..5efbc73f80 100644
--- a/packages/roomkit-react/src/Prebuilt/components/Preview/PreviewForm.jsx
+++ b/packages/roomkit-react/src/Prebuilt/components/Preview/PreviewForm.jsx
@@ -1,11 +1,11 @@
import React from 'react';
import { useMedia } from 'react-use';
-import { JoinForm_JoinBtnType } from '@100mslive/types-prebuilt/elements/join_form';
import { selectPermissions, useHMSStore, useRecordingStreaming } from '@100mslive/react-sdk';
import { RadioIcon } from '@100mslive/react-icons';
import { Button, config as cssConfig, Flex, Input, styled } from '../../..';
import { useRoomLayout } from '../../provider/roomLayoutProvider';
import { PreviewSettings } from './PreviewJoin';
+import { useShowStreamingUI } from '../../common/hooks';
const PreviewForm = ({
name,
@@ -24,10 +24,8 @@ const PreviewForm = ({
const permissions = useHMSStore(selectPermissions);
const layout = useRoomLayout();
const { join_form: joinForm = {} } = layout?.screens?.preview?.default?.elements || {};
- const showGoLive =
- joinForm.join_btn_type === JoinForm_JoinBtnType.JOIN_BTN_TYPE_JOIN_AND_GO_LIVE &&
- !isHLSRunning &&
- permissions?.hlsStreaming;
+ const showStreamingUI = useShowStreamingUI();
+ const showGoLive = showStreamingUI && !isHLSRunning && permissions?.hlsStreaming;
return (