@100mslive/roomkit-react 0.3.16-alpha.0 → 0.3.16-alpha.2

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 (45) hide show
  1. package/dist/{HLSView-KOCGTP23.css → HLSView-LQNFHQLW.css} +3 -3
  2. package/dist/{HLSView-KOCGTP23.css.map → HLSView-LQNFHQLW.css.map} +1 -1
  3. package/dist/{HLSView-P7GF2RAU.js → HLSView-PTKFE7ET.js} +2 -2
  4. package/dist/Prebuilt/components/Chat/ChatBody.d.ts +887 -0
  5. package/dist/Prebuilt/components/Chat/ChatFooter.d.ts +1 -1
  6. package/dist/Prebuilt/components/Chat/utils.d.ts +2 -0
  7. package/dist/Prebuilt/components/PIP/PIPChat.d.ts +2 -0
  8. package/dist/Prebuilt/components/PIP/PIPChatOption.d.ts +5 -0
  9. package/dist/Prebuilt/components/PIP/PIPProvider.d.ts +6 -0
  10. package/dist/Prebuilt/components/PIP/PIPWindow.d.ts +7 -0
  11. package/dist/Prebuilt/components/PIP/context.d.ts +8 -0
  12. package/dist/Prebuilt/components/PIP/usePIPChat.d.ts +5 -0
  13. package/dist/Prebuilt/components/PIP/usePIPWindow.d.ts +2 -0
  14. package/dist/{chunk-DDB4BLHX.js → chunk-QGNIWXTH.js} +10176 -9767
  15. package/dist/chunk-QGNIWXTH.js.map +7 -0
  16. package/dist/index.cjs.css +2 -2
  17. package/dist/index.cjs.css.map +1 -1
  18. package/dist/index.cjs.js +3974 -3512
  19. package/dist/index.cjs.js.map +4 -4
  20. package/dist/index.css +2 -2
  21. package/dist/index.css.map +1 -1
  22. package/dist/index.js +1 -1
  23. package/dist/meta.cjs.json +2275 -1908
  24. package/dist/meta.esbuild.json +2275 -1908
  25. package/package.json +8 -7
  26. package/src/Prebuilt/components/AudioVideoToggle.tsx +14 -7
  27. package/src/Prebuilt/components/Chat/ChatBody.tsx +4 -12
  28. package/src/Prebuilt/components/Chat/ChatFooter.tsx +2 -3
  29. package/src/Prebuilt/components/Chat/utils.ts +11 -0
  30. package/src/Prebuilt/components/ConferenceScreen.tsx +13 -1
  31. package/src/Prebuilt/components/Footer/ParticipantList.tsx +1 -4
  32. package/src/Prebuilt/components/MoreSettings/MoreSettings.tsx +4 -1
  33. package/src/Prebuilt/components/MoreSettings/SplitComponents/DesktopOptions.tsx +13 -5
  34. package/src/Prebuilt/components/Notifications/HandRaisedNotifications.tsx +33 -0
  35. package/src/Prebuilt/components/PIP/PIPChat.tsx +225 -0
  36. package/src/Prebuilt/components/PIP/PIPChatOption.tsx +18 -0
  37. package/src/Prebuilt/components/PIP/PIPProvider.tsx +56 -0
  38. package/src/Prebuilt/components/PIP/PIPWindow.tsx +13 -0
  39. package/src/Prebuilt/components/PIP/context.ts +10 -0
  40. package/src/Prebuilt/components/PIP/usePIPChat.tsx +103 -0
  41. package/src/Prebuilt/components/PIP/usePIPWindow.tsx +12 -0
  42. package/src/Prebuilt/components/Preview/PreviewJoin.tsx +3 -1
  43. package/src/Prebuilt/components/Toast/ToastConfig.jsx +2 -2
  44. package/dist/chunk-DDB4BLHX.js.map +0 -7
  45. /package/dist/{HLSView-P7GF2RAU.js.map → HLSView-PTKFE7ET.js.map} +0 -0
package/package.json CHANGED
@@ -10,7 +10,7 @@
10
10
  "prebuilt",
11
11
  "roomkit"
12
12
  ],
13
- "version": "0.3.16-alpha.0",
13
+ "version": "0.3.16-alpha.2",
14
14
  "author": "100ms",
15
15
  "license": "MIT",
16
16
  "repository": {
@@ -75,12 +75,12 @@
75
75
  "react": ">=17.0.2 <19.0.0"
76
76
  },
