@100mslive/roomkit-react 0.1.15 → 0.1.16

Sign up to get free protection for your applications and to get access to all the features.
Files changed (57) hide show
  1. package/dist/{HLSView-MXBOUQBG.js → HLSView-EMUOLCTM.js} +2 -2
  2. package/dist/Prebuilt/common/PeersSorter.d.ts +1 -0
  3. package/dist/Prebuilt/common/constants.d.ts +7 -4
  4. package/dist/Prebuilt/common/hooks.d.ts +1 -0
  5. package/dist/Prebuilt/components/Footer/ParticipantList.d.ts +17 -0
  6. package/dist/Prebuilt/components/Footer/RoleAccordion.d.ts +3 -2
  7. package/dist/Prebuilt/components/Footer/WhiteboardToggle.d.ts +2 -0
  8. package/dist/Prebuilt/components/Notifications/HandRaisedNotifications.d.ts +1 -0
  9. package/dist/Prebuilt/components/PreviousRoleInMetadata.d.ts +1 -0
  10. package/dist/Prebuilt/components/RemoveParticipant.d.ts +5 -0
  11. package/dist/Prebuilt/components/hooks/useCloseScreenshareWhiteboard.d.ts +4 -0
  12. package/dist/Prebuilt/layouts/WhiteboardView.d.ts +2 -0
  13. package/dist/{chunk-HEOH5H43.js → chunk-ZYR4B4KQ.js} +1886 -7116
  14. package/dist/chunk-ZYR4B4KQ.js.map +7 -0
  15. package/dist/index.cjs.js +2477 -7662
  16. package/dist/index.cjs.js.map +4 -4
  17. package/dist/index.js +1 -1
  18. package/dist/meta.cjs.json +438 -161
  19. package/dist/meta.esbuild.json +443 -166
  20. package/package.json +7 -7
  21. package/src/Prebuilt/AppStateContext.tsx +1 -1
  22. package/src/Prebuilt/common/PeersSorter.ts +12 -5
  23. package/src/Prebuilt/common/constants.ts +5 -6
  24. package/src/Prebuilt/common/hooks.ts +16 -0
  25. package/src/Prebuilt/common/utils.js +5 -6
  26. package/src/Prebuilt/components/AppData/AppData.tsx +1 -16
  27. package/src/Prebuilt/components/Chat/Chat.jsx +7 -30
  28. package/src/Prebuilt/components/Chat/ChatBody.jsx +107 -66
  29. package/src/Prebuilt/components/Chat/ChatFooter.tsx +21 -12
  30. package/src/Prebuilt/components/Chat/ChatSelector.tsx +25 -25
  31. package/src/Prebuilt/components/Chat/ChatSelectorContainer.tsx +15 -16
  32. package/src/Prebuilt/components/Chat/PinnedMessage.tsx +7 -2
  33. package/src/Prebuilt/components/ConferenceScreen.tsx +2 -0
  34. package/src/Prebuilt/components/Footer/ChatToggle.tsx +30 -7
  35. package/src/Prebuilt/components/Footer/Footer.tsx +2 -1
  36. package/src/Prebuilt/components/Footer/PaginatedParticipants.tsx +0 -1
  37. package/src/Prebuilt/components/Footer/{ParticipantList.jsx → ParticipantList.tsx} +169 -127
  38. package/src/Prebuilt/components/Footer/RoleAccordion.tsx +23 -13
  39. package/src/Prebuilt/components/Footer/WhiteboardToggle.tsx +34 -0
  40. package/src/Prebuilt/components/Notifications/HandRaisedNotifications.tsx +35 -0
  41. package/src/Prebuilt/components/Notifications/Notifications.tsx +14 -12
  42. package/src/Prebuilt/components/Notifications/PeerNotifications.tsx +7 -2
  43. package/src/Prebuilt/components/Polls/Voting/QuestionCard.jsx +10 -2
  44. package/src/Prebuilt/components/PreviousRoleInMetadata.tsx +21 -0
  45. package/src/Prebuilt/components/RemoveParticipant.tsx +35 -0
  46. package/src/Prebuilt/components/RoleChangeModal.jsx +1 -1
  47. package/src/Prebuilt/components/SidePaneTabs.tsx +0 -1
  48. package/src/Prebuilt/components/TileMenu/TileMenuContent.tsx +1 -1
  49. package/src/Prebuilt/components/Toast/ToastConfig.jsx +15 -3
  50. package/src/Prebuilt/components/VideoLayouts/GridLayout.tsx +5 -2
  51. package/src/Prebuilt/components/hooks/useCloseScreenshareWhiteboard.tsx +24 -0
  52. package/src/Prebuilt/layouts/VideoStreamingSection.tsx +20 -3
  53. package/src/Prebuilt/layouts/WhiteboardView.tsx +66 -0
  54. package/dist/chunk-HEOH5H43.js.map +0 -7
  55. package/src/Prebuilt/components/AppData/useAppLayout.js +0 -6
  56. package/src/Prebuilt/components/init/initUtils.js +0 -67
  57. /package/dist/{HLSView-MXBOUQBG.js.map → HLSView-EMUOLCTM.js.map} +0 -0
