@100mslive/roomkit-react 0.1.12 → 0.1.13

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 (68) hide show
  1. package/dist/{HLSView-PWVM4OIP.js → HLSView-IENE4HRP.js} +2 -2
  2. package/dist/Prebuilt/App.d.ts +0 -2
  3. package/dist/Prebuilt/AppContext.d.ts +0 -2
  4. package/dist/Prebuilt/common/constants.d.ts +110 -0
  5. package/dist/Prebuilt/components/AppData/AppData.d.ts +2 -0
  6. package/dist/Prebuilt/components/Chat/MwebChatOption.d.ts +7 -0
  7. package/dist/Prebuilt/components/Chat/Navigation.d.ts +8 -0
  8. package/dist/Prebuilt/components/Chat/PinnedMessage.d.ts +4 -0
  9. package/dist/Prebuilt/components/Footer/Footer.d.ts +3 -2
  10. package/dist/Prebuilt/components/Notifications/ChatNotifications.d.ts +2 -0
  11. package/dist/Prebuilt/components/Preview/PreviewJoin.d.ts +2 -1
  12. package/dist/Prebuilt/components/VirtualBackground/VBCollection.d.ts +2 -1
  13. package/dist/Prebuilt/components/VirtualBackground/VBPicker.d.ts +3 -0
  14. package/dist/Prebuilt/components/VirtualBackground/constants.d.ts +0 -6
  15. package/dist/Prebuilt/components/hooks/useChatBlacklist.d.ts +4 -0
  16. package/dist/Prebuilt/components/hooks/useSetPinnedMessages.d.ts +16 -0
  17. package/dist/Prebuilt/provider/roomLayoutProvider/hooks/useRoomLayoutScreen.d.ts +3 -2
  18. package/dist/{chunk-WREKGNP6.js → chunk-U4AB6X6M.js} +2178 -1148
  19. package/dist/chunk-U4AB6X6M.js.map +7 -0
  20. package/dist/index.cjs.js +2746 -1638
  21. package/dist/index.cjs.js.map +4 -4
  22. package/dist/index.js +1 -1
  23. package/dist/meta.cjs.json +803 -208
  24. package/dist/meta.esbuild.json +812 -217
  25. package/package.json +8 -7
  26. package/src/Prebuilt/App.tsx +3 -14
  27. package/src/Prebuilt/AppContext.tsx +0 -2
  28. package/src/Prebuilt/common/{constants.js → constants.ts} +22 -20
  29. package/src/Prebuilt/components/AppData/{AppData.jsx → AppData.tsx} +8 -22
  30. package/src/Prebuilt/components/AppData/useUISettings.js +0 -4
  31. package/src/Prebuilt/components/AuthToken.jsx +15 -5
  32. package/src/Prebuilt/components/Chat/Chat.jsx +49 -73
  33. package/src/Prebuilt/components/Chat/ChatBody.jsx +241 -51
  34. package/src/Prebuilt/components/Chat/ChatFooter.tsx +56 -6
  35. package/src/Prebuilt/components/Chat/ChatStates.jsx +68 -0
  36. package/src/Prebuilt/components/Chat/MwebChatOption.tsx +24 -0
  37. package/src/Prebuilt/components/Chat/Navigation.tsx +60 -0
  38. package/src/Prebuilt/components/Chat/PinnedMessage.tsx +116 -0
  39. package/src/Prebuilt/components/Footer/Footer.tsx +4 -7
  40. package/src/Prebuilt/components/Header/common.jsx +1 -1
  41. package/src/Prebuilt/components/Notifications/ChatNotifications.tsx +34 -0
  42. package/src/Prebuilt/components/Notifications/Notifications.tsx +2 -0
  43. package/src/Prebuilt/components/Polls/Voting/QuestionCard.jsx +6 -4
  44. package/src/Prebuilt/components/Polls/Voting/TimedVoting.jsx +3 -2
  45. package/src/Prebuilt/components/Polls/common/VoteCount.jsx +6 -4
  46. package/src/Prebuilt/components/Preview/PreviewJoin.tsx +7 -9
  47. package/src/Prebuilt/components/RoleChangeRequest/RoleChangeRequestModal.tsx +4 -1
  48. package/src/Prebuilt/components/Settings/DeviceSettings.jsx +1 -1
  49. package/src/Prebuilt/components/SidePaneTabs.tsx +3 -2
  50. package/src/Prebuilt/components/Toast/ToastConfig.jsx +1 -0
  51. package/src/Prebuilt/components/VirtualBackground/VBCollection.tsx +4 -4
  52. package/src/Prebuilt/components/VirtualBackground/{VBPicker.jsx → VBPicker.tsx} +64 -44
  53. package/src/Prebuilt/components/VirtualBackground/constants.ts +0 -8
  54. package/src/Prebuilt/components/hooks/useAutoStartStreaming.tsx +10 -1
  55. package/src/Prebuilt/components/hooks/useChatBlacklist.ts +21 -0
  56. package/src/Prebuilt/components/hooks/useSetPinnedMessages.ts +75 -0
  57. package/src/Prebuilt/components/hooks/useUserPreferences.jsx +1 -0
  58. package/src/Prebuilt/layouts/SidePane.tsx +1 -1
  59. package/src/Prebuilt/layouts/VideoStreamingSection.tsx +16 -8
  60. package/src/Prebuilt/plugins/whiteboard/PusherCommunicationProvider.js +1 -1
  61. package/src/Prebuilt/plugins/whiteboard/README.md +1 -1
  62. package/src/Prebuilt/primitives/DialogContent.jsx +1 -1
  63. package/src/Prebuilt/provider/roomLayoutProvider/constants/index.ts +12 -1
  64. package/src/Prebuilt/provider/roomLayoutProvider/hooks/useRoomLayoutScreen.ts +5 -1
  65. package/dist/chunk-WREKGNP6.js.map +0 -7
  66. package/src/Prebuilt/components/hooks/useSetPinnedMessage.js +0 -38
  67. package/src/Prebuilt/services/tokenService.js +0 -49
  68. /package/dist/{HLSView-PWVM4OIP.js.map → HLSView-IENE4HRP.js.map} +0 -0
