@100mslive/roomkit-react 0.3.16-alpha.1 → 0.3.16-alpha.11

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 (33) hide show
  1. package/dist/{HLSView-ERPYXVOY.js → HLSView-CTSCXXFA.js} +2 -2
  2. package/dist/{HLSView-T2PXKR6I.css → HLSView-Y2452RZU.css} +3 -3
  3. package/dist/{HLSView-T2PXKR6I.css.map → HLSView-Y2452RZU.css.map} +1 -1
  4. package/dist/Prebuilt/components/IconButtonWithOptions/IconButtonWithOptions.d.ts +2 -1
  5. package/dist/{chunk-NKC36NL4.js → chunk-I7DPNCT4.js} +1066 -968
  6. package/dist/chunk-I7DPNCT4.js.map +7 -0
  7. package/dist/index.cjs.css +2 -2
  8. package/dist/index.cjs.css.map +1 -1
  9. package/dist/index.cjs.js +1480 -1384
  10. package/dist/index.cjs.js.map +4 -4
  11. package/dist/index.css +2 -2
  12. package/dist/index.css.map +1 -1
  13. package/dist/index.js +1 -1
  14. package/dist/meta.cjs.json +110 -80
  15. package/dist/meta.esbuild.json +123 -93
  16. package/package.json +7 -7
  17. package/src/Prebuilt/App.tsx +21 -18
  18. package/src/Prebuilt/components/AudioVideoToggle.tsx +6 -12
  19. package/src/Prebuilt/components/ConferenceScreen.tsx +13 -1
  20. package/src/Prebuilt/components/Footer/ParticipantList.tsx +1 -4
  21. package/src/Prebuilt/components/IconButtonWithOptions/IconButtonWithOptions.tsx +9 -1
  22. package/src/Prebuilt/components/MoreSettings/MoreSettings.tsx +1 -4
  23. package/src/Prebuilt/components/MoreSettings/SplitComponents/DesktopOptions.tsx +12 -2
  24. package/src/Prebuilt/components/Notifications/HandRaisedNotifications.tsx +33 -0
  25. package/src/Prebuilt/components/Notifications/Notifications.tsx +3 -1
  26. package/src/Prebuilt/components/PIP/PIPChat.tsx +62 -14
  27. package/src/Prebuilt/components/PIP/PIPWindow.tsx +1 -1
  28. package/src/Prebuilt/components/PIP/usePIPChat.tsx +2 -0
  29. package/src/Prebuilt/components/Preview/PreviewJoin.tsx +3 -1
  30. package/src/Prebuilt/components/Toast/ToastConfig.jsx +2 -2
  31. package/src/Prebuilt/components/VirtualBackground/VBOption.tsx +1 -1
  32. package/dist/chunk-NKC36NL4.js.map +0 -7
  33. /package/dist/{HLSView-ERPYXVOY.js.map → HLSView-CTSCXXFA.js.map} +0 -0
@@ -23,6 +23,7 @@ import { KeyboardHandler } from './components/Input/KeyboardInputManager';
23
23
  import { LeaveScreen } from './components/LeaveScreen';
24
24
  import { MwebLandscapePrompt } from './components/MwebLandscapePrompt';
25
25
  import { Notifications } from './components/Notifications';
26
+ import { PIPProvider } from './components/PIP/PIPProvider';
26
27
  import { PreviewScreen } from './components/Preview/PreviewScreen';
27
28
  // @ts-ignore: No implicit Any
28
29
  import { ToastContainer } from './components/Toast/ToastContainer';
@@ -218,24 +219,26 @@ export const HMSPrebuilt = React.forwardRef<HMSPrebuiltRefType, HMSPrebuiltProps
218
219
  },
219
220
  }}
220
221
  >
