@100mslive/roomkit-react 0.1.18-alpha.1 → 0.1.19-alpha.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (44) hide show
  1. package/dist/{HLSView-MR7RYQZB.js → HLSView-GG4WVUQY.js} +2 -2
  2. package/dist/Prebuilt/common/constants.d.ts +2 -6
  3. package/dist/Prebuilt/components/Chat/Chat.d.ts +2 -0
  4. package/dist/Prebuilt/components/Chat/ChatActions.d.ts +12 -0
  5. package/dist/Prebuilt/components/Chat/ChatBody.d.ts +8 -0
  6. package/dist/Prebuilt/components/Chat/ChatFooter.d.ts +2 -2
  7. package/dist/Prebuilt/components/Chat/ChatStates.d.ts +1 -1
  8. package/dist/Prebuilt/components/Chat/MwebChatOption.d.ts +1 -1
  9. package/dist/Prebuilt/components/Chat/PinnedMessage.d.ts +1 -3
  10. package/dist/Prebuilt/components/hooks/useChatBlacklist.d.ts +4 -0
  11. package/dist/Prebuilt/components/hooks/useSetPinnedMessages.d.ts +3 -10
  12. package/dist/Prebuilt/components/hooks/useUnreadPollQuizPresent.d.ts +5 -0
  13. package/dist/{chunk-WFHOR7AP.js → chunk-GXJIUWTP.js} +10083 -4574
  14. package/dist/chunk-GXJIUWTP.js.map +7 -0
  15. package/dist/index.cjs.js +12716 -7192
  16. package/dist/index.cjs.js.map +4 -4
  17. package/dist/index.js +1 -1
  18. package/dist/meta.cjs.json +367 -240
  19. package/dist/meta.esbuild.json +377 -250
  20. package/package.json +6 -6
  21. package/src/Prebuilt/common/constants.ts +4 -4
  22. package/src/Prebuilt/components/Chat/Chat.tsx +108 -0
  23. package/src/Prebuilt/components/Chat/ChatActions.tsx +297 -0
  24. package/src/Prebuilt/components/Chat/ChatBody.tsx +444 -0
  25. package/src/Prebuilt/components/Chat/ChatFooter.tsx +9 -3
  26. package/src/Prebuilt/components/Chat/ChatStates.tsx +5 -0
  27. package/src/Prebuilt/components/Chat/MwebChatOption.tsx +1 -1
  28. package/src/Prebuilt/components/Chat/PinnedMessage.tsx +4 -2
  29. package/src/Prebuilt/components/Footer/PollsToggle.tsx +12 -3
  30. package/src/Prebuilt/components/MoreSettings/SplitComponents/MwebOptions.tsx +5 -1
  31. package/src/Prebuilt/components/Polls/CreateQuestions/QuestionForm.jsx +3 -3
  32. package/src/Prebuilt/components/Polls/Voting/Leaderboard.tsx +9 -1
  33. package/src/Prebuilt/components/Polls/Voting/LeaderboardEntry.tsx +6 -6
  34. package/src/Prebuilt/components/Polls/Voting/QuestionCard.jsx +20 -23
  35. package/src/Prebuilt/components/Polls/common/MultipleChoiceOptions.jsx +1 -1
  36. package/src/Prebuilt/components/SidePaneTabs.tsx +33 -11
  37. package/src/Prebuilt/components/hooks/useChatBlacklist.ts +7 -1
  38. package/src/Prebuilt/components/hooks/useSetPinnedMessages.ts +19 -11
  39. package/src/Prebuilt/components/hooks/useUnreadPollQuizPresent.tsx +20 -0
  40. package/src/Prebuilt/provider/roomLayoutProvider/index.tsx +11 -2
  41. package/dist/chunk-WFHOR7AP.js.map +0 -7
  42. package/src/Prebuilt/components/Chat/Chat.jsx +0 -124
  43. package/src/Prebuilt/components/Chat/ChatBody.jsx +0 -726
  44. /package/dist/{HLSView-MR7RYQZB.js.map → HLSView-GG4WVUQY.js.map} +0 -0