package/package.json CHANGED
@@ -10,7 +10,7 @@
10
10
  "prebuilt",
11
11
  "roomkit"
12
12
  ],
13
- "version": "0.1.15",
13
+ "version": "0.1.16",
14
14
  "author": "100ms",
15
15
  "license": "MIT",
16
16
  "files": [
@@ -76,11 +76,11 @@
76
76
  "react": ">=17.0.2 <19.0.0"
77
77
  },
78
78
  "dependencies": {
79
- "@100mslive/hls-player": "0.1.24",
80
- "@100mslive/hms-virtual-background": "1.11.24",
81
- "@100mslive/react-icons": "0.8.24",
82
- "@100mslive/react-sdk": "0.8.24",
83
- "@100mslive/types-prebuilt": "0.12.4",
79
+ "@100mslive/hls-player": "0.1.25",
80
+ "@100mslive/hms-virtual-background": "1.11.25",
81
+ "@100mslive/react-icons": "0.8.25",
82
+ "@100mslive/react-sdk": "0.8.25",
83
+ "@100mslive/types-prebuilt": "0.12.5",
84
84
  "@emoji-mart/data": "^1.0.6",
85
85
  "@emoji-mart/react": "^1.0.1",
86
86
  "@radix-ui/react-accordion": "1.0.0",
@@ -115,5 +115,5 @@
115
115
  "uuid": "^8.3.2",
116
116
  "worker-timers": "^7.0.40"
117
117
  },
118
- "gitHead": "49795c5577e2d578fc0a8f372f3290dd0a52c9b2"
118
+ "gitHead": "096d50e1ffbeb9f2ec895b630952d2ad9694dfee"
119
119
  }
@@ -55,7 +55,7 @@ export const useAppStateManager = () => {
55
55
  setActiveState(PrebuiltStates.MEETING);
56
56
  } else if (
57
57
  prevRoomState &&
58
- [HMSRoomState.Reconnecting, HMSRoomState.Connected].includes(prevRoomState) &&
58
+ [HMSRoomState.Reconnecting, HMSRoomState.Connected, HMSRoomState.Connecting].includes(prevRoomState) &&
59
59
  [HMSRoomState.Disconnecting, HMSRoomState.Disconnected].includes(roomState)
60
60
  ) {
61
61
  const goTo = isPreviewScreenEnabled ? PrebuiltStates.PREVIEW : PrebuiltStates.MEETING;
@@ -53,6 +53,7 @@ class PeersSorter {
53
53
 
54
54
  moveSpeakerToFront = (speaker?: HMSPeer) => {
55
55
  if (!speaker) {
56
+ this.maintainLruSize(this.tilesPerPage);
56
57
  this.updateListeners();
57
58
  return;
58
59
  }
@@ -62,11 +63,9 @@ class PeersSorter {
62
63
  }
63
64
  // delete to insert at beginning
64
65
  this.lruPeers.delete(speaker.id);
65
- const lruPeerArray = Array.from(this.lruPeers);
66
- while (lruPeerArray.length >= this.tilesPerPage && lruPeerArray.length) {
67
- lruPeerArray.pop();
68
- }
69
- this.lruPeers = new Set([speaker.id, ...lruPeerArray]);
66
+ // - 1 as we are going to insert the new speaker
67
+ this.maintainLruSize(this.tilesPerPage - 1);
68
+ this.lruPeers = new Set([speaker.id, ...this.lruPeers]);
70
69
  this.updateListeners();
71
70
  };
72
71
 
@@ -98,6 +97,14 @@ class PeersSorter {
98
97
  });
99
98
  this.listeners.forEach(listener => listener?.(orderedPeers));
100
99
  };
100
+
101
+ private maintainLruSize = (size: number) => {
102
+ const lruPeerArray = Array.from(this.lruPeers);
103
+ while (lruPeerArray.length > size && lruPeerArray.length) {
104
+ lruPeerArray.pop();
105
+ }
106
+ this.lruPeers = new Set(lruPeerArray);
107
+ };
101
108
  }
102
109
 
103
110
  export default PeersSorter;