221
- <Init />
222
- <DialogContainerProvider dialogContainerSelector={containerSelector}>
223
- <Box
224
- className={DEFAULT_PORTAL_CONTAINER.slice(1)} // Skips the '.' in the selector
225
- css={{
226
- bg: '$background_dim',
227
- size: '100%',
228
- lineHeight: '1.5',
229
- '-webkit-text-size-adjust': '100%',
230
- position: 'relative',
231
- }}
232
- >
233
- <AppRoutes
234
- authTokenByRoomCodeEndpoint={tokenByRoomCodeEndpoint}
235
- defaultAuthToken={authToken}
236
- />
237
- </Box>
238
- </DialogContainerProvider>
222
+ <PIPProvider>
223
+ <Init />
224
+ <DialogContainerProvider dialogContainerSelector={containerSelector}>
225
+ <Box
226
+ className={DEFAULT_PORTAL_CONTAINER.slice(1)} // Skips the '.' in the selector
227
+ css={{
228
+ bg: '$background_dim',
229
+ size: '100%',
230
+ lineHeight: '1.5',
231
+ '-webkit-text-size-adjust': '100%',
232
+ position: 'relative',
233
+ }}
234
+ >
235
+ <AppRoutes
236
+ authTokenByRoomCodeEndpoint={tokenByRoomCodeEndpoint}
237
+ defaultAuthToken={authToken}
238
+ />
239
+ </Box>
240
+ </DialogContainerProvider>
241
+ </PIPProvider>
239
242
  </HMSThemeProvider>
240
243
  );
241
244
  }}
@@ -44,7 +44,7 @@ import { useRoomLayoutConferencingScreen } from '../provider/roomLayoutProvider/
44
44
  // @ts-ignore: No implicit Any
45
45
  import { useIsNoiseCancellationEnabled, useSetNoiseCancellation } from './AppData/useUISettings';
46
46
  import { useAudioOutputTest } from './hooks/useAudioOutputTest';
47
- import { isMacOS, TEST_AUDIO_URL } from '../common/constants';
47
+ import { isAndroid, isIOS, isMacOS, TEST_AUDIO_URL } from '../common/constants';
48
48
 
49
49
  const krispPlugin = new HMSKrispPlugin();
50
50
  // const optionsCSS = { fontWeight: '$semiBold', color: '$on_surface_high', w: '100%' };