@@ -0,0 +1,444 @@
1
+ import React, { Fragment, useCallback, useEffect, useMemo, useRef, useState } from 'react';
2
+ import { useMedia } from 'react-use';
3
+ import AutoSizer from 'react-virtualized-auto-sizer';
4
+ import { VariableSizeList } from 'react-window';
5
+ import {
6
+ HMSMessage,
7
+ HMSPeerID,
8
+ HMSRoleName,
9
+ selectHMSMessages,
10
+ selectHMSMessagesCount,
11
+ selectLocalPeerID,
12
+ selectLocalPeerRoleName,
13
+ selectPeerNameByID,
14
+ selectSessionStore,
15
+ useHMSActions,
16
+ useHMSStore,
17
+ useHMSVanillaStore,
18
+ } from '@100mslive/react-sdk';
19
+ import { Box, Flex } from '../../../Layout';
20
+ import { Text } from '../../../Text';
21
+ import { config as cssConfig, styled } from '../../../Theme';
22
+ import { Tooltip } from '../../../Tooltip';
23
+ // @ts-ignore
24
+ import emptyChat from '../../images/empty-chat.svg';
25
+ import { ChatActions } from './ChatActions';
26
+ import { useRoomLayoutConferencingScreen } from '../../provider/roomLayoutProvider/hooks/useRoomLayoutScreen';
27
+ // @ts-ignore: No implicit Any
28
+ import { useSetSubscribedChatSelector } from '../AppData/useUISettings';
29
+ import { CHAT_SELECTOR, SESSION_STORE_KEY } from '../../common/constants';
30
+
31
+ const formatTime = (date: Date) => {
32
+ if (!(date instanceof Date)) {
33
+ return '';
34
+ }
35
+ const hours = date.getHours();
36
+ const minutes = date.getMinutes();
37
+ const suffix = hours > 11 ? 'PM' : 'AM';
38
+ return `${hours < 10 ? '0' : ''}${hours}:${minutes < 10 ? '0' : ''}${minutes} ${suffix}`;
39
+ };
40
+
41
+ const rowHeights: Record<number, number> = {};
42
+ function getRowHeight(index: number) {
43
+ // 72 will be default row height for any message length
44
+ // 16 will add margin value as clientHeight don't include margin
45
+ return rowHeights[index] + 16 || 72;
46
+ }
47
+
48
+ const setRowHeight = (index: number, size: number) => {
49
+ Object.assign(rowHeights, { [index]: size });
50
+ };
51
+
52
+ const MessageTypeContainer = ({ left, right }: { left?: string; right?: string }) => {
53
+ return (
54
+ <Flex
55
+ align="center"
56
+ css={{
57
+ ml: '$2',
58
+ mr: '$4',
59
+ gap: '$space$2',
60
+ }}
61
+ >
62
+ {left && (
63
+ <SenderName
64
+ variant="xs"
65
+ as="span"
66
+ css={{ color: '$on_surface_medium', textTransform: 'capitalize', fontWeight: '$regular' }}
67
+ >
68
+ {left}
69
+ </SenderName>
70
+ )}
71
+ {right && (
72
+ <SenderName
73
+ as="span"
74
+ variant="overline"
75
+ css={{
76
+ color: '$on_surface_medium',
77
+ fontWeight: '$regular',
78
+ }}
79
+ >
80
+ {right}
81
+ </SenderName>
82
+ )}
83
+ </Flex>
84
+ );
85
+ };
86
+
87
+ const MessageType = ({
88
+ roles,
89
+ hasCurrentUserSent,
90
+ receiver,
91
+ }: {
92
+ roles?: HMSRoleName[];
93
+ hasCurrentUserSent: boolean;
94
+ receiver?: HMSPeerID;
95
+ }) => {
96
+ const peerName = useHMSStore(selectPeerNameByID(receiver));
97
+ const localPeerRoleName = useHMSStore(selectLocalPeerRoleName);
98
+ if (receiver) {
99
+ return (
100
+ <MessageTypeContainer left={hasCurrentUserSent ? `${peerName ? `to ${peerName}` : ''}` : 'to You'} right="(DM)" />
101
+ );
102
+ }
103
+
104
+ if (roles && roles.length) {
105
+ return <MessageTypeContainer left={`to ${hasCurrentUserSent ? roles[0] : localPeerRoleName}`} right="(Group)" />;
106
+ }
107
+ return null;
108
+ };
109
+
110
+ const URL_REGEX =
111
+ /^https?:\/\/(www\.)?[-a-zA-Z0-9@:%._+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_+.~#?&//=]*)/;
112
+
113
+ const Link = styled('a', {
114
+ color: '$primary_default',
115
+ wordBreak: 'break-word',
116
+ '&:hover': {
117
+ textDecoration: 'underline',
118
+ },
119
+ });
120
+
121
+ export const AnnotisedMessage = ({ message }: { message: string }) => {
122
+ if (!message) {
123
+ return <Fragment />;
124
+ }
125
+
126
+ return (
127
+ <Fragment>
128
+ {message
129
+ .trim()
130
+ .split(/(\s)/)
131
+ .map(part =>
132
+ URL_REGEX.test(part) ? (
133
+ <Link href={part} key={part} target="_blank" rel="noopener noreferrer">
134
+ {part}
135
+ </Link>
136
+ ) : (
137
+ part
138
+ ),
139
+ )}
140
+ </Fragment>
141
+ );
142
+ };
143
+
144
+ const getMessageType = ({ roles, receiver }: { roles?: HMSRoleName[]; receiver?: HMSPeerID }) => {
145
+ if (roles && roles.length > 0) {
146
+ return 'role';
147
+ }
148
+ return receiver ? 'private' : '';
149
+ };
150
+ const SenderName = styled(Text, {
151
+ overflow: 'hidden',
152
+ textOverflow: 'ellipsis',
153
+ whiteSpace: 'nowrap',
154
+ maxWidth: '14ch',
155
+ minWidth: 0,
156
+ color: '$on_surface_high',
157
+ fontWeight: '$semiBold',
158
+ });
159
+
160
+ const ChatMessage = React.memo(
161
+ ({ index, style = {}, message }: { message: HMSMessage; index: number; style: React.CSSProperties }) => {
162
+ const { elements } = useRoomLayoutConferencingScreen();
163
+ const rowRef = useRef<HTMLDivElement | null>(null);
164
+ const isMobile = useMedia(cssConfig.media.md);
165
+ const isPrivateChatEnabled = !!elements?.chat?.private_chat_enabled;
166
+ const roleWhiteList = elements?.chat?.roles_whitelist || [];
167
+ const isOverlay = elements?.chat?.is_overlay && isMobile;
168
+ const localPeerId = useHMSStore(selectLocalPeerID);
169
+ const [selectedRole, setRoleSelector] = useSetSubscribedChatSelector(CHAT_SELECTOR.ROLE);
170
+ const [selectedPeer, setPeerSelector] = useSetSubscribedChatSelector(CHAT_SELECTOR.PEER);
171
+ const messageType = getMessageType({
172
+ roles: message.recipientRoles,
173
+ receiver: message.recipientPeer,
174
+ });
175
+ const [openSheet, setOpenSheet] = useState(false);
176
+ const showPinAction = !!elements?.chat?.allow_pinning_messages;
177
+ let showReply = false;
178
+ if (message.recipientRoles && roleWhiteList.includes(message.recipientRoles[0])) {
179
+ showReply = true;
180
+ } else if (message.sender !== selectedPeer.id && message.sender !== localPeerId && isPrivateChatEnabled) {
181
+ showReply = true;
182
+ }
183
+
184
+ useEffect(() => {
185
+ if (rowRef.current) {
186
+ setRowHeight(index, rowRef.current.clientHeight);
187
+ }
188
+ }, [index]);
189
+
190
+ return (
191
+ <Box
192
+ css={{
193
+ mb: '$5',
194
+ pr: '$10',
195
+ mt: '$4',
196
+ '&:not(:hover} .chat_actions': { display: 'none' },
197
+ '&:hover .chat_actions': { display: 'flex', opacity: 1 },
198
+ }}
199
+ style={style}
200
+ >
201
+ <Flex
202
+ ref={rowRef}
203
+ align="center"
204
+ css={{
205
+ flexWrap: 'wrap',
206
+ position: 'relative',
207
+ // Theme independent color, token should not be used for transparent chat
208
+ bg:
209
+ messageType && !(selectedPeer.id || selectedRole)
210
+ ? isOverlay
211
+ ? 'rgba(0, 0, 0, 0.64)'
212
+ : '$surface_default'
213
+ : undefined,
214
+ r: '$1',
215
+ p: '$4',
216
+ userSelect: 'none',
217
+ '@md': {
218
+ cursor: 'pointer',
219
+ },
220
+ '&:hover': {
221
+ background: 'linear-gradient(277deg, $surface_default 0%, $surface_dim 60.87%)',
222
+ },
223
+ }}
224
+ data-testid="chat_msg"
225
+ onClick={() => {
226
+ if (isMobile) {
227
+ setOpenSheet(true);
228
+ }
229
+ }}
230
+ >
231
+ <Text
232
+ css={{
233
+ color: isOverlay ? '#FFF' : '$on_surface_high',
234
+ fontWeight: '$semiBold',
235
+ display: 'flex',
236
+ alignItems: 'center',
237
+ alignSelf: 'stretch',
238
+ width: '100%',
239
+ }}
240
+ as="div"
241
+ >
242
+ <Flex align="baseline">
243
+ {message.senderName === 'You' || !message.senderName ? (
244
+ <SenderName
245
+ as="span"
246
+ variant="sub2"
247
+ css={{ color: isOverlay ? '#FFF' : '$on_surface_high', fontWeight: '$semiBold' }}
248
+ >
249
+ {message.senderName || 'Anonymous'}
250
+ </SenderName>
251
+ ) : (
252
+ <Tooltip title={message.senderName} side="top" align="start">
253
+ <SenderName
254
+ as="span"
255
+ variant="sub2"
256
+ css={{ color: isOverlay ? '#FFF' : '$on_surface_high', fontWeight: '$semiBold' }}
257
+ >
258
+ {message.senderName}
259
+ </SenderName>
260
+ </Tooltip>
261
+ )}
262
+ <MessageType
263
+ hasCurrentUserSent={message.sender === localPeerId}
264
+ receiver={message.recipientPeer}
265
+ roles={message.recipientRoles}
266
+ />
267
+ </Flex>
268
+
269
+ {!isOverlay ? (
270
+ <Text
271
+ as="span"
272
+ variant="caption"
273
+ css={{
274
+ color: '$on_surface_medium',
275
+ flexShrink: 0,
276
+ position: 'absolute',
277
+ right: 0,
278
+ zIndex: 1,
279
+ mr: '$4',
280
+ p: '$2',
281
+ }}
282
+ >
283
+ {formatTime(message.time)}
284
+ </Text>
285
+ ) : null}
286
+ <ChatActions
287
+ showPinAction={showPinAction}
288
+ message={message}
289
+ sentByLocalPeer={message.sender === localPeerId}
290
+ onReply={() => {
291
+ if (message.recipientRoles?.length) {
292
+ setRoleSelector(message.recipientRoles[0]);
293
+ setPeerSelector({});
294
+ } else {
295
+ setRoleSelector('');
296
+ setPeerSelector({ id: message.sender, name: message.senderName });
297
+ }
298
+ }}
299
+ showReply={showReply}
300
+ isMobile={isMobile}
301
+ openSheet={openSheet}
302
+ setOpenSheet={setOpenSheet}
303
+ />
304
+ </Text>
305
+ <Text
306
+ variant="sm"
307
+ css={{
308
+ w: '100%',
309
+ mt: '$2',
310
+ wordBreak: 'break-word',
311
+ whiteSpace: 'pre-wrap',
312
+ userSelect: 'all',
313
+ color: isOverlay ? '#FFF' : '$on_surface_high',
314
+ }}
315
+ onClick={e => {
316
+ e.stopPropagation();
317
+ setOpenSheet(true);
318
+ }}
319
+ >
320
+ <AnnotisedMessage message={message.message} />
321
+ </Text>
322
+ </Flex>
323
+ </Box>
324
+ );
325
+ },
326
+ );
327
+
328
+ const MessageWrapper = React.memo(
329
+ ({ index, style, data }: { index: number; style: React.CSSProperties; data: HMSMessage[] }) => {
330
+ return <ChatMessage style={style} index={index} key={data[index].id} message={data[index]} />;
331
+ },
332
+ );
333
+
334
+ const VirtualizedChatMessages = React.forwardRef<
335
+ VariableSizeList,
336
+ { messages: HMSMessage[]; scrollToBottom: (count: number) => void }
337
+ >(({ messages, scrollToBottom }, listRef) => {
338
+ const hmsActions = useHMSActions();
339
+ const itemKey = useCallback((index: number, data: HMSMessage[]) => {
340
+ return data[index].id;
341
+ }, []);
342
+ useEffect(() => {
343
+ requestAnimationFrame(() => scrollToBottom(1));
344
+ }, [scrollToBottom]);
345
+ return (
346
+ <Box
347
+ css={{
348
+ mr: '-$10',
349
+ h: '100%',
350
+ }}
351
+ >
352
+ <AutoSizer
353
+ style={{
354
+ width: '90%',
355
+ }}
356
+ >
357
+ {({ height, width }: { height: number; width: number }) => (
358
+ <VariableSizeList
359
+ ref={listRef}
360
+ itemCount={messages.length}
361
+ itemSize={getRowHeight}
362
+ itemData={messages}
363
+ width={width}
364
+ height={height}
365
+ style={{
366
+ overflowX: 'hidden',
367
+ }}
368
+ itemKey={itemKey}
369
+ onItemsRendered={({ visibleStartIndex, visibleStopIndex }) => {
370
+ for (let i = visibleStartIndex; i <= visibleStopIndex; i++) {
371
+ if (!messages[i].read) {
372
+ hmsActions.setMessageRead(true, messages[i].id);
373
+ }
374
+ }
375
+ }}
376
+ >
377
+ {MessageWrapper}
378
+ </VariableSizeList>
379
+ )}
380
+ </AutoSizer>
381
+ </Box>
382
+ );
383
+ });
384
+
385
+ export const ChatBody = React.forwardRef<VariableSizeList, { scrollToBottom: (count: number) => void }>(
386
+ ({ scrollToBottom }: { scrollToBottom: (count: number) => void }, listRef) => {
387
+ const messages = useHMSStore(selectHMSMessages);
388
+ const blacklistedMessageIDs = useHMSStore(selectSessionStore(SESSION_STORE_KEY.CHAT_MESSAGE_BLACKLIST));
389
+ const filteredMessages = useMemo(() => {
390
+ const blacklistedMessageIDSet = new Set(blacklistedMessageIDs || []);
391
+ return messages?.filter(message => message.type === 'chat' && !blacklistedMessageIDSet.has(message.id)) || [];
392
+ }, [blacklistedMessageIDs, messages]);
393
+
394
+ const isMobile = useMedia(cssConfig.media.md);
395
+ const { elements } = useRoomLayoutConferencingScreen();
396
+ const vanillaStore = useHMSVanillaStore();
397
+
398
+ useEffect(() => {
399
+ const unsubscribe = vanillaStore.subscribe(() => {
400
+ // @ts-ignore
401
+ if (!listRef?.current) {
402
+ return;
403
+ }
404
+ // @ts-ignore
405
+ const outerElement = listRef.current._outerRef;
406
+ // @ts-ignore
407
+ if (outerElement.scrollHeight - (listRef.current.state.scrollOffset + outerElement.offsetHeight) <= 10) {
408
+ scrollToBottom(1);
409
+ }
410
+ }, selectHMSMessagesCount);
411
+ return unsubscribe;
412
+ }, [vanillaStore, listRef, scrollToBottom]);
413
+
414
+ if (filteredMessages.length === 0 && !(isMobile && elements?.chat?.is_overlay)) {
415
+ return (
416
+ <Flex
417
+ css={{
418
+ width: '100%',
419
+ flex: '1 1 0',
420
+ textAlign: 'center',
421
+ px: '$4',
422
+ }}
423
+ align="center"
424
+ justify="center"
425
+ >
426
+ <Box>
427
+ <img src={emptyChat} alt="Empty Chat" height={132} width={185} style={{ margin: '0 auto' }} />
428
+ <Text variant="h5" css={{ mt: '$8', c: '$on_surface_high' }}>
429
+ Start a conversation
430
+ </Text>
431
+ <Text
432
+ variant="sm"
433
+ css={{ mt: '$4', maxWidth: '80%', textAlign: 'center', mx: 'auto', c: '$on_surface_medium' }}
434
+ >
435
+ There are no messages here yet. Start a conversation by sending a message.
436
+ </Text>
437
+ </Box>
438
+ </Flex>
439
+ );
440
+ }
441
+
442
+ return <VirtualizedChatMessages messages={filteredMessages} ref={listRef} scrollToBottom={scrollToBottom} />;
443
+ },
444
+ );
@@ -10,11 +10,11 @@ import { IconButton } from '../../../IconButton';
10
10
  import { ToastManager } from '../Toast/ToastManager';
11
11
  import { ChatSelectorContainer } from './ChatSelectorContainer';
12
12
  import { useRoomLayoutConferencingScreen } from '../../provider/roomLayoutProvider/hooks/useRoomLayoutScreen';
13
- // import { ChatSelectorContainer } from './ChatSelectorContainer';
14
13
  // @ts-ignore
15
14
  import { useChatDraftMessage } from '../AppData/useChatState';
16
15
  // @ts-ignore
17
16
  import { useSetSubscribedChatSelector, useSubscribeChatSelector } from '../AppData/useUISettings';
17
+ import { useIsPeerBlacklisted } from '../hooks/useChatBlacklist';
18
18
  // @ts-ignore
19
19
  import { useEmojiPickerStyles } from './useEmojiPickerStyles';
20
20
  import { useDefaultChatSelection } from '../../common/hooks';
@@ -72,7 +72,7 @@ function EmojiPicker({ onSelect }: { onSelect: (emoji: any) => void }) {
72
72
  );
73
73
  }