77
77
  "dependencies": {
78
- "@100mslive/hls-player": "0.3.16-alpha.0",
78
+ "@100mslive/hls-player": "0.3.16-alpha.2",
79
79
  "@100mslive/hms-noise-cancellation": "0.0.1",
80
- "@100mslive/hms-virtual-background": "1.13.16-alpha.0",
81
- "@100mslive/hms-whiteboard": "0.0.6-alpha.0",
82
- "@100mslive/react-icons": "0.10.16-alpha.0",
83
- "@100mslive/react-sdk": "0.10.16-alpha.0",
80
+ "@100mslive/hms-virtual-background": "1.13.16-alpha.2",
81
+ "@100mslive/hms-whiteboard": "0.0.6-alpha.2",
82
+ "@100mslive/react-icons": "0.10.16-alpha.2",
83
+ "@100mslive/react-sdk": "0.10.16-alpha.2",
84
84
  "@100mslive/types-prebuilt": "0.12.9",
85
85
  "@emoji-mart/data": "^1.0.6",
86
86
  "@emoji-mart/react": "^1.0.1",
@@ -104,6 +104,7 @@
104
104
  "eventemitter2": "^6.4.9",
105
105
  "lodash.merge": "^4.6.2",
106
106
  "qrcode.react": "^3.1.0",
107
+ "react-dom": "^18.2.0",
107
108
  "react-draggable": "^4.4.5",
108
109
  "react-intersection-observer": "^9.4.3",
109
110
  "react-swipeable": "^7.0.1",
@@ -116,5 +117,5 @@
116
117
  "uuid": "^8.3.2",
117
118
  "worker-timers": "^7.0.40"
118
119
  },
119
- "gitHead": "0ba6be8700e395887440d6ab1cadaacb6a769e98"
120
+ "gitHead": "0be9feff65c2b2ae3a0def2236e41b492ac17e98"
120
121
  }