@@ -1,4 +1,4 @@
1
- import React, { Fragment, useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react';
1
+ import React, { Fragment, useCallback, useEffect, useLayoutEffect, useRef, useState } from 'react';
2
2
  import { useInView } from 'react-intersection-observer';
3
3
  import { useMedia } from 'react-use';
4
4
  import AutoSizer from 'react-virtualized-auto-sizer';
@@ -6,25 +6,41 @@ import { VariableSizeList } from 'react-window';
6
6
  import {
7
7
  selectHMSMessages,
8
8
  selectLocalPeerID,
9
+ selectLocalPeerName,
9
10
  selectLocalPeerRoleName,
10
11
  selectMessagesByPeerID,
11
12
  selectMessagesByRole,
12
13
  selectPeerNameByID,
13
14
  selectPermissions,
15
+ selectSessionStore,
14
16
  useHMSActions,
15
17
  useHMSStore,
16
18
  } from '@100mslive/react-sdk';
17
- import { PinIcon, VerticalMenuIcon } from '@100mslive/react-icons';
19
+ import { CopyIcon, CrossCircleIcon, CrossIcon, EyeCloseIcon, PinIcon, VerticalMenuIcon } from '@100mslive/react-icons';
18
20
  import { Dropdown } from '../../../Dropdown';
19
21
  import { IconButton } from '../../../IconButton';
20
22
  import { Box, Flex } from '../../../Layout';
23
+ import { Sheet } from '../../../Sheet';
21
24
  import { Text } from '../../../Text';
22
25
  import { config as cssConfig, styled } from '../../../Theme';
23
26
  import { Tooltip } from '../../../Tooltip';
24
27
  import emptyChat from '../../images/empty-chat.svg';
28
+ import { ToastManager } from '../Toast/ToastManager';
29
+ import { MwebChatOption } from './MwebChatOption';
25
30
  import { useRoomLayoutConferencingScreen } from '../../provider/roomLayoutProvider/hooks/useRoomLayoutScreen';
26
- import { useSetPinnedMessage } from '../hooks/useSetPinnedMessage';
31
+ import { useChatBlacklist } from '../hooks/useChatBlacklist';
32
+ import { useSetPinnedMessages } from '../hooks/useSetPinnedMessages';
27
33
  import { useUnreadCount } from './useUnreadCount';
34
+ import { SESSION_STORE_KEY } from '../../common/constants';
35
+
36
+ const iconStyle = { height: '1.125rem', width: '1.125rem' };
37
+ const tooltipBoxCSS = {
38
+ fontSize: '$xs',
39
+ backgroundColor: '$surface_default',
40
+ p: '$1 $5',
41
+ fontWeight: '$regular',
42
+ borderRadius: '$3',
43
+ };
28
44
 
29
45
  const formatTime = date => {
30
46
  if (!(date instanceof Date)) {
@@ -127,56 +143,188 @@ const getMessageType = ({ roles, receiver }) => {
127
143
  }
128
144
  return receiver ? 'private' : '';
129
145
  };
130
- const ChatActions = ({ onPin, showPinAction }) => {
146
+ const ChatActions = ({ onPin, showPinAction, message, sentByLocalPeer, isMobile, openSheet, setOpenSheet }) => {
147
+ const { elements } = useRoomLayoutConferencingScreen();
148
+ const { can_hide_message, can_block_user } = elements?.chat?.real_time_controls || {
149
+ can_hide_message: false,
150
+ can_block_user: false,
151
+ };
131
152
  const [open, setOpen] = useState(false);
132
- if (!showPinAction) {
133
- return null;
153
+ const blacklistedPeerIDs = useHMSStore(selectSessionStore(SESSION_STORE_KEY.CHAT_PEER_BLACKLIST)) || [];
154
+ const { blacklistItem: blacklistPeer } = useChatBlacklist(SESSION_STORE_KEY.CHAT_PEER_BLACKLIST);
155
+
156
+ const blacklistedMessageIDs = useHMSStore(selectSessionStore(SESSION_STORE_KEY.CHAT_MESSAGE_BLACKLIST)) || [];
157
+ const { blacklistItem: blacklistMessage } = useChatBlacklist(SESSION_STORE_KEY.CHAT_MESSAGE_BLACKLIST);
158
+
159
+ const { unpinBlacklistedMessages } = useSetPinnedMessages();
160
+
161
+ const pinnedMessages = useHMSStore(selectSessionStore(SESSION_STORE_KEY.PINNED_MESSAGES)) || [];
162
+
163
+ const updatePinnedMessages = useCallback(
164
+ ({ messageID = '', peerID = '' }) => {
165
+ const blacklistedMessageIDSet = new Set([...blacklistedMessageIDs, messageID]);
166
+ const blacklistedPeerIDSet = new Set([...blacklistedPeerIDs, peerID]);
167
+ unpinBlacklistedMessages(pinnedMessages, blacklistedPeerIDSet, blacklistedMessageIDSet);
168
+ },
169
+ [pinnedMessages, blacklistedMessageIDs, blacklistedPeerIDs],
170
+ );
171
+
172
+ const copyMessageContent = useCallback(() => {
173
+ try {
174
+ navigator?.clipboard.writeText(message.message);
175
+ ToastManager.addToast({
176
+ title: 'Message copied successfully',
177
+ });
178
+ } catch (e) {
179
+ console.log(e);
180
+ ToastManager.addToast({
181
+ title: 'Could not copy message',
182
+ });
183
+ }
184
+ }, [message]);
185
+
186
+ const options = {
187
+ pin: {
188
+ text: 'Pin message',
189
+ tooltipText: 'Pin',
190
+ icon: <PinIcon style={iconStyle} />,
191
+ onClick: onPin,
192
+ show: showPinAction,
193
+ },
194
+ copy: {
195
+ text: 'Copy text',
196
+ tooltipText: 'Copy',
197
+ icon: <CopyIcon style={iconStyle} />,
198
+ onClick: copyMessageContent,
199
+ show: true,
200
+ },
201
+ hide: {
202
+ text: 'Hide for everyone',
203
+ icon: <EyeCloseIcon style={iconStyle} />,
204
+ onClick: async () => {
205
+ blacklistMessage(blacklistedMessageIDs, message.id);
206
+ updatePinnedMessages({ messageID: message.id });
207
+ },
208
+ show: can_hide_message,
209
+ },
210
+ block: {
211
+ text: 'Block from chat',
212
+ icon: <CrossCircleIcon style={iconStyle} />,
213
+ onClick: async () => {
214
+ blacklistPeer(blacklistedPeerIDs, message?.senderUserId);
215
+ updatePinnedMessages({ peerID: message?.senderUserId });
216
+ },
217
+ color: '$alert_error_default',
218
+ show: can_block_user && !sentByLocalPeer,
219
+ },
220
+ };
221
+
222
+ if (isMobile) {
223
+ return (
224
+ <Sheet.Root open={openSheet} onOpenChange={setOpenSheet}>
225
+ <Sheet.Content css={{ bg: '$surface_default', pb: '$14' }} onClick={() => setOpenSheet(false)}>
226
+ <Sheet.Title
227
+ css={{
228
+ display: 'flex',
229
+ color: '$on_surface_high',
230
+ w: '100%',
231
+ justifyContent: 'space-between',
232
+ mt: '$8',
233
+ fontSize: '$md',
234
+ px: '$10',
235
+ pb: '$8',
236
+ borderBottom: '1px solid $border_bright',
237
+ alignItems: 'center',
238
+ }}
239
+ >
240
+ Message options
241
+ <Sheet.Close css={{ color: '$on_surface_high' }} onClick={() => setOpenSheet(false)}>
242
+ <CrossIcon />
243
+ </Sheet.Close>
244
+ </Sheet.Title>
245
+
246
+ {Object.keys(options).map(optionKey => {
247
+ const option = options[optionKey];
248
+ return option.show ? (
249
+ <MwebChatOption
250
+ key={optionKey}
251
+ text={option.text}
252
+ icon={option.icon}
253
+ onClick={option.onClick}
254
+ color={option?.color}
255
+ />
256
+ ) : null;
257
+ })}
258
+ </Sheet.Content>
259
+ </Sheet.Root>
260
+ );
134
261
  }
135
262
 
136
263
  return (
137
- <Dropdown.Root open={open} onOpenChange={setOpen}>
138
- <Dropdown.Trigger className="chat_actions" css={{ opacity: open ? 1 : 0, '@md': { opacity: 1 } }} asChild>
139
- <IconButton>
140
- <Tooltip title="More options">
141
- <VerticalMenuIcon />
264
+ <Dropdown.Root open={open} onOpenChange={setOpen} css={{ '@md': { display: 'none' } }}>
265
+ <Flex
266
+ className="chat_actions"
267
+ css={{
268
+ background: '$surface_bright',
269
+ borderRadius: '$1',
270
+ p: '$2',
271
+ opacity: open ? 1 : 0,
272
+ '@md': { opacity: 1 },
273
+ }}
274
+ >
275
+ {options.pin.show ? (
276
+ <Tooltip boxCss={tooltipBoxCSS} title={options.pin.tooltipText}>
277
+ <IconButton data-testid="pin_message_btn" onClick={options.pin.onClick}>
278
+ {options.pin.icon}
279
+ </IconButton>
280
+ </Tooltip>
281
+ ) : null}
282
+
283
+ {options.copy.show ? (
284
+ <Tooltip boxCss={tooltipBoxCSS} title={options.copy.tooltipText}>
285
+ <IconButton onClick={options.copy.onClick} data-testid="copy_message_btn">
286
+ <CopyIcon style={iconStyle} />
287
+ </IconButton>
288
+ </Tooltip>
289
+ ) : null}
290
+
291
+ {options.block.show || options.hide.show ? (
292
+ <Tooltip boxCss={tooltipBoxCSS} title="More actions">
293
+ <Dropdown.Trigger asChild>
294
+ <IconButton>
295
+ <VerticalMenuIcon style={iconStyle} />
296
+ </IconButton>
297
+ </Dropdown.Trigger>
142
298
  </Tooltip>
143
- </IconButton>
144
- </Dropdown.Trigger>
299
+ ) : null}
300
+ </Flex>
145
301
  <Dropdown.Portal>
146
302
  <Dropdown.Content
147
303
  sideOffset={5}
148
304
  align="end"
149
305
  css={{ width: '$48', backgroundColor: '$surface_bright', py: '$0', border: '1px solid $border_bright' }}
150
306
  >
151
- <Dropdown.Item data-testid="pin_message_btn" onClick={onPin}>
152
- <PinIcon />
153
- <Text variant="sm" css={{ ml: '$4' }}>
154
- Pin Message
155
- </Text>
156
- </Dropdown.Item>
157
- {/* {isMobile ? (
307
+ {options.hide.show ? (
308
+ <Dropdown.Item data-testid="hide_message_btn" onClick={options.hide.onClick}>
309
+ {options.hide.icon}
310
+ <Text variant="sm" css={{ ml: '$4', fontWeight: '$semiBold' }}>
311
+ {options.hide.text}
312
+ </Text>
313
+ </Dropdown.Item>
314
+ ) : null}
315
+
316
+ {options.block.show ? (
158
317
  <Dropdown.Item
159
- data-testid="copy_message_btn"
160
- onClick={() => {
161
- try {
162
- navigator?.clipboard.writeText(messageContent);
163
- ToastManager.addToast({
164
- title: 'Message copied successfully',
165
- });
166
- } catch (e) {
167
- console.log(e);
168
- ToastManager.addToast({
169
- title: 'Could not copy message',
170
- });
171
- }
172
- }}
318
+ data-testid="block_peer_btn"
319
+ onClick={options.block.onClick}
320
+ css={{ color: options.block.color }}
173
321
  >
174
- <CopyIcon />
175
- <Text variant="sm" css={{ ml: '$4' }}>
176
- Copy Message
322
+ {options.block.icon}
323
+ <Text variant="sm" css={{ ml: '$4', color: 'inherit', fontWeight: '$semiBold' }}>
324
+ {options.block.text}
177
325
  </Text>
178
326
  </Dropdown.Item>
179
- ) : null} */}
327
+ ) : null}
180
328
  </Dropdown.Content>
181
329
  </Dropdown.Portal>
182
330
  </Dropdown.Root>
@@ -212,9 +360,9 @@ const ChatMessage = React.memo(
212
360
  roles: message.recipientRoles,
213
361
  receiver: message.recipientPeer,
214
362
  });
363
+ const [openSheet, setOpenSheet] = useState(false);
215
364
  // show pin action only if peer has remove others permission and the message is of broadcast type
216
365
  const showPinAction = permissions.removeOthers && !messageType && elements?.chat?.allow_pinning_messages;
217
-
218
366
  useEffect(() => {
219
367
  if (message.id && !message.read && inView) {
220
368
  hmsActions.setMessageRead(true, message.id);
@@ -231,7 +379,12 @@ const ChatMessage = React.memo(
231
379
  <Box
232
380
  ref={ref}
233
381
  as="div"
234
- css={{ mb: '$10', pr: '$10', mt: '$8', '&:hover .chat_actions': { opacity: 1 } }}
382
+ css={{
383
+ mb: '$5',
384
+ pr: '$10',
385
+ mt: '$4',
386
+ '&:hover .chat_actions': { opacity: 1 },
387
+ }}
235
388
  style={style}
236
389
  >
237
390
  <Flex
@@ -241,13 +394,23 @@ const ChatMessage = React.memo(
241
394
  flexWrap: 'wrap',
242
395
  // Theme independent color, token should not be used for transparent chat
243
396
  bg: messageType ? (isOverlay ? 'rgba(0, 0, 0, 0.64)' : '$surface_default') : undefined,
244
- r: messageType ? '$1' : undefined,
245
- px: messageType ? '$4' : '$2',
246
- py: messageType ? '$4' : 0,
397
+ r: '$1',
398
+ p: '$1 $2',
247
399
  userSelect: 'none',
400
+ '@md': {
401
+ cursor: 'pointer',
402
+ },
403
+ '&:hover': {
404
+ background: 'linear-gradient(277deg, $surface_default 0%, $surface_dim 60.87%)',
405
+ },
248
406
  }}
249
407
  key={message.time}
250
408
  data-testid="chat_msg"
409
+ onClick={() => {
410
+ if (isMobile) {
411
+ setOpenSheet(true);
412
+ }
413
+ }}
251
414
  >
252
415
  <Text
253
416
  css={{
@@ -291,7 +454,16 @@ const ChatMessage = React.memo(
291
454
  receiver={message.recipientPeer}
292
455
  roles={message.recipientRoles}
293
456
  />
294
- {!isOverlay ? <ChatActions onPin={onPin} showPinAction={showPinAction} /> : null}
457
+
458
+ <ChatActions
459
+ onPin={onPin}
460
+ showPinAction={showPinAction}
461
+ message={message}
462
+ sentByLocalPeer={message.sender === localPeerId}
463
+ isMobile={isMobile}
464
+ openSheet={openSheet}
465
+ setOpenSheet={setOpenSheet}
466
+ />
295
467
  </Text>
296
468
  <Text
297
469
  variant="sm"
@@ -303,7 +475,10 @@ const ChatMessage = React.memo(
303
475
  userSelect: 'all',
304
476
  color: isOverlay ? '#FFF' : '$on_surface_high',
305
477
  }}
306
- onClick={e => e.stopPropagation()}
478
+ onClick={e => {
479
+ e.stopPropagation();
480
+ setOpenSheet(true);
481
+ }}
307
482
  >
308
483
  <AnnotisedMessage message={message.message} />
309
484
  </Text>
@@ -314,7 +489,9 @@ const ChatMessage = React.memo(
314
489
  );
315
490
  const ChatList = React.forwardRef(
316
491
  ({ width, height, setRowHeight, getRowHeight, messages, unreadCount = 0, scrollToBottom }, listRef) => {
317
- const { setPinnedMessage } = useSetPinnedMessage();
492
+ const { setPinnedMessages } = useSetPinnedMessages();
493
+ const pinnedMessages = useHMSStore(selectSessionStore(SESSION_STORE_KEY.PINNED_MESSAGES)) || [];
494
+ const localPeerName = useHMSStore(selectLocalPeerName);
318
495
  useLayoutEffect(() => {
319
496
  if (listRef.current && listRef.current.scrollToItem) {
320
497
  scrollToBottom(1);
@@ -343,7 +520,7 @@ const ChatList = React.forwardRef(
343
520
  unreadCount={unreadCount}
344
521
  isLast={index >= messages.length - 2}
345
522
  scrollToBottom={scrollToBottom}
346
- onPin={() => setPinnedMessage(messages[index])}
523
+ onPin={() => setPinnedMessages(pinnedMessages, messages[index], localPeerName)}
347
524
  />
348
525
  )}
349
526
  </VariableSizeList>
@@ -397,14 +574,27 @@ const VirtualizedChatMessages = React.forwardRef(({ messages, unreadCount = 0, s
397
574
  );
398
575
  });
399
576
 
400
- export const ChatBody = React.forwardRef(({ role, peerId, scrollToBottom }, listRef) => {
577
+ export const ChatBody = React.forwardRef(({ role, peerId, scrollToBottom, blacklistedPeerIDs }, listRef) => {
401
578
  const storeMessageSelector = role
402
579
  ? selectMessagesByRole(role)
403
580
  : peerId
404
581
  ? selectMessagesByPeerID(peerId)
405
582
  : selectHMSMessages;
406
- let messages = useHMSStore(storeMessageSelector);
407
- messages = useMemo(() => messages?.filter(message => message.type === 'chat') || [], [messages]);
583
+ let messages = useHMSStore(storeMessageSelector) || [];
584
+ const blacklistedMessageIDs = useHMSStore(selectSessionStore(SESSION_STORE_KEY.CHAT_MESSAGE_BLACKLIST)) || [];
585
+ const getFilteredMessages = () => {
586
+ const blacklistedMessageIDSet = new Set(blacklistedMessageIDs);
587
+ const blacklistedPeerIDSet = new Set(blacklistedPeerIDs);
588
+ return (
589
+ messages?.filter(
590
+ message =>
591
+ message.type === 'chat' &&
592
+ !blacklistedMessageIDSet.has(message.id) &&
593
+ !blacklistedPeerIDSet.has(message?.senderUserId),
594
+ ) || []
595
+ );
596
+ };
597
+
408
598
  const isMobile = useMedia(cssConfig.media.md);
409
599
  const { elements } = useRoomLayoutConferencingScreen();
410
600
  const unreadCount = useUnreadCount({ role, peerId });
@@ -440,7 +630,7 @@ export const ChatBody = React.forwardRef(({ role, peerId, scrollToBottom }, list
440
630
  return (
441
631
  <Fragment>
442
632
  <VirtualizedChatMessages
443
- messages={messages}
633
+ messages={getFilteredMessages()}
444
634
  scrollToBottom={scrollToBottom}
445
635
  unreadCount={unreadCount}
446
636
  ref={listRef}
@@ -2,9 +2,10 @@ import React, { ReactNode, useCallback, useEffect, useRef, useState } from 'reac
2
2
  import { useMedia } from 'react-use';
3
3
  import data from '@emoji-mart/data';
4
4
  import Picker from '@emoji-mart/react';
5
- import { useHMSActions } from '@100mslive/react-sdk';
6
- import { EmojiIcon, SendIcon } from '@100mslive/react-icons';
7
- import { Box, config as cssConfig, Flex, IconButton as BaseIconButton, Popover, styled } from '../../..';
5
+ import { selectLocalPeer, useHMSActions, useHMSStore } from '@100mslive/react-sdk';
6
+ import { EmojiIcon, PauseCircleIcon, SendIcon, VerticalMenuIcon } from '@100mslive/react-icons';
7
+ import { Box, config as cssConfig, Flex, IconButton as BaseIconButton, Popover, styled, Text } from '../../..';
8
+ import { IconButton } from '../../../IconButton';
8
9
  // @ts-ignore
9
10
  import { ToastManager } from '../Toast/ToastManager';
10
11
  import { useRoomLayoutConferencingScreen } from '../../provider/roomLayoutProvider/hooks/useRoomLayoutScreen';
@@ -13,6 +14,8 @@ import { useRoomLayoutConferencingScreen } from '../../provider/roomLayoutProvid
13
14
  import { useChatDraftMessage } from '../AppData/useChatState';
14
15
  // @ts-ignore
15
16
  import { useEmojiPickerStyles } from './useEmojiPickerStyles';
17
+ // @ts-ignore
18
+ import { SESSION_STORE_KEY } from '../../common/constants';
16
19
 
17
20
  const TextArea = styled('textarea', {
18
21
  width: '100%',
@@ -82,7 +85,10 @@ export const ChatFooter = ({
82
85
  const [draftMessage, setDraftMessage] = useChatDraftMessage();
83
86
  const isMobile = useMedia(cssConfig.media.md);
84
87
  const { elements } = useRoomLayoutConferencingScreen();
88
+ const message_placeholder = elements?.chat?.message_placeholder || 'Send a message';
89
+ const localPeer = useHMSStore(selectLocalPeer);
85
90
  const isOverlayChat = elements?.chat?.is_overlay;
91
+ const canDisableChat = !!elements?.chat?.real_time_controls?.can_disable_chat;
86
92
 
87
93
  const sendMessage = useCallback(async () => {
88
94
  const message = inputRef?.current?.value;
@@ -122,10 +128,54 @@ export const ChatFooter = ({
122
128
  }, [setDraftMessage]);
123
129
 
124
130
  return (
125
- <>
131
+ <Box>
126
132
  {/* {screenType !== 'hls_live_streaming' ? (
127
133
  <ChatSelectorContainer onSelect={onSelect} role={role} peerId={peerId} selection={selection} />
128
134
  ) : null} */}
135
+ {canDisableChat ? (
136
+ <Flex align="center" justify="end" css={{ w: '100%', mb: '$4' }}>
137
+ <Popover.Root>
138
+ <Popover.Trigger asChild>
139
+ <IconButton css={{ border: '1px solid $border_bright' }}>
140
+ <VerticalMenuIcon height="16" width="16" />
141
+ </IconButton>
142
+ </Popover.Trigger>
143
+ <Popover.Portal>
144
+ <Popover.Content
145
+ align="end"
146
+ side="top"
147
+ onClick={() => {
148
+ const chatState = {
149
+ enabled: false,
150
+ updatedBy: {
151
+ peerId: localPeer?.id,
152
+ userId: localPeer?.customerUserId,
153
+ userName: localPeer?.name,
154
+ },
155
+ updatedAt: Date.now(),
156
+ };
157
+ hmsActions.sessionStore.set(SESSION_STORE_KEY.CHAT_STATE, chatState);
158
+ }}
159
+ css={{
160
+ backgroundColor: '$surface_default',
161
+ display: 'flex',
162
+ alignItems: 'center',
163
+ gap: '$4',
164
+ borderRadius: '$1',
165
+ color: '$on_surface_high',
166
+ cursor: 'pointer',
167
+ '&:hover': { backgroundColor: '$surface_dim' },
168
+ }}
169
+ >
170
+ <PauseCircleIcon />
171
+ <Text variant="sm" css={{ fontWeight: '$semiBold' }}>
172
+ Pause Chat
173
+ </Text>
174
+ </Popover.Content>
175
+ </Popover.Portal>
176
+ </Popover.Root>
177
+ </Flex>
178
+ ) : null}
129
179
  <Flex align="center" css={{ gap: '$4', w: '100%' }}>
130
180
  <Flex
131
181
  align="center"
@@ -153,7 +203,7 @@ export const ChatFooter = ({
153
203
  '& ~ .send-msg': { color: '$on_surface_low' },
154
204
  '&::placeholder': { color: '$on_surface_medium' },
155
205
  }}
156
- placeholder="Send a message...."
206
+ placeholder={message_placeholder}
157
207
  ref={inputRef}
158
208
  required
159
209
  autoFocus={!isMobile}
@@ -195,6 +245,6 @@ export const ChatFooter = ({
195
245
  </BaseIconButton>
196
246
  </Flex>
197
247
  </Flex>
198
- </>
248
+ </Box>
199
249
  );
200
250
  };
@@ -0,0 +1,68 @@
1
+ import React, { useCallback } from 'react';
2
+ import { selectLocalPeer, selectSessionStore, useHMSActions, useHMSStore } from '@100mslive/react-sdk';
3
+ import { Button } from '../../../Button';
4
+ import { Box, Flex } from '../../../Layout';
5
+ import { Text } from '../../../Text';
6
+ import { useRoomLayoutConferencingScreen } from '../../provider/roomLayoutProvider/hooks/useRoomLayoutScreen';
7
+ import { SESSION_STORE_KEY } from '../../common/constants';
8
+
9
+ export const ChatPaused = () => {
10
+ const hmsActions = useHMSActions();
11
+ const { elements } = useRoomLayoutConferencingScreen();
12
+ const { can_disable_chat } = elements?.chat.real_time_controls || false;
13
+ const { enabled: isChatEnabled = true, updatedBy: chatStateUpdatedBy } =
14
+ useHMSStore(selectSessionStore(SESSION_STORE_KEY.CHAT_STATE)) || {};
15
+
16
+ const localPeer = useHMSStore(selectLocalPeer);
17
+
18
+ const unPauseChat = useCallback(
19
+ async () =>
20
+ await hmsActions.sessionStore.set(SESSION_STORE_KEY.CHAT_STATE, {
21
+ enabled: true,
22
+ updatedBy: { userName: localPeer.name, userId: localPeer?.customerUserId, peerId: localPeer.id },
23
+ updatedAt: Date.now(),
24
+ }),
25
+ [hmsActions, localPeer],
26
+ );
27
+
28
+ return isChatEnabled ? null : (
29
+ <Flex
30
+ align="center"
31
+ justify="between"
32
+ css={{ borderRadius: '$1', bg: '$surface_default', p: '$4 $4 $4 $8', w: '100%' }}
33
+ >
34
+ <Box>
35
+ <Text variant="sm" css={{ fontWeight: '$semiBold', color: '$on_surface_high' }}>
36
+ Chat paused
37
+ </Text>
38
+ <Text
39
+ variant="xs"
40
+ css={{ color: '$on_surface_medium', maxWidth: '100%', overflow: 'hidden', textOverflow: 'ellipsis' }}
41
+ >
42
+ Chat has been paused by {chatStateUpdatedBy?.peerId === localPeer.id ? 'you' : chatStateUpdatedBy?.userName}
43
+ </Text>
44
+ </Box>
45
+ {can_disable_chat ? (
46
+ <Button css={{ fontWeight: '$semiBold', fontSize: '$sm', borderRadius: '$2' }} onClick={unPauseChat}>
47
+ Resume
48
+ </Button>
49
+ ) : (
50
+ <></>
51
+ )}
52
+ </Flex>
53
+ );
54
+ };
55
+
56
+ export const ChatBlocked = () => {
57
+ return (
58
+ <Flex
59
+ align="center"
60
+ justify="between"
61
+ css={{ borderRadius: '$1', bg: '$surface_default', p: '$4 $4 $4 $8', w: '100%' }}
62
+ >
63
+ <Text variant="sm" css={{ color: '$on_surface_medium', textAlign: 'center', w: '100%' }}>
64
+ You've been blocked from sending messages
65
+ </Text>
66
+ </Flex>
67
+ );
68
+ };
@@ -0,0 +1,24 @@
1
+ import React from 'react';
2
+ import { Flex } from '../../../Layout';
3
+ import { Text } from '../../../Text';
4
+
5
+ export const MwebChatOption = ({
6
+ icon,
7
+ text,
8
+ onClick,
9
+ color = '$on_surface_high',
10
+ }: {
11
+ icon: any;
12
+ text: string;
13
+ onClick: () => void | Promise<void>;
14
+ color: string;
15
+ }) => {
16
+ return (
17
+ <Flex align="center" css={{ w: '100%', color, cursor: 'pointer', gap: '$4', p: '$8' }} onClick={onClick}>
18
+ {icon}
19
+ <Text variant="sm" css={{ color, fontWeight: '$semiBold' }}>
20
+ {text}
21
+ </Text>
22
+ </Flex>
23
+ );
24
+ };
@@ -0,0 +1,60 @@
1
+ import React from 'react';
2
+ import { ChevronDownIcon, ChevronUpIcon } from '@100mslive/react-icons';
3
+ import { Box, Flex } from '../../../Layout';
4
+
5
+ export const Navigation = ({
6
+ total,
7
+ index,
8
+ showPrevious,
9
+ showNext,
10
+ isMobile,
11
+ }: {
12
+ total: number;
13
+ index: number;
14
+ showPrevious: () => void;
15
+ showNext: () => void;
16
+ isMobile: boolean;
17
+ }) => {
18
+ const sticksCount = Math.min(3, total);
19
+
20
+ if (total < 2) {
21
+ return null;
22
+ }
23
+
24
+ return isMobile ? (
25
+ <Flex direction="column" css={{ gap: '$1' }}>
26
+ {[...Array(sticksCount)].map((_, i) => (
27
+ <Box
28
+ css={{
29
+ borderLeft: '2px solid',
30
+ height: '$4',
31
+ borderColor: i === index ? '$on_surface_high' : '$on_surface_low',
32
+ }}
33
+ />
34
+ ))}
35
+ </Flex>
36
+ ) : (
37
+ <Flex direction="column" css={{ gap: '$4' }}>
38
+ <Flex
39
+ onClick={showPrevious}
40
+ css={
41
+ index === 0
42
+ ? { cursor: 'not-allowed', color: '$on_surface_low' }
43
+ : { cursor: 'pointer', color: '$on_surface_medium' }
44
+ }
45
+ >
46
+ <ChevronUpIcon height={20} width={20} />
47
+ </Flex>
48
+ <Flex
49
+ onClick={showNext}
50
+ css={
51
+ index === total - 1
52
+ ? { cursor: 'not-allowed', color: '$on_surface_low' }
53
+ : { cursor: 'pointer', color: '$on_surface_medium' }
54
+ }
55
+ >
56
+ <ChevronDownIcon height={20} width={20} />
57
+ </Flex>
58
+ </Flex>
59
+ );
60
+ };