@@ -17,7 +17,7 @@ export const RTMP_RECORD_DEFAULT_RESOLUTION = {
17
17
  export const EMOJI_REACTION_TYPE = 'EMOJI_REACTION';
18
18
 
19
19
  export const CHAT_SELECTOR = {
20
- PEER_ID: 'peer_id',
20
+ PEER: 'peer',
21
21
  ROLE: 'role',
22
22
  EVERYONE: 'Everyone',
23
23
  };
@@ -33,7 +33,6 @@ export const APP_DATA = {
33
33
  waitingViewerRole: 'waitingViewerRole',
34
34
  subscribedNotifications: 'subscribedNotifications',
35
35
  logo: 'logo',
36
- appLayout: 'appLayout',
37
36
  hlsStarted: 'hlsStarted',
38
37
  rtmpStarted: 'rtmpStarted',
39
38
  recordingStarted: 'recordingStarted',
@@ -122,15 +121,15 @@ export enum INTERACTION_TYPE {
122
121
  export enum QUESTION_TYPE_TITLE {
123
122
  'single-choice' = 'Single Choice',
124
123
  'multiple-choice' = 'Multiple Choice',
125
- // "short-answer": "Short Answer",
126
- // "long-answer": "Long Answer",
124
+ 'short-answer' = 'Short Answer',
125
+ 'long-answer' = 'Long Answer',
127
126
  }
128
127
 
129
128
  export enum QUESTION_TYPE {
130
129
  SINGLE_CHOICE = 'single-choice',
131
130
  MULTIPLE_CHOICE = 'multiple-choice',
132
- // SHORT_ANSWER: "short-answer",
133
- // LONG_ANSWER: "long-answer",
131
+ SHORT_ANSWER = 'short-answer',
132
+ LONG_ANSWER = 'long-answer',
134
133
  }
135
134
 
136
135
  export const ROLE_CHANGE_DECLINED = 'role_change_declined';
@@ -12,6 +12,7 @@ import {
12
12
  } from '@100mslive/react-sdk';
13
13
  import { useRoomLayout } from '../provider/roomLayoutProvider';
14
14
  import { useRoomLayoutConferencingScreen } from '../provider/roomLayoutProvider/hooks/useRoomLayoutScreen';
15
+ import { CHAT_SELECTOR } from './constants';
15
16
  /**
16
17
  * Hook to execute a callback when alone in room(after a certain 5d of time)
17
18
  * @param {number} thresholdMs The threshold(in ms) after which the callback is executed,
@@ -55,6 +56,21 @@ export const useFilteredRoles = () => {
55
56
  return elements?.chat?.roles_whitelist || [];
56
57
  };
57
58
 
59
+ export const useDefaultChatSelection = () => {
60
+ const { elements } = useRoomLayoutConferencingScreen();
61
+ const roles = useFilteredRoles();
62
+ // default is everyone for public chat
63
+ if (elements?.chat?.public_chat_enabled) {
64
+ return CHAT_SELECTOR.EVERYONE;
65
+ }
66
+ // sending first role as default
67
+ if (roles.length > 0) {
68
+ return roles[0];
69
+ }
70
+ // sending empty
71
+ return '';
72
+ };
73
+
58
74
  export const useShowStreamingUI = () => {
59
75
  const layout = useRoomLayout();
60
76
  const { join_form } = layout?.screens?.preview?.default?.elements || {};
@@ -1,4 +1,3 @@
1
- import { isEqual } from 'lodash';
2
1
  import { QUESTION_TYPE } from './constants';
3
2
 
4
3
  // eslint-disable-next-line complexity
@@ -159,11 +158,11 @@ export const getPeerParticipationSummary = (poll, localPeerID, localCustomerUser
159
158
  if (!peerResponse?.[0]) {
160
159
  return;
161
160
  }
162
- const submission = [peerResponse[0].option] || peerResponse[0].options;
163
- const answer =
164
- [questions[peerResponse[0].questionIndex - 1].answer?.option] ||
165
- questions[peerResponse[0].questionIndex - 1].answer?.options;
166
- const isCorrect = isEqual(submission, answer);
161
+ const isCorrect = checkCorrectAnswer(
162
+ questions[peerResponse[0].questionIndex - 1].answer,
163
+ peerResponse[0],
164
+ questions[peerResponse[0].questionIndex - 1].type,
165
+ );
167
166
  if (isCorrect) {
168
167
  score += questions[peerResponse[0].questionIndex - 1]?.weight || 0;
169
168
  correctResponses++;
@@ -1,11 +1,8 @@
1
1
  import React, { useEffect } from 'react';
2
2
  import {
3
3
  HMSRoomState,
4
- selectAvailableRoleNames,
5
4
  selectFullAppData,
6
5
  selectHLSState,
7
- selectLocalPeerRoleName,
8
- selectRolesMap,
9
6
  selectRoomState,
10
7
  selectRTMPState,
11
8
  useHMSActions,
@@ -13,8 +10,6 @@ import {
13
10
  useRecordingStreaming,
14
11
  } from '@100mslive/react-sdk';
15
12
  //@ts-ignore
16
- import { normalizeAppPolicyConfig } from '../init/initUtils';
17
- //@ts-ignore
18
13
  import { UserPreferencesKeys, useUserPreferences } from '../hooks/useUserPreferences';
19
14
  // @ts-ignore
20
15
  import { useIsSidepaneTypeOpen, useSidepaneToggle } from './useSidepane';
@@ -49,7 +44,7 @@ const initialAppData = {
49
44
  [APP_DATA.chatOpen]: false,
50
45
  [APP_DATA.chatSelector]: {
51
46
  [CHAT_SELECTOR.ROLE]: '',
52
- [CHAT_SELECTOR.PEER_ID]: '',
47
+ [CHAT_SELECTOR.PEER]: {},
53
48
  },
54
49
  [APP_DATA.chatDraft]: '',
55
50
  [APP_DATA.sidePane]: '',
@@ -73,9 +68,6 @@ const initialAppData = {
73
68
  export const AppData = React.memo(() => {
74
69
  const hmsActions = useHMSActions();
75
70
  const [preferences = {}] = useUserPreferences(UserPreferencesKeys.UI_SETTINGS);
76
- const roleNames = useHMSStore(selectAvailableRoleNames);
77
- const rolesMap = useHMSStore(selectRolesMap);
78
- const localPeerRole = useHMSStore(selectLocalPeerRoleName);
79
71
  const appData = useHMSStore(selectFullAppData);
80
72
 
81
73
  useEffect(() => {
@@ -109,13 +101,6 @@ export const AppData = React.memo(() => {
109
101
  hmsActions.setAppData(APP_DATA.subscribedNotifications, preferences.subscribedNotifications, true);
110
102
  }, [preferences.subscribedNotifications, hmsActions]);
111
103
 
112
- useEffect(() => {
113
- if (localPeerRole) {
114
- const config = normalizeAppPolicyConfig(roleNames, rolesMap);
115
- hmsActions.setAppData(APP_DATA.appLayout, config[localPeerRole]);
116
- }
117
- }, [roleNames, rolesMap, localPeerRole, hmsActions]);
118
-
119
104
  return <ResetStreamingStart />;
120
105
  });
121
106
 
@@ -1,13 +1,7 @@
1
- import React, { useCallback, useEffect, useRef, useState } from 'react';
1
+ import React, { useCallback, useRef, useState } from 'react';
2
2
  import { useMedia } from 'react-use';
3
- import { selectLocalPeer, selectPeerByID, selectSessionStore } from '@100mslive/hms-video-store';
4
- import {
5
- HMSNotificationTypes,
6
- selectHMSMessagesCount,
7
- useHMSActions,
8
- useHMSNotifications,
9
- useHMSStore,
10
- } from '@100mslive/react-sdk';
3
+ import { selectLocalPeer, selectSessionStore, selectUnreadHMSMessagesCount } from '@100mslive/hms-video-store';
4
+ import { selectHMSMessagesCount, useHMSActions, useHMSStore } from '@100mslive/react-sdk';
11
5
  import { ChevronDownIcon } from '@100mslive/react-icons';
12
6
  import { Button } from '../../../Button';
13
7
  import { Flex } from '../../../Layout';
@@ -17,33 +11,18 @@ import { ChatFooter } from './ChatFooter';
17
11
  import { ChatBlocked, ChatPaused } from './ChatStates';
18
12
  import { PinnedMessage } from './PinnedMessage';
19
13
  import { useRoomLayoutConferencingScreen } from '../../provider/roomLayoutProvider/hooks/useRoomLayoutScreen';
20
- import { useSetSubscribedChatSelector } from '../AppData/useUISettings';
21
14
  import { useSetPinnedMessages } from '../hooks/useSetPinnedMessages';
22
- import { useUnreadCount } from './useUnreadCount';
23
- import { CHAT_SELECTOR, SESSION_STORE_KEY } from '../../common/constants';
15
+ import { SESSION_STORE_KEY } from '../../common/constants';
24
16
 
25
17
  export const Chat = () => {
26
18
  const { elements, screenType } = useRoomLayoutConferencingScreen();
27
- const notification = useHMSNotifications(HMSNotificationTypes.PEER_LEFT);
28
- const [selectedPeer, setPeerSelector] = useSetSubscribedChatSelector(CHAT_SELECTOR.PEER_ID);
29
- const [selectedRole, setRoleSelector] = useSetSubscribedChatSelector(CHAT_SELECTOR.ROLE);
30
19
  const localPeer = useHMSStore(selectLocalPeer);
31
20
  const [isSelectorOpen] = useState(false);
32
21
  const listRef = useRef(null);
33
22
  const hmsActions = useHMSActions();
34
23
  const { removePinnedMessage } = useSetPinnedMessages();
35
24
  const pinnedMessages = useHMSStore(selectSessionStore(SESSION_STORE_KEY.PINNED_MESSAGES)) || [];
36
- const isPeerPresent = !!useHMSStore(selectPeerByID(selectedPeer));
37
25
 
38
- useEffect(() => {
39
- if (notification && notification.data && selectedPeer === notification.data.id) {
40
- setPeerSelector('');
41
- setRoleSelector('');
42
- }
43
- if (selectedPeer && !isPeerPresent) {
44
- setPeerSelector('');
45
- }
46
- }, [notification, selectedPeer, setPeerSelector, setRoleSelector, isPeerPresent]);
47
26
  const blacklistedPeerIDs = useHMSStore(selectSessionStore(SESSION_STORE_KEY.CHAT_PEER_BLACKLIST)) || [];
48
27
  const blacklistedPeerIDSet = new Set(blacklistedPeerIDs);
49
28
  const isLocalPeerBlacklisted = blacklistedPeerIDSet.has(localPeer?.customerUserId);
@@ -97,17 +76,15 @@ export const Chat = () => {
97
76
 
98
77
  {isChatEnabled && !isLocalPeerBlacklisted ? (
99
78
  <ChatFooter onSend={() => scrollToBottom(1)} screenType={screenType}>
100
- {!isSelectorOpen && !isScrolledToBottom && (
101
- <NewMessageIndicator role={selectedRole} peerId={selectedPeer} scrollToBottom={scrollToBottom} />
102
- )}
79
+ {!isSelectorOpen && !isScrolledToBottom && <NewMessageIndicator scrollToBottom={scrollToBottom} />}
103
80
  </ChatFooter>
104
81
  ) : null}
105
82
  </Flex>
106
83
  );
107
84
  };
108
85
 
109
- const NewMessageIndicator = ({ role, peerId, scrollToBottom }) => {
110
- const unreadCount = useUnreadCount({ role, peerId });
86
+ const NewMessageIndicator = ({ scrollToBottom }) => {
87
+ const unreadCount = useHMSStore(selectUnreadHMSMessagesCount);
111
88
  if (!unreadCount) {
112
89
  return null;
113
90
  }
@@ -7,10 +7,11 @@ import {
7
7
  selectHMSMessages,
8
8
  selectLocalPeerID,
9
9
  selectLocalPeerName,
10
- selectMessagesByPeerID,
11
- selectMessagesByRole,
10
+ selectLocalPeerRoleName,
11
+ selectPeerNameByID,
12
12
  selectPermissions,
13
13
  selectSessionStore,
14
+ selectUnreadHMSMessagesCount,
14
15
  useHMSActions,
15
16
  useHMSStore,
16
17
  } from '@100mslive/react-sdk';
@@ -19,6 +20,7 @@ import {
19
20
  CrossCircleIcon,
20
21
  CrossIcon,
21
22
  EyeCloseIcon,
23
+ PeopleRemoveIcon,
22
24
  PinIcon,
23
25
  ReplyIcon,
24
26
  VerticalMenuIcon,
@@ -34,10 +36,9 @@ import emptyChat from '../../images/empty-chat.svg';
34
36
  import { ToastManager } from '../Toast/ToastManager';
35
37
  import { MwebChatOption } from './MwebChatOption';
36
38
  import { useRoomLayoutConferencingScreen } from '../../provider/roomLayoutProvider/hooks/useRoomLayoutScreen';
37
- import { useSetSubscribedChatSelector, useSubscribeChatSelector } from '../AppData/useUISettings';
39
+ import { useSetSubscribedChatSelector } from '../AppData/useUISettings';
38
40
  import { useChatBlacklist } from '../hooks/useChatBlacklist';
39
41
  import { useSetPinnedMessages } from '../hooks/useSetPinnedMessages';
40
- import { useUnreadCount } from './useUnreadCount';
41
42
  import { CHAT_SELECTOR, SESSION_STORE_KEY } from '../../common/constants';
42
43
 
43
44
  const iconStyle = { height: '1.125rem', width: '1.125rem' };
@@ -70,27 +71,28 @@ const MessageTypeContainer = ({ left, right }) => {
70
71
  <Flex
71
72
  align="center"
72
73
  css={{
73
- position: 'absolute',
74
- right: 0,
75
- zIndex: 1,
74
+ ml: '$2',
76
75
  mr: '$4',
77
- p: '$2',
78
- border: '1px solid $border_bright',
79
- r: '$0',
80
- gap: '$3',
76
+ gap: '$space$2',
81
77
  }}
82
- className="message_type_container"
83
78
  >
84
79
  {left && (
85
- <SenderName variant="caption" as="span" css={{ color: '$on_surface_medium' }}>
80
+ <SenderName
81
+ variant="xs"
82
+ as="span"
83
+ css={{ color: '$on_surface_medium', textTransform: 'capitalize', fontWeight: '$regular' }}
84
+ >
86
85
  {left}
87
86
  </SenderName>
88
87
  )}
89
88
  {right && (
90
89
  <SenderName
91
90
  as="span"
92
- variant="caption"
93
- css={{ color: '$on_surface_high', textTransform: 'capitalize', fontWeight: '$semiBold' }}
91
+ variant="overline"
92
+ css={{
93
+ color: '$on_surface_medium',
94
+ fontWeight: '$regular',
95
+ }}
94
96
  >
95
97
  {right}
96
98
  </SenderName>
@@ -99,13 +101,17 @@ const MessageTypeContainer = ({ left, right }) => {
99
101
  );
100
102
  };
101
103
 
102
- const MessageType = ({ roles, receiver }) => {
104
+ const MessageType = ({ roles, hasCurrentUserSent, receiver }) => {
105
+ const peerName = useHMSStore(selectPeerNameByID(receiver));
106
+ const localPeerRoleName = useHMSStore(selectLocalPeerRoleName);
103
107
  if (receiver) {
104
- return <MessageTypeContainer left="Direct Message" />;
108
+ return (
109
+ <MessageTypeContainer left={hasCurrentUserSent ? `${peerName ? `to ${peerName}` : ''}` : 'to You'} right="(DM)" />
110
+ );
105
111
  }
106
112
 
107
- if (roles && roles.length > 0) {
108
- return <MessageTypeContainer left="To Group" right={roles[0]} />;
113
+ if (roles && roles.length) {
114
+ return <MessageTypeContainer left={`to ${hasCurrentUserSent ? roles[0] : localPeerRoleName}`} right="(Group)" />;
109
115
  }
110
116
  return null;
111
117
  };
@@ -153,8 +159,8 @@ const getMessageType = ({ roles, receiver }) => {
153
159
  const ChatActions = ({
154
160
  onPin,
155
161
  showPinAction,
156
- onReplyPrivately,
157
- showReplyPrivateAction,
162
+ onReply,
163
+ showReply,
158
164
  message,
159
165
  sentByLocalPeer,
160
166
  isMobile,
@@ -167,6 +173,8 @@ const ChatActions = ({
167
173
  can_block_user: false,
168
174
  };
169
175
  const [open, setOpen] = useState(false);
176
+ const actions = useHMSActions();
177
+ const canRemoveOthers = useHMSStore(selectPermissions)?.removeOthers;
170
178
  const { blacklistItem: blacklistPeer } = useChatBlacklist(SESSION_STORE_KEY.CHAT_PEER_BLACKLIST);
171
179
 
172
180
  const { blacklistItem: blacklistMessage, blacklistedIDs: blacklistedMessageIDs = [] } = useChatBlacklist(
@@ -199,11 +207,11 @@ const ChatActions = ({
199
207
 
200
208
  const options = {
201
209
  reply: {
202
- text: 'Reply Privately',
203
- tooltipText: 'Reply privately',
210
+ text: message.recipientRoles?.length ? 'Reply to Group' : 'Reply Privately',
211
+ tooltipText: message.recipientRoles?.length ? 'Reply to Group' : 'Reply Privately',
204
212
  icon: <ReplyIcon style={iconStyle} />,
205
- onClick: onReplyPrivately,
206
- show: showReplyPrivateAction,
213
+ onClick: onReply,
214
+ show: showReply,
207
215
  },
208
216
  pin: {
209
217
  text: 'Pin message',
@@ -220,7 +228,7 @@ const ChatActions = ({
220
228
  show: true,
221
229
  },
222
230
  hide: {
223
- text: 'Hide for everyone',
231
+ text: message.recipientPeer ? 'Hide for both' : 'Hide for everyone',
224
232
  icon: <EyeCloseIcon style={iconStyle} />,
225
233
  onClick: async () => {
226
234
  blacklistMessage(message.id);
@@ -235,6 +243,19 @@ const ChatActions = ({
235
243
  color: '$alert_error_default',
236
244
  show: can_block_user && !sentByLocalPeer,
237
245
  },
246
+ remove: {
247
+ text: 'Remove Partipant',
248
+ icon: <PeopleRemoveIcon style={iconStyle} />,
249
+ color: '$alert_error_default',
250
+ show: canRemoveOthers && !sentByLocalPeer,
251
+ onClick: async () => {
252
+ try {
253
+ await actions.removePeer(message.sender, '');
254
+ } catch (error) {
255
+ ToastManager.addToast({ title: error.message, variant: 'error' });
256
+ }
257
+ },
258
+ },
238
259
  };
239
260
 
240
261
  if (isMobile) {
@@ -316,7 +337,7 @@ const ChatActions = ({
316
337
  </Tooltip>
317
338
  ) : null}
318
339
 
319
- {options.block.show || options.hide.show ? (
340
+ {options.block.show || options.hide.show || options.remove.show ? (
320
341
  <Tooltip boxCss={tooltipBoxCSS} title="More actions">
321
342
  <Dropdown.Trigger asChild>
322
343
  <IconButton>
@@ -353,6 +374,18 @@ const ChatActions = ({
353
374
  </Text>
354
375
  </Dropdown.Item>
355
376
  ) : null}
377
+ {options.remove.show ? (
378
+ <Dropdown.Item
379
+ data-testid="remove_peer_btn"
380
+ onClick={options.remove.onClick}
381
+ css={{ color: options.remove.color }}
382
+ >
383
+ {options.remove.icon}
384
+ <Text variant="sm" css={{ ml: '$4', color: 'inherit', fontWeight: '$semiBold' }}>
385
+ {options.remove.text}
386
+ </Text>
387
+ </Dropdown.Item>
388
+ ) : null}
356
389
  </Dropdown.Content>
357
390
  </Dropdown.Portal>
358
391
  </Dropdown.Root>
@@ -363,7 +396,7 @@ const SenderName = styled(Text, {
363
396
  overflow: 'hidden',
364
397
  textOverflow: 'ellipsis',
365
398
  whiteSpace: 'nowrap',
366
- maxWidth: '16ch',
399
+ maxWidth: '14ch',
367
400
  minWidth: 0,
368
401
  color: '$on_surface_high',
369
402
  fontWeight: '$semiBold',
@@ -381,20 +414,25 @@ const ChatMessage = React.memo(
381
414
  }, [index, setRowHeight]);
382
415
  const isMobile = useMedia(cssConfig.media.md);
383
416
  const isPrivateChatEnabled = !!elements?.chat?.private_chat_enabled;
417
+ const roleWhiteList = elements?.chat?.roles_whitelist || [];
384
418
  const isOverlay = elements?.chat?.is_overlay && isMobile;
385
419
  const hmsActions = useHMSActions();
386
420
  const localPeerId = useHMSStore(selectLocalPeerID);
387
- const permissions = useHMSStore(selectPermissions);
388
- const selectedPeer = useSubscribeChatSelector(CHAT_SELECTOR.PEER_ID);
389
- const selectedRole = useSubscribeChatSelector(CHAT_SELECTOR.ROLE);
390
- const [, setPeerSelector] = useSetSubscribedChatSelector(CHAT_SELECTOR.PEER_ID);
421
+ const [selectedRole, setRoleSelector] = useSetSubscribedChatSelector(CHAT_SELECTOR.ROLE);
422
+ const [selectedPeer, setPeerSelector] = useSetSubscribedChatSelector(CHAT_SELECTOR.PEER);
391
423
  const messageType = getMessageType({
392
424
  roles: message.recipientRoles,
393
425
  receiver: message.recipientPeer,
394
426
  });
395
427
  const [openSheet, setOpenSheet] = useState(false);
396
- // show pin action only if peer has remove others permission and the message is of broadcast type
397
- const showPinAction = permissions.removeOthers && !messageType && elements?.chat?.allow_pinning_messages;
428
+ const showPinAction = !!elements?.chat?.allow_pinning_messages;
429
+ let showReply = false;
430
+ if (message.recipientRoles && roleWhiteList.includes(message.recipientRoles[0])) {
431
+ showReply = true;
432
+ } else if (message.sender !== selectedPeer.id && message.sender !== localPeerId && isPrivateChatEnabled) {
433
+ showReply = true;
434
+ }
435
+
398
436
  useEffect(() => {
399
437
  if (message.id && !message.read && inView) {
400
438
  hmsActions.setMessageRead(true, message.id);
@@ -428,7 +466,7 @@ const ChatMessage = React.memo(
428
466
  position: 'relative',
429
467
  // Theme independent color, token should not be used for transparent chat
430
468
  bg:
431
- messageType && !(selectedPeer || selectedRole)
469
+ messageType && !(selectedPeer.id || selectedRole)
432
470
  ? isOverlay
433
471
  ? 'rgba(0, 0, 0, 0.64)'
434
472
  : '$surface_default'
@@ -482,31 +520,45 @@ const ChatMessage = React.memo(
482
520
  </SenderName>
483
521
  </Tooltip>
484
522
  )}
485
- {!isOverlay ? (
486
- <Text
487
- as="span"
488
- variant="caption"
489
- css={{
490
- ml: '$2',
491
- color: '$on_surface_medium',
492
- flexShrink: 0,
493
- }}
494
- >
495
- {formatTime(message.time)}
496
- </Text>
497
- ) : null}
523
+ <MessageType
524
+ hasCurrentUserSent={message.sender === localPeerId}
525
+ receiver={message.recipientPeer}
526
+ roles={message.recipientRoles}
527
+ />
498
528
  </Flex>
499
- {!(selectedPeer || selectedRole) && (
500
- <MessageType receiver={message.recipientPeer} roles={message.recipientRoles} />
501
- )}
502
529
 
530
+ {!isOverlay ? (
531
+ <Text
532
+ as="span"
533
+ variant="caption"
534
+ css={{
535
+ color: '$on_surface_medium',
536
+ flexShrink: 0,
537
+ position: 'absolute',
538
+ right: 0,
539
+ zIndex: 1,
540
+ mr: '$4',
541
+ p: '$2',
542
+ }}
543
+ >
544
+ {formatTime(message.time)}
545
+ </Text>
546
+ ) : null}
503
547
  <ChatActions
504
548
  onPin={onPin}
505
549
  showPinAction={showPinAction}
506
550
  message={message}
507
551
  sentByLocalPeer={message.sender === localPeerId}
508
- onReplyPrivately={() => setPeerSelector(message.sender)}
509
- showReplyPrivateAction={!selectedPeer && message.sender !== localPeerId && isPrivateChatEnabled}
552
+ onReply={() => {
553
+ if (message.recipientRoles?.length) {
554
+ setRoleSelector(message.recipientRoles[0]);
555
+ setPeerSelector({});
556
+ } else {
557
+ setRoleSelector('');
558
+ setPeerSelector({ id: message.sender, name: message.senderName });
559
+ }
560
+ }}
561
+ showReply={showReply}
510
562
  isMobile={isMobile}
511
563
  openSheet={openSheet}
512
564
  setOpenSheet={setOpenSheet}
@@ -622,27 +674,16 @@ const VirtualizedChatMessages = React.forwardRef(({ messages, unreadCount = 0, s
622
674
  });
623
675
 
624
676
  export const ChatBody = React.forwardRef(({ scrollToBottom }, listRef) => {
625
- const selectedPeer = useSubscribeChatSelector(CHAT_SELECTOR.PEER_ID);
626
- const selectedRole = useSubscribeChatSelector(CHAT_SELECTOR.ROLE);
627
- let storeMessageSelector;
628
- if (selectedRole) {
629
- storeMessageSelector = selectMessagesByRole(selectedRole);
630
- } else if (selectedPeer) {
631
- storeMessageSelector = selectMessagesByPeerID(selectedPeer);
632
- } else {
633
- storeMessageSelector = selectHMSMessages;
634
- }
635
- let messages = useHMSStore(storeMessageSelector) || [];
677
+ let messages = useHMSStore(selectHMSMessages);
636
678
  const blacklistedMessageIDs = useHMSStore(selectSessionStore(SESSION_STORE_KEY.CHAT_MESSAGE_BLACKLIST)) || [];
637
679
  const getFilteredMessages = () => {
638
680
  const blacklistedMessageIDSet = new Set(blacklistedMessageIDs);
639
-
640
681
  return messages?.filter(message => message.type === 'chat' && !blacklistedMessageIDSet.has(message.id)) || [];
641
682
  };
642
683
 
643
684
  const isMobile = useMedia(cssConfig.media.md);
644
685
  const { elements } = useRoomLayoutConferencingScreen();
645
- const unreadCount = useUnreadCount({ role: selectedRole, peerId: selectedPeer });
686
+ const unreadCount = useHMSStore(selectUnreadHMSMessagesCount);
646
687
 
647
688
  if (messages.length === 0 && !(isMobile && elements?.chat?.is_overlay)) {
648
689
  return (