@@ -109,6 +109,9 @@ const useNoiseCancellationWithPlugin = () => {
109
109
  if (inProgress) {
110
110
  return;
111
111
  }
112
+ if (!krispPlugin.checkSupport().isSupported) {
113
+ throw Error('Krisp plugin is not supported');
114
+ }
112
115
  setInProgress(true);
113
116
  if (enabled) {
114
117
  await actions.addPluginToAudioTrack(krispPlugin);
@@ -281,13 +284,17 @@ export const AudioVideoToggle = ({ hideOptions = false }: { hideOptions?: boolea
281
284
  useEffect(() => {
282
285
  (async () => {
283
286
  if (isNoiseCancellationEnabled && !isKrispPluginAdded && !inProgress && localPeer?.audioTrack) {
284
- await setNoiseCancellationWithPlugin(true);
285
- ToastManager.addToast({
286
- title: `Noise Reduction Enabled`,
287
- variant: 'standard',
288
- duration: 2000,
289
- icon: <AudioLevelIcon />,
290
- });
287
+ try {
288
+ await setNoiseCancellationWithPlugin(true);
289
+ ToastManager.addToast({
290
+ title: `Noise Reduction Enabled`,
291
+ variant: 'standard',
292
+ duration: 2000,
293
+ icon: <AudioLevelIcon />,
294
+ });
295
+ } catch (error) {
296
+ console.error(error);
297
+ }
291
298
  }
292
299
  })();
293
300
  // eslint-disable-next-line react-hooks/exhaustive-deps
@@ -28,18 +28,9 @@ import { useRoomLayoutConferencingScreen } from '../../provider/roomLayoutProvid
28
28
  // @ts-ignore: No implicit Any
29
29
  import { useSetSubscribedChatSelector } from '../AppData/useUISettings';
30
30
  import { usePinnedBy } from '../hooks/usePinnedBy';
31
+ import { formatTime } from './utils';
31
32
  import { CHAT_SELECTOR, SESSION_STORE_KEY } from '../../common/constants';
32
33
 
33
- const formatTime = (date: Date) => {
34
- if (!(date instanceof Date)) {
35
- return '';
36
- }
37
- const hours = date.getHours();
38
- const minutes = date.getMinutes();
39
- const suffix = hours > 11 ? 'PM' : 'AM';
40
- return `${hours < 10 ? '0' : ''}${hours}:${minutes < 10 ? '0' : ''}${minutes} ${suffix}`;
41
- };
42
-
43
34
  const rowHeights: Record<number, { size: number; id: string }> = {};
44
35
  let listInstance: VariableSizeList | null = null; //eslint-disable-line
45
36
  function getRowHeight(index: number) {
@@ -109,7 +100,7 @@ const MessageTypeContainer = ({ left, right }: { left?: string; right?: string }
109
100
  );
110
101
  };
111
102
 
112
- const MessageType = ({
103
+ export const MessageType = ({
113
104
  roles,
114
105
  hasCurrentUserSent,
115
106
  receiver,
@@ -172,7 +163,8 @@ const getMessageType = ({ roles, receiver }: { roles?: HMSRoleName[]; receiver?:
172
163
  }
173
164
  return receiver ? 'private' : '';
174
165
  };
175
- const SenderName = styled(Text, {
166
+
167
+ export const SenderName = styled(Text, {
176
168
  overflow: 'hidden',
177
169
  textOverflow: 'ellipsis',
178
170
  whiteSpace: 'nowrap',
@@ -20,10 +20,9 @@ import { useIsPeerBlacklisted } from '../hooks/useChatBlacklist';
20
20
  // @ts-ignore: No implicit any
21
21
  import { useEmojiPickerStyles } from './useEmojiPickerStyles';
22
22
  import { useDefaultChatSelection, useLandscapeHLSStream, useMobileHLSStream } from '../../common/hooks';
23
+ import { CHAT_MESSAGE_LIMIT } from './utils';
23
24
  import { CHAT_SELECTOR, SESSION_STORE_KEY } from '../../common/constants';
24
25
 
25
- const CHAT_MESSAGE_LIMIT = 2000;
26
-
27
26
  const TextArea = styled('textarea', {
28
27
  width: '100%',
29
28
  bg: 'transparent',
@@ -76,7 +75,7 @@ function EmojiPicker({ onSelect }: { onSelect: (emoji: any) => void }) {
76
75
  );
77
76
  }
78
77
 
79
- export const ChatFooter = ({ onSend, children }: { onSend: (count: number) => void; children: ReactNode }) => {
78
+ export const ChatFooter = ({ onSend, children }: { onSend: (count: number) => void; children?: ReactNode }) => {
80
79
  const hmsActions = useHMSActions();
81
80
  const inputRef = useRef<HTMLTextAreaElement>(null);
82
81
  const [draftMessage, setDraftMessage] = useChatDraftMessage();
@@ -0,0 +1,11 @@
1
+ export const formatTime = (date: Date) => {
2
+ if (!(date instanceof Date)) {
3
+ return '';
4
+ }
5
+ const hours = date.getHours();
6
+ const minutes = date.getMinutes();
7
+ const suffix = hours > 11 ? 'PM' : 'AM';
8
+ return `${hours < 10 ? '0' : ''}${hours}:${minutes < 10 ? '0' : ''}${minutes} ${suffix}`;
9
+ };
10
+
11
+ export const CHAT_MESSAGE_LIMIT = 2000;
@@ -6,6 +6,7 @@ import {
6
6
  selectAppData,
7
7
  selectIsConnectedToRoom,
8
8
  selectRoomState,
9
+ useAwayNotifications,
9
10
  useHMSActions,
10
11
  useHMSStore,
11
12
  } from '@100mslive/react-sdk';
@@ -50,6 +51,7 @@ export const ConferenceScreen = () => {
50
51
  const isMobileDevice = isAndroid || isIOS || isIPadOS;
51
52
  const dropdownListRef = useRef<string[]>();
52
53
  const [isHLSStarted] = useSetAppDataByKey(APP_DATA.hlsStarted);
54
+ const { requestPermission } = useAwayNotifications();
53
55
 
54
56
  // using it in hls stream to show action button when chat is disabled
55
57
  const showChat = !!screenProps.elements?.chat;
@@ -99,10 +101,20 @@ export const ConferenceScreen = () => {
99
101
  speakerAutoSelectionBlacklist: ['Yeti Stereo Microphone'],
100
102
  },
101
103
  })
104
+ .then(() => requestPermission())
102
105
  .catch(console.error);
103
106
  autoRoomJoined.current = true;
104
107
  }
105
- }, [authTokenInAppData, endpoints?.init, hmsActions, isConnectedToRoom, isPreviewScreenEnabled, roomState, userName]);
108
+ }, [
109
+ authTokenInAppData,
110
+ endpoints?.init,
111
+ hmsActions,
112
+ isConnectedToRoom,
113
+ isPreviewScreenEnabled,
114
+ roomState,
115
+ userName,
116
+ requestPermission,
117
+ ]);
106
118
 
107
119
  useEffect(() => {
108
120
  onJoinFunc?.();
@@ -194,7 +194,6 @@ export const Participant = ({
194
194
  <ParticipantActions
195
195
  peerId={peer.id}
196
196
  peerType={peer.type}
197
- isLocal={peer.id === localPeerId}
198
197
  role={peer.roleName}
199
198
  isHandRaisedAccordion={isHandRaisedAccordion}
200
199
  />
@@ -273,12 +272,10 @@ const ParticipantActions = React.memo(
273
272
  peerId,
274
273
  peerType,
275
274
  role,
276
- isLocal,
277
275
  isHandRaisedAccordion,
278
276
  }: {
279
277
  peerId: string;
280
278
  role: string;
281
- isLocal: boolean;
282
279
  isHandRaisedAccordion?: boolean;
283
280
  peerType: HMSPeerType;
284
281
  }) => {
@@ -331,7 +328,7 @@ const ParticipantActions = React.memo(
331
328
  </Flex>
332
329
  ) : null}
333
330
 
334
- {shouldShowMoreActions && !isLocal ? <ParticipantMoreActions peerId={peerId} role={role} /> : null}
331
+ {shouldShowMoreActions ? <ParticipantMoreActions peerId={peerId} role={role} /> : null}
335
332
  </>
336
333
  )}
337
334
  </Flex>
@@ -10,6 +10,7 @@ import { DesktopOptions } from './SplitComponents/DesktopOptions';
10
10
  // @ts-ignore: No implicit Any
11
11
  import { MwebOptions } from './SplitComponents/MwebOptions';
12
12
  import { config as cssConfig } from '../../..';
13
+ import { PIPProvider } from '../PIP/PIPProvider';
13
14
  import { useLandscapeHLSStream } from '../../common/hooks';
14
15
 
15
16
  export const MoreSettings = ({
@@ -24,6 +25,8 @@ export const MoreSettings = ({
24
25
  return isMobile || isLandscapeHLSStream ? (
25
26
  <MwebOptions elements={elements} screenType={screenType} />
26
27
  ) : (
27
- <DesktopOptions elements={elements} screenType={screenType} />
28
+ <PIPProvider>
29
+ <DesktopOptions elements={elements} screenType={screenType} />
30
+ </PIPProvider>
28
31
  );
29
32
  };
@@ -28,8 +28,11 @@ import { Checkbox, Dropdown, Flex, Switch, Text, Tooltip } from '../../../..';
28
28
  import IconButton from '../../../IconButton';
29
29
  // @ts-ignore: No implicit any
30
30
  import { PIP } from '../../PIP';
31
+ import { PIPChat } from '../../PIP/PIPChat';
31
32
  // @ts-ignore: No implicit any
33
+ import { PIPChatOption } from '../../PIP/PIPChatOption';
32
34
  import { PictureInPicture } from '../../PIP/PIPManager';
35
+ import { PIPWindow } from '../../PIP/PIPWindow';
33
36
  // @ts-ignore: No implicit any
34
37
  import { RoleChangeModal } from '../../RoleChangeModal';
35
38
  // @ts-ignore: No implicit any
@@ -48,6 +51,8 @@ import { MuteAllModal } from '../MuteAllModal';
48
51
  import { useDropdownList } from '../../hooks/useDropdownList';
49
52
  import { useMyMetadata } from '../../hooks/useMetadata';
50
53
  // @ts-ignore: No implicit any
54
+ import { usePIPChat } from '../../PIP/usePIPChat';
55
+ // @ts-ignore: No implicit any
51
56
  import { APP_DATA, isMacOS } from '../../../common/constants';
52
57
 
53
58
  const MODALS = {
@@ -79,6 +84,8 @@ export const DesktopOptions = ({
79
84
  const isBRBEnabled = !!elements?.brb;
80
85
  const isTranscriptionAllowed = useHMSStore(selectIsTranscriptionAllowedByMode(HMSTranscriptionMode.CAPTION));
81
86
  const isTranscriptionEnabled = useHMSStore(selectIsTranscriptionEnabled);
87
+ const { isSupported, pipWindow, requestPipWindow } = usePIPChat();
88
+ const showPipChatOption = !!elements?.chat && isSupported;
82
89
 
83
90
  useDropdownList({ open: openModals.size > 0, name: 'MoreSettings' });
84
91
 
@@ -98,6 +105,11 @@ export const DesktopOptions = ({
98
105
 
99
106
  return (
100
107
  <Fragment>
108
+ {isSupported && pipWindow ? (
109
+ <PIPWindow pipWindow={pipWindow}>
110
+ <PIPChat />
111
+ </PIPWindow>
112
+ ) : null}
101
113
  <Dropdown.Root
102
114
  open={openModals.has(MODALS.MORE_SETTINGS)}
103
115
  onOpenChange={value => updateState(MODALS.MORE_SETTINGS, value)}
@@ -168,13 +180,9 @@ export const DesktopOptions = ({
168
180
  </Dropdown.Item>
169
181
  ) : null}
170
182
 
183
+ <PIPChatOption showPIPChat={showPipChatOption} openChat={async () => await requestPipWindow(350, 500)} />
171
184
  <FullScreenItem />
172
- {/* {isAllowedToPublish.screen && isEmbedEnabled && (
173
- <EmbedUrl setShowOpenUrl={() => updateState(MODALS.EMBED_URL, true)} />
174
- )} */}
175
-
176
185
  <Dropdown.ItemSeparator css={{ mx: 0 }} />
177
-
178
186
  <Dropdown.Item onClick={() => updateState(MODALS.DEVICE_SETTINGS, true)} data-testid="device_settings_btn">
179
187
  <SettingsIcon />
180
188
  <Text variant="sm" css={{ ml: '$4' }}>
@@ -1,14 +1,19 @@
1
1
  import { useEffect } from 'react';
2
+ import { useDebounce } from 'react-use';
2
3
  import {
3
4
  HMSNotificationTypes,
4
5
  HMSRoomState,
6
+ selectHandRaisedPeers,
5
7
  selectHasPeerHandRaised,
8
+ selectIsLocalScreenShared,
6
9
  selectPeerByID,
7
10
  selectRoomState,
11
+ useAwayNotifications,
8
12
  useHMSNotifications,
9
13
  useHMSStore,
10
14
  useHMSVanillaStore,
11
15
  } from '@100mslive/react-sdk';
16
+ import { useRoomLayout } from '../../provider/roomLayoutProvider';
12
17
  // @ts-ignore: No implicit Any
13
18
  import { ToastBatcher } from '../Toast/ToastBatcher';
14
19
  import { useRoomLayoutConferencingScreen } from '../../provider/roomLayoutProvider/hooks/useRoomLayoutScreen';
@@ -22,6 +27,9 @@ export const HandRaisedNotifications = () => {
22
27
  const vanillaStore = useHMSVanillaStore();
23
28
  const { on_stage_exp } = useRoomLayoutConferencingScreen().elements || {};
24
29
  const isSubscribing = !!useSubscribedNotifications(SUBSCRIBED_NOTIFICATIONS.METADATA_UPDATED);
30
+ const amIScreenSharing = useHMSStore(selectIsLocalScreenShared);
31
+ const { showNotification } = useAwayNotifications();
32
+ const logoURL = useRoomLayout()?.logo?.url;
25
33
 
26
34
  useEffect(() => {
27
35
  if (!notification?.data) {
@@ -32,6 +40,7 @@ export const HandRaisedNotifications = () => {
32
40
  if (roomState !== HMSRoomState.Connected || notification.data.isLocal || !isSubscribing) {
33
41
  return;
34
42
  }
43
+
35
44
  const hasPeerHandRaised = vanillaStore.getState(selectHasPeerHandRaised(notification.data.id));
36
45
  const peer = vanillaStore.getState(selectPeerByID(notification.data.id));
37
46
  if (hasPeerHandRaised) {
@@ -41,5 +50,29 @@ export const HandRaisedNotifications = () => {
41
50
  }
42
51
  }, [isSubscribing, notification, on_stage_exp, roomState, vanillaStore]);
43
52
 
53
+ useDebounce(
54
+ () => {
55
+ if (!notification?.data) {
56
+ return;
57
+ }
58
+
59
+ // Don't show toast message in case of local peer.
60
+ if (roomState !== HMSRoomState.Connected || notification.data.isLocal || !isSubscribing) {
61
+ return;
62
+ }
63
+
64
+ const hasPeerHandRaised = vanillaStore.getState(selectHasPeerHandRaised(notification.data.id));
65
+ const peer = vanillaStore.getState(selectPeerByID(notification.data.id));
66
+ const handRaisedPeers = vanillaStore.getState(selectHandRaisedPeers);
67
+ if (amIScreenSharing && hasPeerHandRaised) {
68
+ const title = `${peer?.name} ${
69
+ handRaisedPeers.length > 1 ? `and ${handRaisedPeers.length - 1} others` : ''
70
+ } raised hand`;
71
+ showNotification(title, { icon: logoURL });
72
+ }
73
+ },
74
+ 1000,
75
+ [isSubscribing, notification, roomState, vanillaStore, amIScreenSharing],
76
+ );
44
77
  return null;
45
78
  };
@@ -0,0 +1,225 @@
1
+ import React, { useCallback, useEffect, useMemo, useState } from 'react';
2
+ import {
3
+ selectHMSMessages,
4
+ selectLocalPeerID,
5
+ selectSessionStore,
6
+ selectUnreadHMSMessagesCount,
7
+ useHMSStore,
8
+ } from '@100mslive/react-sdk';
9
+ import { SendIcon } from '@100mslive/react-icons';
10
+ import { Box, Flex } from '../../../Layout';
11
+ import { Text } from '../../../Text';
12
+ import { TextArea } from '../../../TextArea';
13
+ import { Tooltip } from '../../../Tooltip';
14
+ import IconButton from '../../IconButton';
15
+ import { AnnotisedMessage } from '../Chat/ChatBody';
16
+ import { useRoomLayoutConferencingScreen } from '../../provider/roomLayoutProvider/hooks/useRoomLayoutScreen';
17
+ import { CHAT_MESSAGE_LIMIT, formatTime } from '../Chat/utils';
18
+ import { SESSION_STORE_KEY } from '../../common/constants';
19
+
20
+ export const PIPChat = () => {
21
+ const messages = useHMSStore(selectHMSMessages);
22
+ const localPeerID = useHMSStore(selectLocalPeerID);
23
+ const count = useHMSStore(selectUnreadHMSMessagesCount);
24
+ const [unreadMessageCount, setUnreadMessageCount] = useState(0);
25
+
26
+ const getSenderName = useCallback(
27
+ (senderName: string, senderID?: string) => {
28
+ const slicedName = senderName.length > 10 ? senderName.slice(0, 10) + '...' : senderName;
29
+ return slicedName + (senderID === localPeerID ? ' (You)' : '');
30
+ },
31
+ [localPeerID],
32
+ );
33
+
34
+ useEffect(() => {
35
+ const timeoutId = setTimeout(() => {
36
+ setUnreadMessageCount(count);
37
+ }, 100);
38
+ return () => clearTimeout(timeoutId);
39
+ }, [count]);
40
+
41
+ const blacklistedMessageIDs = useHMSStore(selectSessionStore(SESSION_STORE_KEY.CHAT_MESSAGE_BLACKLIST));
42
+ const filteredMessages = useMemo(() => {
43
+ const blacklistedMessageIDSet = new Set(blacklistedMessageIDs || []);
44
+ return messages?.filter(message => message.type === 'chat' && !blacklistedMessageIDSet.has(message.id)) || [];
45
+ }, [blacklistedMessageIDs, messages]);
46
+ const { elements } = useRoomLayoutConferencingScreen();
47
+ const message_placeholder = elements?.chat?.message_placeholder || 'Send a message';
48
+ const canSendChatMessages = !!elements?.chat?.public_chat_enabled || !!elements?.chat?.roles_whitelist?.length;
49
+
50
+ return (
51
+ <div style={{ height: '100%' }}>
52
+ <Box
53
+ id="chat-container"
54
+ css={{
55
+ bg: '$surface_dim',
56
+ overflowY: 'auto',
57
+ // Subtracting height of footer
58
+ h: canSendChatMessages ? 'calc(100% - 90px)' : '100%',
59
+ position: 'relative',
60
+ }}
61
+ >
62
+ {unreadMessageCount ? (
63
+ <Box
64
+ id="new-message-notif"
65
+ style={{
66
+ position: 'fixed',
67
+ bottom: '76px',
68
+ right: '4px',
69
+ }}
70
+ >
71
+ <Text
72
+ variant="xs"
73
+ css={{ cursor: 'pointer' }}
74
+ style={{ color: 'white', background: 'gray', padding: '4px', borderRadius: '4px' }}
75
+ >
76
+ {unreadMessageCount === 1 ? 'New message' : `${unreadMessageCount} new messages`}
77
+ </Text>
78
+ </Box>
79
+ ) : (
80
+ ''
81
+ )}
82
+ {filteredMessages.length === 0 ? (
83
+ <div
84
+ style={{ display: 'flex', height: '100%', width: '100%', alignItems: 'center', justifyContent: 'center' }}
85
+ >
86
+ <Text>No messages here yet</Text>
87
+ </div>
88
+ ) : (
89
+ filteredMessages.map(message => (
90
+ <Box className="pip-message" key={message.id} id={message.id} style={{ padding: '8px 0.75rem' }}>
91
+ <Flex style={{ width: '100%', alignItems: 'center', justifyContent: 'between' }}>
92
+ <Text
93
+ style={{ display: 'flex', justifyContent: 'between', width: '100%', alignItems: 'center' }}
94
+ css={{
95
+ color: '$on_surface_high',
96
+ fontWeight: '$semiBold',
97
+ }}
98
+ >
99
+ <Flex style={{ flexGrow: 1, gap: '2px', alignItems: 'center' }}>
100
+ {message.senderName === 'You' || !message.senderName ? (
101
+ <Text as="span" variant="sub2" css={{ color: '$on_surface_high', fontWeight: '$semiBold' }}>
102
+ {message.senderName || 'Anonymous'}
103
+ </Text>
104
+ ) : (
105
+ <Tooltip title={message.senderName} side="top" align="start">
106
+ <Text as="span" variant="sub2" css={{ color: '$on_surface_high', fontWeight: '$semiBold' }}>
107
+ {getSenderName(message.senderName, message?.sender)}
108
+ </Text>
109
+ </Tooltip>
110
+ )}
111
+ {message.recipientRoles ? (
112
+ <Text as="span" variant="sub2" css={{ color: '$on_surface_high', fontWeight: '$semiBold' }}>
113
+ to {message.recipientRoles} (Group)
114
+ </Text>
115
+ ) : null}
116
+ {message.recipientPeer ? (
117
+ <Text as="span" variant="sub2" css={{ color: '$on_surface_high', fontWeight: '$semiBold' }}>
118
+ (DM)
119
+ </Text>
120
+ ) : null}
121
+ </Flex>
122
+
123
+ <Text
124
+ variant="xs"
125
+ css={{
126
+ color: '$on_surface_medium',
127
+ flexShrink: 0,
128
+ p: '$2',
129
+ whitespace: 'nowrap',
130
+ }}
131
+ >
132
+ {formatTime(message.time)}
133
+ </Text>
134
+ </Text>
135
+ </Flex>
136
+ <Text
137
+ variant="sm"
138
+ css={{
139
+ w: '100%',
140
+ mt: '$2',
141
+ wordBreak: 'break-word',
142
+ whiteSpace: 'pre-wrap',
143
+ userSelect: 'all',
144
+ color: '$on_surface_high',
145
+ }}
146
+ >
147
+ <AnnotisedMessage message={message.message} />
148
+ </Text>
149
+ </Box>
150
+ ))
151
+ )}
152
+ <div id="marker" style={{ height: filteredMessages.length ? '1px' : 0 }} />
153
+ </Box>
154
+ {canSendChatMessages && (
155
+ <Box css={{ bg: '$surface_dim' }}>
156
+ <Flex css={{ px: '$4', pb: '3px', gap: '$2', alignItems: 'center' }}>
157
+ <Text variant="caption">To:</Text>
158
+ <Flex css={{ bg: '$primary_bright', color: '$on_primary_high', r: '$2' }}>
159
+ <select
160
+ id="selector"
161
+ style={{
162
+ background: 'inherit',
163
+ color: 'inherit',
164
+ border: 'none',
165
+ outline: 'none',
166
+ borderRadius: '4px',
167
+ padding: '0 2px',
168
+ }}
169
+ defaultValue={elements.chat?.public_chat_enabled ? 'Everyone' : elements.chat?.roles_whitelist?.[0]}
170
+ >
171
+ {elements.chat?.roles_whitelist?.map(role => (
172
+ <option key={role} value={role}>
173
+ {role}
174
+ </option>
175
+ ))}
176
+ {elements.chat?.public_chat_enabled ? <option value="Everyone">Everyone</option> : ''}
177
+ </select>
178
+ </Flex>
179
+ </Flex>
180
+ <Flex
181
+ align="center"
182
+ css={{
183
+ bg: '$surface_default',
184
+ minHeight: '$16',
185
+ width: '100%',
186
+ py: '$6',
187
+ pl: '$4',
188
+ boxSizing: 'border-box',
189
+ gap: '$2',
190
+ r: '$2',
191
+ }}
192
+ >
193
+ <TextArea
194
+ id="chat-input"
195
+ maxLength={CHAT_MESSAGE_LIMIT}
196
+ style={{ border: 'none', resize: 'none' }}
197
+ css={{
198
+ w: '100%',
199
+ c: '$on_surface_high',
200
+ padding: '0.25rem !important',
201
+ }}
202
+ placeholder={message_placeholder}
203
+ required
204
+ autoComplete="off"
205
+ aria-autocomplete="none"
206
+ />
207
+
208
+ <IconButton
209
+ id="send-btn"
210
+ css={{
211
+ ml: 'auto',
212
+ height: 'max-content',
213
+ mr: '$4',
214
+ '&:hover': { c: '$on_surface_medium' },
215
+ }}
216
+ data-testid="send_msg_btn"
217
+ >
218
+ <SendIcon />
219
+ </IconButton>
220
+ </Flex>
221
+ </Box>
222
+ )}
223
+ </div>
224
+ );
225
+ };
@@ -0,0 +1,18 @@
1
+ import React from 'react';
2
+ import { ExternalLinkIcon } from '@100mslive/react-icons';
3
+ import { Dropdown } from '../../../Dropdown';
4
+ import { Text } from '../../../Text';
5
+
6
+ export const PIPChatOption = ({ openChat, showPIPChat }: { openChat: () => void; showPIPChat: boolean }) => {
7
+ if (!showPIPChat) {
8
+ return <></>;
9
+ }
10
+ return (
11
+ <Dropdown.Item onClick={openChat} data-testid="brb_btn">
12
+ <ExternalLinkIcon height={18} width={18} style={{ padding: '0 $2' }} />
13
+ <Text variant="sm" css={{ ml: '$4', color: '$on_surface_high' }}>
14
+ Pop out Chat
15
+ </Text>
16
+ </Dropdown.Item>
17
+ );
18
+ };
@@ -0,0 +1,56 @@
1
+ import React, { useCallback, useMemo, useState } from 'react';
2
+ import { PIPContext } from './context';
3
+
4
+ type PIPProviderProps = {
5
+ children: React.ReactNode;
6
+ };
7
+
8
+ export const PIPProvider = ({ children }: PIPProviderProps) => {
9
+ // Detect if the feature is available.
10
+ const isSupported = 'documentPictureInPicture' in window;
11
+
12
+ // Expose pipWindow that is currently active
13
+ const [pipWindow, setPipWindow] = useState<Window | null>(null);
14
+
15
+ // Close pipWidnow programmatically
16
+ const closePipWindow = useCallback(() => {
17
+ if (pipWindow != null) {
18
+ pipWindow.close();
19
+ setPipWindow(null);
20
+ }
21
+ }, [pipWindow]);
22
+
23
+ // Open new pipWindow
24
+ const requestPipWindow = useCallback(
25
+ async (width: number, height: number) => {
26
+ // We don't want to allow multiple requests.
27
+ if (pipWindow != null) {
28
+ return;
29
+ }
30
+ // @ts-ignore for documentPIP
31
+ const pip = await window.documentPictureInPicture.requestWindow({
32
+ width,
33
+ height,
34
+ });
35
+
36
+ // Detect when window is closed by user
37
+ pip.addEventListener('pagehide', () => {
38
+ setPipWindow(null);
39
+ });
40
+
41
+ setPipWindow(pip);
42
+ },
43
+ [pipWindow],
44
+ );
45
+
46
+ const value = useMemo(() => {
47
+ return {
48
+ isSupported,
49
+ pipWindow,
50
+ requestPipWindow,
51
+ closePipWindow,
52
+ };
53
+ }, [closePipWindow, isSupported, pipWindow, requestPipWindow]);
54
+
55
+ return <PIPContext.Provider value={value}>{children}</PIPContext.Provider>;
56
+ };
@@ -0,0 +1,13 @@
1
+ import React from 'react';
2
+ import { createPortal } from 'react-dom';
3
+
4
+ type PIPWindowProps = {
5
+ pipWindow: Window;
6
+ children: React.ReactNode;
7
+ };
8
+
9
+ export const PIPWindow = ({ pipWindow, children }: PIPWindowProps) => {
10
+ pipWindow.document.body.style.margin = '0';
11
+ pipWindow.document.body.style.overflowX = 'hidden';
12
+ return createPortal(children, pipWindow.document.body);
13
+ };