@@ -310,10 +310,9 @@ export const AudioVideoToggle = ({ hideOptions = false }: { hideOptions?: boolea
310
310
  disabled={!toggleAudio}
311
311
  hideOptions={hideOptions || !hasAudioDevices}
312
312
  onDisabledClick={toggleAudio}
313
+ testid="audio_toggle_btn"
313
314
  tooltipMessage={`Turn ${isLocalAudioEnabled ? 'off' : 'on'} audio (${isMacOS ? '⌘' : 'ctrl'} + d)`}
314
- icon={
315
- !isLocalAudioEnabled ? <MicOffIcon data-testid="audio_off_btn" /> : <MicOnIcon data-testid="audio_on_btn" />
316
- }
315
+ icon={!isLocalAudioEnabled ? <MicOffIcon /> : <MicOnIcon />}
317
316
  active={isLocalAudioEnabled}
318
317
  onClick={toggleAudio}
319
318
  key="toggleAudio"
@@ -353,13 +352,8 @@ export const AudioVideoToggle = ({ hideOptions = false }: { hideOptions?: boolea
353
352
  hideOptions={hideOptions || !hasVideoDevices}
354
353
  onDisabledClick={toggleVideo}
355
354
  tooltipMessage={`Turn ${isLocalVideoEnabled ? 'off' : 'on'} video (${isMacOS ? '⌘' : 'ctrl'} + e)`}
356
- icon={
357
- !isLocalVideoEnabled ? (
358
- <VideoOffIcon data-testid="video_off_btn" />
359
- ) : (
360
- <VideoOnIcon data-testid="video_on_btn" />
361
- )
362
- }
355
+ testid="video_toggle_btn"
356
+ icon={!isLocalVideoEnabled ? <VideoOffIcon /> : <VideoOnIcon />}
363
357
  key="toggleVideo"
364
358
  active={isLocalVideoEnabled}
365
359
  onClick={toggleVideo}
@@ -372,7 +366,7 @@ export const AudioVideoToggle = ({ hideOptions = false }: { hideOptions?: boolea
372
366
  </IconButtonWithOptions>
373
367
  ) : null}
374
368
 
375
- {localVideoTrack?.facingMode && roomState === HMSRoomState.Preview ? (
369
+ {localVideoTrack?.facingMode && roomState === HMSRoomState.Preview && (isIOS || isAndroid) ? (
376
370
  <Tooltip title="Switch Camera" key="switchCamera">
377
371
  <IconButton
378
372
  onClick={async () => {
@@ -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>
@@ -90,6 +90,7 @@ export const IconButtonWithOptions = ({
90
90
  onDisabledClick = () => {
91
91
  return;
92
92
  },
93
+ testid = '',
93
94
  tooltipMessage = '',
94
95
  icon,
95
96
  children,
@@ -103,6 +104,7 @@ export const IconButtonWithOptions = ({
103
104
  onDisabledClick: () => void;
104
105
  icon: React.ReactNode;
105
106
  children: React.ReactNode;
107
+ testid?: string;
106
108
  hideOptions?: boolean;
107
109
  active: boolean;
108
110
  disabled?: boolean;
@@ -111,7 +113,13 @@ export const IconButtonWithOptions = ({
111
113
  const commonProps = { disabled, active };
112
114
  return (
113
115
  <Flex>
114
- <IconSection {...commonProps} onClick={onClick} hideOptions={hideOptions} className="__cancel-drag-event">
116
+ <IconSection
117
+ data-testid={testid}
118
+ {...commonProps}
119
+ onClick={onClick}
120
+ hideOptions={hideOptions}
121
+ className="__cancel-drag-event"
122
+ >
115
123
  <Tooltip disabled={!tooltipMessage} title={tooltipMessage}>
116
124
  <Icon {...commonProps}>{icon}</Icon>
117
125
  </Tooltip>
@@ -10,7 +10,6 @@ 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';
14
13
  import { useLandscapeHLSStream } from '../../common/hooks';
15
14
 
16
15
  export const MoreSettings = ({
@@ -25,8 +24,6 @@ export const MoreSettings = ({
25
24
  return isMobile || isLandscapeHLSStream ? (
26
25
  <MwebOptions elements={elements} screenType={screenType} />
27
26
  ) : (
28
- <PIPProvider>
29
- <DesktopOptions elements={elements} screenType={screenType} />
30
- </PIPProvider>
27
+ <DesktopOptions elements={elements} screenType={screenType} />
31
28
  );
32
29
  };
@@ -48,12 +48,14 @@ import { CaptionModal } from '../CaptionModal';
48
48
  import { FullScreenItem } from '../FullScreenItem';
49
49
  import { MuteAllModal } from '../MuteAllModal';
50
50
  // @ts-ignore: No implicit any
51
+ import { useIsSidepaneTypeOpen, useSidepaneToggle } from '../../AppData/useSidepane';
52
+ // @ts-ignore: No implicit any
51
53
  import { useDropdownList } from '../../hooks/useDropdownList';
52
54
  import { useMyMetadata } from '../../hooks/useMetadata';
53
55
  // @ts-ignore: No implicit any
54
56
  import { usePIPChat } from '../../PIP/usePIPChat';
55
57
  // @ts-ignore: No implicit any
56
- import { APP_DATA, isMacOS } from '../../../common/constants';
58
+ import { APP_DATA, isMacOS, SIDE_PANE_OPTIONS } from '../../../common/constants';
57
59
 
58
60
  const MODALS = {
59
61
  CHANGE_NAME: 'changeName',
@@ -85,6 +87,8 @@ export const DesktopOptions = ({
85
87
  const isTranscriptionAllowed = useHMSStore(selectIsTranscriptionAllowedByMode(HMSTranscriptionMode.CAPTION));
86
88
  const isTranscriptionEnabled = useHMSStore(selectIsTranscriptionEnabled);
87
89
  const { isSupported, pipWindow, requestPipWindow } = usePIPChat();
90
+ const isChatOpen = useIsSidepaneTypeOpen(SIDE_PANE_OPTIONS.CHAT);
91
+ const toggleChat = useSidepaneToggle(SIDE_PANE_OPTIONS.CHAT);
88
92
  const showPipChatOption = !!elements?.chat && isSupported;
89
93
 
90
94
  useDropdownList({ open: openModals.size > 0, name: 'MoreSettings' });
@@ -180,7 +184,13 @@ export const DesktopOptions = ({
180
184
  </Dropdown.Item>
181
185
  ) : null}
182
186
 
183
- <PIPChatOption showPIPChat={showPipChatOption} openChat={async () => await requestPipWindow(350, 500)} />
187
+ <PIPChatOption
188
+ showPIPChat={showPipChatOption}
189
+ openChat={async () => {
190
+ isChatOpen && toggleChat();
191
+ await requestPipWindow(350, 500);
192
+ }}
193
+ />
184
194
  <FullScreenItem />
185
195
  <Dropdown.ItemSeparator css={{ mx: 0 }} />
186
196
  <Dropdown.Item onClick={() => updateState(MODALS.DEVICE_SETTINGS, true)} data-testid="device_settings_btn">
@@ -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
  };
@@ -35,6 +35,7 @@ import { useRoomLayoutConferencingScreen } from '../../provider/roomLayoutProvid
35
35
  import { usePollViewToggle } from '../AppData/useSidepane';
36
36
  // @ts-ignore: No implicit Any
37
37
  import { useIsNotificationDisabled, useSubscribedNotifications } from '../AppData/useUISettings';
38
+ import { usePIPWindow } from '../PIP/usePIPWindow';
38
39
  import { ROLE_CHANGE_DECLINED } from '../../common/constants';
39
40
 
40
41
  const pollToastKey: Record<string, string> = {};
@@ -52,6 +53,7 @@ export function Notifications() {
52
53
  const { showNotification } = useAwayNotifications();
53
54
  const amIScreenSharing = useHMSStore(selectIsLocalScreenShared);
54
55
  const logoURL = useRoomLayout()?.logo?.url;
56
+ const { pipWindow } = usePIPWindow();
55
57
 
56
58
  const handleRoleChangeDenied = useCallback((request: HMSRoleChangeRequest & { peerName: string }) => {
57
59
  ToastManager.addToast({
@@ -172,7 +174,7 @@ export function Notifications() {
172
174
  }
173
175
  break;
174
176
  case HMSNotificationTypes.NEW_MESSAGE:
175
- if (amIScreenSharing && !notification.data?.ignored) {
177
+ if (amIScreenSharing && !notification.data?.ignored && !pipWindow) {
176
178
  showNotification(`New message from ${notification.data.senderName}`, {
177
179
  body: notification.data.message,
178
180
  icon: logoURL,
@@ -2,6 +2,7 @@ import React, { useCallback, useEffect, useMemo, useState } from 'react';
2
2
  import {
3
3
  selectHMSMessages,
4
4
  selectLocalPeerID,
5
+ selectPeerNameByID,
5
6
  selectSessionStore,
6
7
  selectUnreadHMSMessagesCount,
7
8
  useHMSStore,
@@ -14,6 +15,7 @@ import { Tooltip } from '../../../Tooltip';
14
15
  import IconButton from '../../IconButton';
15
16
  import { AnnotisedMessage } from '../Chat/ChatBody';
16
17
  import { useRoomLayoutConferencingScreen } from '../../provider/roomLayoutProvider/hooks/useRoomLayoutScreen';
18
+ import { useIsPeerBlacklisted } from '../hooks/useChatBlacklist';
17
19
  import { CHAT_MESSAGE_LIMIT, formatTime } from '../Chat/utils';
18
20
  import { SESSION_STORE_KEY } from '../../common/constants';
19
21
 
@@ -43,10 +45,29 @@ export const PIPChat = () => {
43
45
  const blacklistedMessageIDSet = new Set(blacklistedMessageIDs || []);
44
46
  return messages?.filter(message => message.type === 'chat' && !blacklistedMessageIDSet.has(message.id)) || [];
45
47
  }, [blacklistedMessageIDs, messages]);
48
+ const { enabled: isChatEnabled = true, updatedBy: chatStateUpdatedBy = '' } =
49
+ useHMSStore(selectSessionStore(SESSION_STORE_KEY.CHAT_STATE)) || {};
50
+ const isLocalPeerBlacklisted = useIsPeerBlacklisted({ local: true });
46
51
  const { elements } = useRoomLayoutConferencingScreen();
47
52
  const message_placeholder = elements?.chat?.message_placeholder || 'Send a message';
48
53
  const canSendChatMessages = !!elements?.chat?.public_chat_enabled || !!elements?.chat?.roles_whitelist?.length;
49
54
 
55
+ const getChatStatus = useCallback(() => {
56
+ if (isLocalPeerBlacklisted) return "You've been blocked from sending messages";
57
+ if (!isChatEnabled)
58
+ return `Chat has been paused by ${
59
+ chatStateUpdatedBy.peerId === localPeerID ? 'you' : chatStateUpdatedBy?.userName
60
+ }`;
61
+ return message_placeholder;
62
+ }, [
63
+ chatStateUpdatedBy.peerId,
64
+ chatStateUpdatedBy?.userName,
65
+ isChatEnabled,
66
+ isLocalPeerBlacklisted,
67
+ localPeerID,
68
+ message_placeholder,
69
+ ]);
70
+
50
71
  return (
51
72
  <div style={{ height: '100%' }}>
52
73
  <Box
@@ -55,7 +76,7 @@ export const PIPChat = () => {
55
76
  bg: '$surface_dim',
56
77
  overflowY: 'auto',
57
78
  // Subtracting height of footer
58
- h: canSendChatMessages ? 'calc(100% - 90px)' : '100%',
79
+ h: canSendChatMessages ? 'calc(100% - 87px)' : '100%',
59
80
  position: 'relative',
60
81
  }}
61
82
  >
@@ -108,16 +129,11 @@ export const PIPChat = () => {
108
129
  </Text>
109
130
  </Tooltip>
110
131
  )}
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}
132
+ <MessageTitle
133
+ localPeerID={localPeerID}
134
+ recipientPeer={message.recipientPeer}
135
+ recipientRoles={message.recipientRoles}
136
+ />
121
137
  </Flex>
122
138
 
123
139
  <Text
@@ -193,13 +209,16 @@ export const PIPChat = () => {
193
209
  <TextArea
194
210
  id="chat-input"
195
211
  maxLength={CHAT_MESSAGE_LIMIT}
196
- style={{ border: 'none', resize: 'none' }}
212
+ disabled={!isChatEnabled || isLocalPeerBlacklisted}
213
+ rows={1}
197
214
  css={{
198
215
  w: '100%',
199
216
  c: '$on_surface_high',
200
- padding: '0.25rem !important',
217
+ p: '0.75rem 0.75rem !important',
218
+ border: 'none',
219
+ resize: 'none',
201
220
  }}
202
- placeholder={message_placeholder}
221
+ placeholder={getChatStatus()}
203
222
  required
204
223
  autoComplete="off"
205
224
  aria-autocomplete="none"
@@ -207,6 +226,8 @@ export const PIPChat = () => {
207
226
 
208
227
  <IconButton
209
228
  id="send-btn"
229
+ disabled={!isChatEnabled || isLocalPeerBlacklisted}
230
+ title={getChatStatus()}
210
231
  css={{
211
232
  ml: 'auto',
212
233
  height: 'max-content',
@@ -223,3 +244,30 @@ export const PIPChat = () => {
223
244
  </div>
224
245
  );
225
246
  };
247
+
248
+ const MessageTitle = ({
249
+ recipientPeer,
250
+ recipientRoles,
251
+ localPeerID,
252
+ }: {
253
+ recipientPeer?: string;
254
+ recipientRoles?: string[];
255
+ localPeerID: string;
256
+ }) => {
257
+ const peerName = useHMSStore(selectPeerNameByID(recipientPeer));
258
+
259
+ return (
260
+ <>
261
+ {recipientRoles ? (
262
+ <Text as="span" variant="sub2" css={{ color: '$on_surface_high', fontWeight: '$semiBold' }}>
263
+ to {recipientRoles} (Group)
264
+ </Text>
265
+ ) : null}
266
+ {recipientPeer ? (
267
+ <Text as="span" variant="sub2" css={{ color: '$on_surface_high', fontWeight: '$semiBold' }}>
268
+ to {recipientPeer === localPeerID ? 'You' : peerName} (DM)
269
+ </Text>
270
+ ) : null}
271
+ </>
272
+ );
273
+ };
@@ -8,6 +8,6 @@ type PIPWindowProps = {
8
8
 
9
9
  export const PIPWindow = ({ pipWindow, children }: PIPWindowProps) => {
10
10
  pipWindow.document.body.style.margin = '0';
11
- pipWindow.document.body.style.overflowX = 'hidden';
11
+ pipWindow.document.body.style.overflow = 'clip';
12
12
  return createPortal(children, pipWindow.document.body);
13
13
  };
@@ -25,6 +25,8 @@ export const usePIPChat = () => {
25
25
  const pipChatInput = pipWindow.document.getElementById('chat-input') as HTMLTextAreaElement;
26
26
  const marker = pipWindow.document.getElementById('marker');
27
27
 
28
+ marker?.scrollIntoView({ block: 'end' });
29
+
28
30
  const observer = new IntersectionObserver(
29
31
  entries => {
30
32
  entries.forEach(entry => {
@@ -8,6 +8,7 @@ import {
8
8
  selectRoomState,
9
9
  selectVideoTrackByID,
10
10
  useAVToggle,
11
+ useAwayNotifications,
11
12
  useHMSStore,
12
13
  useParticipants,
13
14
  usePreviewJoin,
@@ -100,6 +101,7 @@ const PreviewJoin = ({
100
101
  },
101
102
  asRole,
102
103
  });
104
+ const { requestPermission } = useAwayNotifications();
103
105
  const roomState = useHMSStore(selectRoomState);
104
106
  const savePreferenceAndJoin = useCallback(() => {
105
107
  setPreviewPreference({
@@ -115,7 +117,7 @@ const PreviewJoin = ({
115
117
  if (skipPreview) {
116
118
  savePreferenceAndJoin();
117
119
  } else {
118
- preview();
120
+ preview().then(() => requestPermission());
119
121
  }
120
122
  }
121
123
  // eslint-disable-next-line react-hooks/exhaustive-deps
@@ -111,7 +111,7 @@ export const ToastConfig = {
111
111
  const count = new Set(notifications.map(notification => notification.data?.id)).size;
112
112
  return {
113
113
  title: `${notifications[notifications.length - 1].data?.name} ${
114
- count > 1 ? `${count} and others` : ''
114
+ count > 1 ? `and ${count} others` : ''
115
115
  } raised hand`,
116
116
  icon: <HandIcon />,
117
117
  };
@@ -129,7 +129,7 @@ export const ToastConfig = {
129
129
  const count = new Set(notifications.map(notification => notification.data?.id)).size;
130
130
  return {
131
131
  title: `${notifications[notifications.length - 1].data?.name} ${
132
- count > 1 ? `${count} and others` : ''
132
+ count > 1 ? `and ${count} others` : ''
133
133
  } raised hand`,
134
134
  icon: <HandIcon />,
135
135
  action: <HandRaiseAction isSingleHandRaise={false} />,
@@ -26,7 +26,7 @@ const Root = ({
26
26
  border: `4px solid ${isActive ? '$primary_default' : '$surface_dim'}`,
27
27
  cursor: 'pointer',
28
28
  '&:hover': { border: '4px solid $primary_dim' },
29
- ...(mediaURL ? { height: '$20', backgroundImage: `url(${mediaURL})`, backgroundSize: 'cover' } : {}),
29
+ ...(mediaURL ? { height: '$20', backgroundImage: `url("${mediaURL}")`, backgroundSize: 'cover' } : {}),
30
30
  }}
31
31
  onClick={async () => {
32
32
  await onClick?.();