74
74
 
75
- export const ChatFooter = ({ onSend, children }: { onSend: () => void; children: ReactNode }) => {
75
+ export const ChatFooter = ({ onSend, children }: { onSend: (count: number) => void; children: ReactNode }) => {
76
76
  const hmsActions = useHMSActions();
77
77
  const inputRef = useRef<HTMLTextAreaElement>(null);
78
78
  const [draftMessage, setDraftMessage] = useChatDraftMessage();
@@ -86,6 +86,8 @@ export const ChatFooter = ({ onSend, children }: { onSend: () => void; children:
86
86
  const [selectedRole, setRoleSelector] = useSetSubscribedChatSelector(CHAT_SELECTOR.ROLE);
87
87
  const defaultSelection = useDefaultChatSelection();
88
88
  const selection = selectedPeer.name || selectedRole || defaultSelection;
89
+ const isLocalPeerBlacklisted = useIsPeerBlacklisted({ local: true });
90
+
89
91
  useEffect(() => {
90
92
  if (!selectedPeer.id && !selectedRole && !['Everyone', ''].includes(defaultSelection)) {
91
93
  setRoleSelector(defaultSelection);
@@ -108,7 +110,7 @@ export const ChatFooter = ({ onSend, children }: { onSend: () => void; children:
108
110
  }
109
111
  inputRef.current.value = '';
110
112
  setTimeout(() => {
111
- onSend();
113
+ onSend(1);
112
114
  }, 0);
113
115
  } catch (error) {
114
116
  const err = error as HMSException;
@@ -132,6 +134,10 @@ export const ChatFooter = ({ onSend, children }: { onSend: () => void; children:
132
134
  };
133
135
  }, [setDraftMessage]);
134
136
 
137
+ if (isLocalPeerBlacklisted) {
138
+ return null;
139
+ }
140
+
135
141
  return (
136
142
  <Box>
137
143
  <Flex>
@@ -4,6 +4,7 @@ import { Button } from '../../../Button';
4
4
  import { Box, Flex } from '../../../Layout';
5
5
  import { Text } from '../../../Text';
6
6
  import { useRoomLayoutConferencingScreen } from '../../provider/roomLayoutProvider/hooks/useRoomLayoutScreen';
7
+ import { useIsPeerBlacklisted } from '../hooks/useChatBlacklist';
7
8
  import { SESSION_STORE_KEY } from '../../common/constants';
8
9
 
9
10
  export const ChatPaused = () => {
@@ -54,6 +55,10 @@ export const ChatPaused = () => {
54
55
  };
55
56
 
56
57
  export const ChatBlocked = () => {
58
+ const isLocalPeerBlacklisted = useIsPeerBlacklisted({ local: true });
59
+ if (!isLocalPeerBlacklisted) {
60
+ return null;
61
+ }
57
62
  return (
58
63
  <Flex
59
64
  align="center"
@@ -11,7 +11,7 @@ export const MwebChatOption = ({
11
11
  icon: any;
12
12
  text: string;
13
13
  onClick: () => void | Promise<void>;
14
- color: string;
14
+ color?: string;
15
15
  }) => {
16
16
  return (
17
17
  <Flex align="center" css={{ w: '100%', color, cursor: 'pointer', gap: '$4', p: '$8' }} onClick={onClick}>
@@ -11,13 +11,15 @@ import { ArrowNavigation } from './ArrowNavigation';
11
11
  import { AnnotisedMessage } from './ChatBody';
12
12
  import { StickIndicator } from './StickIndicator';
13
13
  import { useRoomLayoutConferencingScreen } from '../../provider/roomLayoutProvider/hooks/useRoomLayoutScreen';
14
+ import { useSetPinnedMessages } from '../hooks/useSetPinnedMessages';
14
15
  import { SESSION_STORE_KEY } from '../../common/constants';
15
16
 
16
17
  const PINNED_MESSAGE_LENGTH = 75;
17
18
 
18
- export const PinnedMessage = ({ clearPinnedMessage }: { clearPinnedMessage: (index: number) => void }) => {
19
+ export const PinnedMessage = () => {
19
20
  const pinnedMessages = useHMSStore(selectSessionStore(SESSION_STORE_KEY.PINNED_MESSAGES));
20
21
  const [pinnedMessageIndex, setPinnedMessageIndex] = useState(0);
22
+ const { removePinnedMessage } = useSetPinnedMessages();
21
23
  const isMobile = useMedia(cssConfig.media.md);
22
24
 
23
25
  const { elements } = useRoomLayoutConferencingScreen();
@@ -117,7 +119,7 @@ export const PinnedMessage = ({ clearPinnedMessage }: { clearPinnedMessage: (ind
117
119
  {canUnpinMessage ? (
118
120
  <Flex
119
121
  onClick={() => {
120
- clearPinnedMessage(pinnedMessageIndex);
122
+ removePinnedMessage(pinnedMessageIndex);
121
123
  setPinnedMessageIndex(Math.max(0, pinnedMessageIndex - 1));
122
124
  }}
123
125
  css={{
@@ -1,21 +1,30 @@
1
1
  import React from 'react';
2
- import { QuizIcon } from '@100mslive/react-icons';
2
+ import { QuizActiveIcon, QuizIcon } from '@100mslive/react-icons';
3
3
  import { Tooltip } from '../../..';
4
4
  // @ts-ignore: No implicit Any
5
5
  import IconButton from '../../IconButton';
6
6
  // @ts-ignore: No implicit Any
7
7
  import { useIsSidepaneTypeOpen, usePollViewToggle } from '../AppData/useSidepane';
8
+ import { useUnreadPollQuizPresent } from '../hooks/useUnreadPollQuizPresent';
8
9
  // @ts-ignore: No implicit Any
9
10
  import { SIDE_PANE_OPTIONS } from '../../common/constants';
10
11
 
11
12
  export const PollsToggle = () => {
12
13
  const isPollsOpen = useIsSidepaneTypeOpen(SIDE_PANE_OPTIONS.POLLS);
13
14
  const togglePollView = usePollViewToggle();
15
+ const { unreadPollQuiz, setUnreadPollQuiz } = useUnreadPollQuizPresent();
14
16
 
15
17
  return (
16
18
  <Tooltip key="polls" title={`${isPollsOpen ? 'Close' : 'Open'} polls and quizzes`}>
17
- <IconButton onClick={togglePollView} active={!isPollsOpen} data-testid="polls_btn">
18
- <QuizIcon />
19
+ <IconButton
20
+ onClick={() => {
21
+ togglePollView();
22
+ setUnreadPollQuiz(false);
23
+ }}
24
+ active={!isPollsOpen}
25
+ data-testid="polls_btn"
26
+ >
27
+ {unreadPollQuiz ? <QuizActiveIcon /> : <QuizIcon />}
19
28
  </IconButton>
20
29
  </Tooltip>
21
30
  );
@@ -18,6 +18,7 @@ import {
18
18
  HandIcon,
19
19
  HandRaiseSlashedIcon,
20
20
  PeopleIcon,
21
+ QuizActiveIcon,
21
22
  QuizIcon,
22
23
  RecordIcon,
23
24
  SettingsIcon,
@@ -48,6 +49,7 @@ import { useShowPolls } from '../../AppData/useUISettings';
48
49
  import { useDropdownList } from '../../hooks/useDropdownList';
49
50
  // @ts-ignore: No implicit any
50
51
  import { useMyMetadata } from '../../hooks/useMetadata';
52
+ import { useUnreadPollQuizPresent } from '../../hooks/useUnreadPollQuizPresent';
51
53
  // @ts-ignore: No implicit any
52
54
  import { getFormattedCount } from '../../../common/utils';
53
55
  // @ts-ignore: No implicit any
@@ -92,6 +94,7 @@ export const MwebOptions = ({
92
94
  const { isBRBOn, toggleBRB, isHandRaised, toggleHandRaise } = useMyMetadata();
93
95
  const { toggleAudio, toggleVideo } = useAVToggle();
94
96
  const noAVPermissions = !(toggleAudio || toggleVideo);
97
+ const { unreadPollQuiz, setUnreadPollQuiz } = useUnreadPollQuizPresent();
95
98
  // const isVideoOn = useHMSStore(selectIsLocalVideoEnabled);
96
99
 
97
100
  useDropdownList({ open: openModals.size > 0 || openOptionsSheet || openSettingsSheet, name: 'MoreSettings' });
@@ -202,9 +205,10 @@ export const MwebOptions = ({
202
205
  onClick={() => {
203
206
  togglePollView();
204
207
  setOpenOptionsSheet(false);
208
+ setUnreadPollQuiz(false);
205
209
  }}
206
210
  >
207
- <QuizIcon />
211
+ {unreadPollQuiz ? <QuizActiveIcon /> : <QuizIcon />}
208
212
  <ActionTile.Title>Polls and Quizzes</ActionTile.Title>
209
213
  </ActionTile.Root>
210
214
  )}
@@ -262,9 +262,9 @@ export const isValidQuestion = ({ text, type, options, weight, isQuiz = false })
262
262
  return false;
263
263
  }
264
264
 
265
- if (![QUESTION_TYPE.SINGLE_CHOICE, QUESTION_TYPE.MULTIPLE_CHOICE].includes(type)) {
266
- return true;
267
- }
265
+ // if (![QUESTION_TYPE.SINGLE_CHOICE, QUESTION_TYPE.MULTIPLE_CHOICE].includes(type)) {
266
+ // return true;
267
+ // }
268
268
 
269
269
  const everyOptionHasText = options.length > 0 && options.every(option => option && isValidTextInput(option.text, 1));
270
270
  const hasCorrectAnswer = options.some(option => option.isCorrectAnswer);
@@ -78,7 +78,15 @@ export const Leaderboard = ({ pollID }: { pollID: string }) => {
78
78
  <Text variant="xs" css={{ color: '$on_surface_medium' }}>
79
79
  Based on score and time taken to cast the correct answer
80
80
  </Text>
81
- <Box css={{ mt: '$8', gap: '$4', overflowY: 'auto', flex: '1 1 0', mr: '-$6', pr: '$6' }}>
81
+ <Box
82
+ css={{
83
+ mt: '$8',
84
+ overflowY: 'auto',
85
+ flex: '1 1 0',
86
+ mr: '-$6',
87
+ pr: '$6',
88
+ }}
89
+ >
82
90
  {pollLeaderboard?.entries &&
83
91
  pollLeaderboard.entries.map(question => (
84
92
  <LeaderboardEntry
@@ -21,7 +21,7 @@ export const LeaderboardEntry = ({
21
21
  maxPossibleScore: number;
22
22
  }) => {
23
23
  return (
24
- <Flex align="center" justify="between">
24
+ <Flex align="center" justify="between" css={{ my: '$4' }}>
25
25
  <Flex align="center" css={{ gap: '$6' }}>
26
26
  <Flex
27
27
  align="center"
@@ -44,17 +44,17 @@ export const LeaderboardEntry = ({
44
44
  {userName}
45
45
  </Text>
46
46
 
47
- <Text variant="sm">
48
- {score}/{maxPossibleScore} points
47
+ <Text variant="sm" css={{ mt: '$2' }}>
48
+ {score} / {maxPossibleScore} points
49
49
  </Text>
50
50
  </Box>
51
51
  </Flex>
52
- <Flex align="center" css={{ gap: '$6', color: '$on_surface_medium' }}>
53
- {position === 1 ? <TrophyFilledIcon /> : null}
52
+ <Flex align="center" css={{ gap: '$4', color: '$on_surface_medium' }}>
53
+ {position === 1 ? <TrophyFilledIcon height={18} width={18} /> : null}
54
54
  <CheckCircleIcon height={16} width={16} />
55
55
  {questionCount ? (
56
56
  <Text variant="xs">
57
- {correctResponses}/{questionCount}
57
+ {correctResponses} / {questionCount}
58
58
  </Text>
59
59
  ) : null}
60
60
  </Flex>