@100mslive/roomkit-react 0.3.16-alpha.0 → 0.3.16-alpha.10
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.
- package/dist/{HLSView-KOCGTP23.css → HLSView-D62ZFGUE.css} +3 -3
- package/dist/{HLSView-KOCGTP23.css.map → HLSView-D62ZFGUE.css.map} +1 -1
- package/dist/{HLSView-P7GF2RAU.js → HLSView-QYHOAJX7.js} +2 -2
- package/dist/Prebuilt/components/Chat/ChatBody.d.ts +887 -0
- package/dist/Prebuilt/components/Chat/ChatFooter.d.ts +1 -1
- package/dist/Prebuilt/components/Chat/utils.d.ts +2 -0
- package/dist/Prebuilt/components/PIP/PIPChat.d.ts +2 -0
- package/dist/Prebuilt/components/PIP/PIPChatOption.d.ts +5 -0
- package/dist/Prebuilt/components/PIP/PIPProvider.d.ts +6 -0
- package/dist/Prebuilt/components/PIP/PIPWindow.d.ts +7 -0
- package/dist/Prebuilt/components/PIP/context.d.ts +8 -0
- package/dist/Prebuilt/components/PIP/usePIPChat.d.ts +5 -0
- package/dist/Prebuilt/components/PIP/usePIPWindow.d.ts +2 -0
- package/dist/{chunk-DDB4BLHX.js → chunk-NYBDQX5B.js} +10131 -9675
- package/dist/chunk-NYBDQX5B.js.map +7 -0
- package/dist/index.cjs.css +2 -2
- package/dist/index.cjs.css.map +1 -1
- package/dist/index.cjs.js +4021 -3510
- package/dist/index.cjs.js.map +4 -4
- package/dist/index.css +2 -2
- package/dist/index.css.map +1 -1
- package/dist/index.js +1 -1
- package/dist/meta.cjs.json +2292 -1910
- package/dist/meta.esbuild.json +2326 -1944
- package/package.json +8 -7
- package/src/Prebuilt/App.tsx +21 -18
- package/src/Prebuilt/components/AudioVideoToggle.tsx +16 -9
- package/src/Prebuilt/components/Chat/ChatBody.tsx +4 -12
- package/src/Prebuilt/components/Chat/ChatFooter.tsx +2 -3
- package/src/Prebuilt/components/Chat/utils.ts +11 -0
- package/src/Prebuilt/components/ConferenceScreen.tsx +13 -1
- package/src/Prebuilt/components/Footer/ParticipantList.tsx +1 -4
- package/src/Prebuilt/components/MoreSettings/SplitComponents/DesktopOptions.tsx +24 -6
- package/src/Prebuilt/components/Notifications/HandRaisedNotifications.tsx +33 -0
- package/src/Prebuilt/components/Notifications/Notifications.tsx +3 -1
- package/src/Prebuilt/components/PIP/PIPChat.tsx +273 -0
- package/src/Prebuilt/components/PIP/PIPChatOption.tsx +18 -0
- package/src/Prebuilt/components/PIP/PIPProvider.tsx +56 -0
- package/src/Prebuilt/components/PIP/PIPWindow.tsx +13 -0
- package/src/Prebuilt/components/PIP/context.ts +10 -0
- package/src/Prebuilt/components/PIP/usePIPChat.tsx +105 -0
- package/src/Prebuilt/components/PIP/usePIPWindow.tsx +12 -0
- package/src/Prebuilt/components/Preview/PreviewJoin.tsx +3 -1
- package/src/Prebuilt/components/Toast/ToastConfig.jsx +2 -2
- package/dist/chunk-DDB4BLHX.js.map +0 -7
- /package/dist/{HLSView-P7GF2RAU.js.map → HLSView-QYHOAJX7.js.map} +0 -0
@@ -0,0 +1,273 @@
|
|
1
|
+
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
2
|
+
import {
|
3
|
+
selectHMSMessages,
|
4
|
+
selectLocalPeerID,
|
5
|
+
selectPeerNameByID,
|
6
|
+
selectSessionStore,
|
7
|
+
selectUnreadHMSMessagesCount,
|
8
|
+
useHMSStore,
|
9
|
+
} from '@100mslive/react-sdk';
|
10
|
+
import { SendIcon } from '@100mslive/react-icons';
|
11
|
+
import { Box, Flex } from '../../../Layout';
|
12
|
+
import { Text } from '../../../Text';
|
13
|
+
import { TextArea } from '../../../TextArea';
|
14
|
+
import { Tooltip } from '../../../Tooltip';
|
15
|
+
import IconButton from '../../IconButton';
|
16
|
+
import { AnnotisedMessage } from '../Chat/ChatBody';
|
17
|
+
import { useRoomLayoutConferencingScreen } from '../../provider/roomLayoutProvider/hooks/useRoomLayoutScreen';
|
18
|
+
import { useIsPeerBlacklisted } from '../hooks/useChatBlacklist';
|
19
|
+
import { CHAT_MESSAGE_LIMIT, formatTime } from '../Chat/utils';
|
20
|
+
import { SESSION_STORE_KEY } from '../../common/constants';
|
21
|
+
|
22
|
+
export const PIPChat = () => {
|
23
|
+
const messages = useHMSStore(selectHMSMessages);
|
24
|
+
const localPeerID = useHMSStore(selectLocalPeerID);
|
25
|
+
const count = useHMSStore(selectUnreadHMSMessagesCount);
|
26
|
+
const [unreadMessageCount, setUnreadMessageCount] = useState(0);
|
27
|
+
|
28
|
+
const getSenderName = useCallback(
|
29
|
+
(senderName: string, senderID?: string) => {
|
30
|
+
const slicedName = senderName.length > 10 ? senderName.slice(0, 10) + '...' : senderName;
|
31
|
+
return slicedName + (senderID === localPeerID ? ' (You)' : '');
|
32
|
+
},
|
33
|
+
[localPeerID],
|
34
|
+
);
|
35
|
+
|
36
|
+
useEffect(() => {
|
37
|
+
const timeoutId = setTimeout(() => {
|
38
|
+
setUnreadMessageCount(count);
|
39
|
+
}, 100);
|
40
|
+
return () => clearTimeout(timeoutId);
|
41
|
+
}, [count]);
|
42
|
+
|
43
|
+
const blacklistedMessageIDs = useHMSStore(selectSessionStore(SESSION_STORE_KEY.CHAT_MESSAGE_BLACKLIST));
|
44
|
+
const filteredMessages = useMemo(() => {
|
45
|
+
const blacklistedMessageIDSet = new Set(blacklistedMessageIDs || []);
|
46
|
+
return messages?.filter(message => message.type === 'chat' && !blacklistedMessageIDSet.has(message.id)) || [];
|
47
|
+
}, [blacklistedMessageIDs, messages]);
|
48
|
+
const { enabled: isChatEnabled = true, updatedBy: chatStateUpdatedBy = '' } =
|
49
|
+
useHMSStore(selectSessionStore(SESSION_STORE_KEY.CHAT_STATE)) || {};
|
50
|
+
const isLocalPeerBlacklisted = useIsPeerBlacklisted({ local: true });
|
51
|
+
const { elements } = useRoomLayoutConferencingScreen();
|
52
|
+
const message_placeholder = elements?.chat?.message_placeholder || 'Send a message';
|
53
|
+
const canSendChatMessages = !!elements?.chat?.public_chat_enabled || !!elements?.chat?.roles_whitelist?.length;
|
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
|
+
|
71
|
+
return (
|
72
|
+
<div style={{ height: '100%' }}>
|
73
|
+
<Box
|
74
|
+
id="chat-container"
|
75
|
+
css={{
|
76
|
+
bg: '$surface_dim',
|
77
|
+
overflowY: 'auto',
|
78
|
+
// Subtracting height of footer
|
79
|
+
h: canSendChatMessages ? 'calc(100% - 87px)' : '100%',
|
80
|
+
position: 'relative',
|
81
|
+
}}
|
82
|
+
>
|
83
|
+
{unreadMessageCount ? (
|
84
|
+
<Box
|
85
|
+
id="new-message-notif"
|
86
|
+
style={{
|
87
|
+
position: 'fixed',
|
88
|
+
bottom: '76px',
|
89
|
+
right: '4px',
|
90
|
+
}}
|
91
|
+
>
|
92
|
+
<Text
|
93
|
+
variant="xs"
|
94
|
+
css={{ cursor: 'pointer' }}
|
95
|
+
style={{ color: 'white', background: 'gray', padding: '4px', borderRadius: '4px' }}
|
96
|
+
>
|
97
|
+
{unreadMessageCount === 1 ? 'New message' : `${unreadMessageCount} new messages`}
|
98
|
+
</Text>
|
99
|
+
</Box>
|
100
|
+
) : (
|
101
|
+
''
|
102
|
+
)}
|
103
|
+
{filteredMessages.length === 0 ? (
|
104
|
+
<div
|
105
|
+
style={{ display: 'flex', height: '100%', width: '100%', alignItems: 'center', justifyContent: 'center' }}
|
106
|
+
>
|
107
|
+
<Text>No messages here yet</Text>
|
108
|
+
</div>
|
109
|
+
) : (
|
110
|
+
filteredMessages.map(message => (
|
111
|
+
<Box className="pip-message" key={message.id} id={message.id} style={{ padding: '8px 0.75rem' }}>
|
112
|
+
<Flex style={{ width: '100%', alignItems: 'center', justifyContent: 'between' }}>
|
113
|
+
<Text
|
114
|
+
style={{ display: 'flex', justifyContent: 'between', width: '100%', alignItems: 'center' }}
|
115
|
+
css={{
|
116
|
+
color: '$on_surface_high',
|
117
|
+
fontWeight: '$semiBold',
|
118
|
+
}}
|
119
|
+
>
|
120
|
+
<Flex style={{ flexGrow: 1, gap: '2px', alignItems: 'center' }}>
|
121
|
+
{message.senderName === 'You' || !message.senderName ? (
|
122
|
+
<Text as="span" variant="sub2" css={{ color: '$on_surface_high', fontWeight: '$semiBold' }}>
|
123
|
+
{message.senderName || 'Anonymous'}
|
124
|
+
</Text>
|
125
|
+
) : (
|
126
|
+
<Tooltip title={message.senderName} side="top" align="start">
|
127
|
+
<Text as="span" variant="sub2" css={{ color: '$on_surface_high', fontWeight: '$semiBold' }}>
|
128
|
+
{getSenderName(message.senderName, message?.sender)}
|
129
|
+
</Text>
|
130
|
+
</Tooltip>
|
131
|
+
)}
|
132
|
+
<MessageTitle
|
133
|
+
localPeerID={localPeerID}
|
134
|
+
recipientPeer={message.recipientPeer}
|
135
|
+
recipientRoles={message.recipientRoles}
|
136
|
+
/>
|
137
|
+
</Flex>
|
138
|
+
|
139
|
+
<Text
|
140
|
+
variant="xs"
|
141
|
+
css={{
|
142
|
+
color: '$on_surface_medium',
|
143
|
+
flexShrink: 0,
|
144
|
+
p: '$2',
|
145
|
+
whitespace: 'nowrap',
|
146
|
+
}}
|
147
|
+
>
|
148
|
+
{formatTime(message.time)}
|
149
|
+
</Text>
|
150
|
+
</Text>
|
151
|
+
</Flex>
|
152
|
+
<Text
|
153
|
+
variant="sm"
|
154
|
+
css={{
|
155
|
+
w: '100%',
|
156
|
+
mt: '$2',
|
157
|
+
wordBreak: 'break-word',
|
158
|
+
whiteSpace: 'pre-wrap',
|
159
|
+
userSelect: 'all',
|
160
|
+
color: '$on_surface_high',
|
161
|
+
}}
|
162
|
+
>
|
163
|
+
<AnnotisedMessage message={message.message} />
|
164
|
+
</Text>
|
165
|
+
</Box>
|
166
|
+
))
|
167
|
+
)}
|
168
|
+
<div id="marker" style={{ height: filteredMessages.length ? '1px' : 0 }} />
|
169
|
+
</Box>
|
170
|
+
{canSendChatMessages && (
|
171
|
+
<Box css={{ bg: '$surface_dim' }}>
|
172
|
+
<Flex css={{ px: '$4', pb: '3px', gap: '$2', alignItems: 'center' }}>
|
173
|
+
<Text variant="caption">To:</Text>
|
174
|
+
<Flex css={{ bg: '$primary_bright', color: '$on_primary_high', r: '$2' }}>
|
175
|
+
<select
|
176
|
+
id="selector"
|
177
|
+
style={{
|
178
|
+
background: 'inherit',
|
179
|
+
color: 'inherit',
|
180
|
+
border: 'none',
|
181
|
+
outline: 'none',
|
182
|
+
borderRadius: '4px',
|
183
|
+
padding: '0 2px',
|
184
|
+
}}
|
185
|
+
defaultValue={elements.chat?.public_chat_enabled ? 'Everyone' : elements.chat?.roles_whitelist?.[0]}
|
186
|
+
>
|
187
|
+
{elements.chat?.roles_whitelist?.map(role => (
|
188
|
+
<option key={role} value={role}>
|
189
|
+
{role}
|
190
|
+
</option>
|
191
|
+
))}
|
192
|
+
{elements.chat?.public_chat_enabled ? <option value="Everyone">Everyone</option> : ''}
|
193
|
+
</select>
|
194
|
+
</Flex>
|
195
|
+
</Flex>
|
196
|
+
<Flex
|
197
|
+
align="center"
|
198
|
+
css={{
|
199
|
+
bg: '$surface_default',
|
200
|
+
minHeight: '$16',
|
201
|
+
width: '100%',
|
202
|
+
py: '$6',
|
203
|
+
pl: '$4',
|
204
|
+
boxSizing: 'border-box',
|
205
|
+
gap: '$2',
|
206
|
+
r: '$2',
|
207
|
+
}}
|
208
|
+
>
|
209
|
+
<TextArea
|
210
|
+
id="chat-input"
|
211
|
+
maxLength={CHAT_MESSAGE_LIMIT}
|
212
|
+
disabled={!isChatEnabled || isLocalPeerBlacklisted}
|
213
|
+
rows={1}
|
214
|
+
css={{
|
215
|
+
w: '100%',
|
216
|
+
c: '$on_surface_high',
|
217
|
+
p: '0.75rem 0.75rem !important',
|
218
|
+
border: 'none',
|
219
|
+
resize: 'none',
|
220
|
+
}}
|
221
|
+
placeholder={getChatStatus()}
|
222
|
+
required
|
223
|
+
autoComplete="off"
|
224
|
+
aria-autocomplete="none"
|
225
|
+
/>
|
226
|
+
|
227
|
+
<IconButton
|
228
|
+
id="send-btn"
|
229
|
+
disabled={!isChatEnabled || isLocalPeerBlacklisted}
|
230
|
+
title={getChatStatus()}
|
231
|
+
css={{
|
232
|
+
ml: 'auto',
|
233
|
+
height: 'max-content',
|
234
|
+
mr: '$4',
|
235
|
+
'&:hover': { c: '$on_surface_medium' },
|
236
|
+
}}
|
237
|
+
data-testid="send_msg_btn"
|
238
|
+
>
|
239
|
+
<SendIcon />
|
240
|
+
</IconButton>
|
241
|
+
</Flex>
|
242
|
+
</Box>
|
243
|
+
)}
|
244
|
+
</div>
|
245
|
+
);
|
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
|
+
};
|
@@ -0,0 +1,18 @@
|
|
1
|
+
import React from 'react';
|
2
|
+
import { ExternalLinkIcon } from '@100mslive/react-icons';
|
3
|
+
import { Dropdown } from '../../../Dropdown';
|
4
|
+
import { Text } from '../../../Text';
|
5
|
+
|
6
|
+
export const PIPChatOption = ({ openChat, showPIPChat }: { openChat: () => void; showPIPChat: boolean }) => {
|
7
|
+
if (!showPIPChat) {
|
8
|
+
return <></>;
|
9
|
+
}
|
10
|
+
return (
|
11
|
+
<Dropdown.Item onClick={openChat} data-testid="brb_btn">
|
12
|
+
<ExternalLinkIcon height={18} width={18} style={{ padding: '0 $2' }} />
|
13
|
+
<Text variant="sm" css={{ ml: '$4', color: '$on_surface_high' }}>
|
14
|
+
Pop out Chat
|
15
|
+
</Text>
|
16
|
+
</Dropdown.Item>
|
17
|
+
);
|
18
|
+
};
|
@@ -0,0 +1,56 @@
|
|
1
|
+
import React, { useCallback, useMemo, useState } from 'react';
|
2
|
+
import { PIPContext } from './context';
|
3
|
+
|
4
|
+
type PIPProviderProps = {
|
5
|
+
children: React.ReactNode;
|
6
|
+
};
|
7
|
+
|
8
|
+
export const PIPProvider = ({ children }: PIPProviderProps) => {
|
9
|
+
// Detect if the feature is available.
|
10
|
+
const isSupported = 'documentPictureInPicture' in window;
|
11
|
+
|
12
|
+
// Expose pipWindow that is currently active
|
13
|
+
const [pipWindow, setPipWindow] = useState<Window | null>(null);
|
14
|
+
|
15
|
+
// Close pipWidnow programmatically
|
16
|
+
const closePipWindow = useCallback(() => {
|
17
|
+
if (pipWindow != null) {
|
18
|
+
pipWindow.close();
|
19
|
+
setPipWindow(null);
|
20
|
+
}
|
21
|
+
}, [pipWindow]);
|
22
|
+
|
23
|
+
// Open new pipWindow
|
24
|
+
const requestPipWindow = useCallback(
|
25
|
+
async (width: number, height: number) => {
|
26
|
+
// We don't want to allow multiple requests.
|
27
|
+
if (pipWindow != null) {
|
28
|
+
return;
|
29
|
+
}
|
30
|
+
// @ts-ignore for documentPIP
|
31
|
+
const pip = await window.documentPictureInPicture.requestWindow({
|
32
|
+
width,
|
33
|
+
height,
|
34
|
+
});
|
35
|
+
|
36
|
+
// Detect when window is closed by user
|
37
|
+
pip.addEventListener('pagehide', () => {
|
38
|
+
setPipWindow(null);
|
39
|
+
});
|
40
|
+
|
41
|
+
setPipWindow(pip);
|
42
|
+
},
|
43
|
+
[pipWindow],
|
44
|
+
);
|
45
|
+
|
46
|
+
const value = useMemo(() => {
|
47
|
+
return {
|
48
|
+
isSupported,
|
49
|
+
pipWindow,
|
50
|
+
requestPipWindow,
|
51
|
+
closePipWindow,
|
52
|
+
};
|
53
|
+
}, [closePipWindow, isSupported, pipWindow, requestPipWindow]);
|
54
|
+
|
55
|
+
return <PIPContext.Provider value={value}>{children}</PIPContext.Provider>;
|
56
|
+
};
|
@@ -0,0 +1,13 @@
|
|
1
|
+
import React from 'react';
|
2
|
+
import { createPortal } from 'react-dom';
|
3
|
+
|
4
|
+
type PIPWindowProps = {
|
5
|
+
pipWindow: Window;
|
6
|
+
children: React.ReactNode;
|
7
|
+
};
|
8
|
+
|
9
|
+
export const PIPWindow = ({ pipWindow, children }: PIPWindowProps) => {
|
10
|
+
pipWindow.document.body.style.margin = '0';
|
11
|
+
pipWindow.document.body.style.overflow = 'clip';
|
12
|
+
return createPortal(children, pipWindow.document.body);
|
13
|
+
};
|
@@ -0,0 +1,10 @@
|
|
1
|
+
import { createContext } from 'react';
|
2
|
+
|
3
|
+
export type PIPContextType = {
|
4
|
+
isSupported: boolean;
|
5
|
+
pipWindow: Window | null;
|
6
|
+
requestPipWindow: (width: number, height: number) => Promise<void>;
|
7
|
+
closePipWindow: () => void;
|
8
|
+
};
|
9
|
+
|
10
|
+
export const PIPContext = createContext<PIPContextType | undefined>(undefined);
|
@@ -0,0 +1,105 @@
|
|
1
|
+
import { useEffect } from 'react';
|
2
|
+
import { useHMSActions } from '@100mslive/react-sdk';
|
3
|
+
import { getCssText } from '../../../Theme';
|
4
|
+
import { usePIPWindow } from './usePIPWindow';
|
5
|
+
|
6
|
+
export const usePIPChat = () => {
|
7
|
+
const hmsActions = useHMSActions();
|
8
|
+
const { isSupported, requestPipWindow, pipWindow, closePipWindow } = usePIPWindow();
|
9
|
+
|
10
|
+
useEffect(() => {
|
11
|
+
if (document && pipWindow) {
|
12
|
+
const style = document.createElement('style');
|
13
|
+
style.id = 'stitches';
|
14
|
+
style.textContent = getCssText();
|
15
|
+
pipWindow.document.head.appendChild(style);
|
16
|
+
}
|
17
|
+
}, [pipWindow]);
|
18
|
+
|
19
|
+
// @ts-ignore
|
20
|
+
useEffect(() => {
|
21
|
+
if (pipWindow) {
|
22
|
+
const chatContainer = pipWindow.document.getElementById('chat-container');
|
23
|
+
const selector = pipWindow.document.getElementById('selector') as HTMLSelectElement;
|
24
|
+
const sendBtn = pipWindow.document.getElementById('send-btn');
|
25
|
+
const pipChatInput = pipWindow.document.getElementById('chat-input') as HTMLTextAreaElement;
|
26
|
+
const marker = pipWindow.document.getElementById('marker');
|
27
|
+
|
28
|
+
marker?.scrollIntoView({ block: 'end' });
|
29
|
+
|
30
|
+
const observer = new IntersectionObserver(
|
31
|
+
entries => {
|
32
|
+
entries.forEach(entry => {
|
33
|
+
if (entry.isIntersecting && entry.target.id) {
|
34
|
+
hmsActions.setMessageRead(true, entry.target.id);
|
35
|
+
}
|
36
|
+
});
|
37
|
+
},
|
38
|
+
{
|
39
|
+
root: chatContainer,
|
40
|
+
threshold: 0.8,
|
41
|
+
},
|
42
|
+
);
|
43
|
+
|
44
|
+
const mutationObserver = new MutationObserver(mutations => {
|
45
|
+
mutations.forEach(mutation => {
|
46
|
+
if (mutation.addedNodes.length > 0) {
|
47
|
+
const newMessages = mutation.addedNodes;
|
48
|
+
newMessages.forEach(message => {
|
49
|
+
const messageId = (message as Element)?.id;
|
50
|
+
if (messageId === 'new-message-notif') {
|
51
|
+
message.addEventListener('click', () =>
|
52
|
+
setTimeout(() => marker?.scrollIntoView({ block: 'end', behavior: 'smooth' }), 0),
|
53
|
+
);
|
54
|
+
} else if (messageId) observer.observe(message as Element);
|
55
|
+
});
|
56
|
+
}
|
57
|
+
});
|
58
|
+
});
|
59
|
+
mutationObserver.observe(chatContainer as Node, {
|
60
|
+
childList: true,
|
61
|
+
});
|
62
|
+
|
63
|
+
const sendMessage = async () => {
|
64
|
+
const selection = selector?.value || 'Everyone';
|
65
|
+
if (selection === 'Everyone') {
|
66
|
+
await hmsActions.sendBroadcastMessage(pipChatInput.value.trim());
|
67
|
+
} else {
|
68
|
+
await hmsActions.sendGroupMessage(pipChatInput.value.trim(), [selection]);
|
69
|
+
}
|
70
|
+
pipChatInput.value = '';
|
71
|
+
setTimeout(() => marker?.scrollIntoView({ block: 'end', behavior: 'smooth' }), 0);
|
72
|
+
};
|
73
|
+
|
74
|
+
if (sendBtn && hmsActions && pipChatInput) {
|
75
|
+
const pipMessages = pipWindow.document.getElementsByClassName('pip-message');
|
76
|
+
// @ts-ignore
|
77
|
+
[...pipMessages].forEach(message => {
|
78
|
+
if (message.id) {
|
79
|
+
hmsActions.setMessageRead(true, message.id);
|
80
|
+
}
|
81
|
+
});
|
82
|
+
// @ts-ignore
|
83
|
+
const sendOnEnter = e => {
|
84
|
+
if (e.key === 'Enter') sendMessage();
|
85
|
+
};
|
86
|
+
sendBtn.addEventListener('click', sendMessage);
|
87
|
+
pipChatInput.addEventListener('keypress', sendOnEnter);
|
88
|
+
return () => {
|
89
|
+
sendBtn.removeEventListener('click', sendMessage);
|
90
|
+
pipChatInput.removeEventListener('keypress', sendOnEnter);
|
91
|
+
mutationObserver.disconnect();
|
92
|
+
observer.disconnect();
|
93
|
+
};
|
94
|
+
}
|
95
|
+
}
|
96
|
+
}, [pipWindow, hmsActions]);
|
97
|
+
|
98
|
+
useEffect(() => {
|
99
|
+
return () => {
|
100
|
+
pipWindow && closePipWindow();
|
101
|
+
};
|
102
|
+
}, [closePipWindow, pipWindow]);
|
103
|
+
|
104
|
+
return { isSupported, requestPipWindow, pipWindow };
|
105
|
+
};
|
@@ -0,0 +1,12 @@
|
|
1
|
+
import { useContext } from 'react';
|
2
|
+
import { PIPContext, PIPContextType } from './context';
|
3
|
+
|
4
|
+
export const usePIPWindow = (): PIPContextType => {
|
5
|
+
const context = useContext(PIPContext);
|
6
|
+
|
7
|
+
if (context === undefined) {
|
8
|
+
throw new Error('usePIPWindow must be used within a PIPContext');
|
9
|
+
}
|
10
|
+
|
11
|
+
return context;
|
12
|
+
};
|
@@ -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 ?
|
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 ?
|
132
|
+
count > 1 ? `and ${count} others` : ''
|
133
133
|
} raised hand`,
|
134
134
|
icon: <HandIcon />,
|
135
135
|
action: <HandRaiseAction isSingleHandRaise={false} />,
|