@100mslive/roomkit-react 0.3.15 → 0.3.16-alpha.1
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-ICCDFOHV.js → HLSView-ERPYXVOY.js} +2 -2
- package/dist/{HLSView-SFAI4O64.css → HLSView-T2PXKR6I.css} +3 -3
- package/dist/{HLSView-SFAI4O64.css.map → HLSView-T2PXKR6I.css.map} +1 -1
- 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-WKJZMVEX.js → chunk-NKC36NL4.js} +8533 -8150
- package/dist/chunk-NKC36NL4.js.map +7 -0
- package/dist/index.cjs.css +2 -2
- package/dist/index.cjs.css.map +1 -1
- package/dist/index.cjs.js +3921 -3481
- 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 +1790 -1438
- package/dist/meta.esbuild.json +2330 -1978
- package/package.json +8 -7
- package/src/Prebuilt/components/AudioVideoToggle.tsx +14 -7
- 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/Footer/RoleAccordion.tsx +15 -1
- package/src/Prebuilt/components/Header/HeaderComponents.jsx +2 -3
- package/src/Prebuilt/components/MoreSettings/MoreSettings.tsx +4 -1
- package/src/Prebuilt/components/MoreSettings/SplitComponents/DesktopOptions.tsx +13 -5
- package/src/Prebuilt/components/PIP/PIPChat.tsx +225 -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 +103 -0
- package/src/Prebuilt/components/PIP/usePIPWindow.tsx +12 -0
- package/src/Prebuilt/components/hooks/useMetadata.tsx +2 -1
- package/dist/chunk-WKJZMVEX.js.map +0 -7
- /package/dist/{HLSView-ICCDFOHV.js.map → HLSView-ERPYXVOY.js.map} +0 -0
package/package.json
CHANGED
@@ -10,7 +10,7 @@
|
|
10
10
|
"prebuilt",
|
11
11
|
"roomkit"
|
12
12
|
],
|
13
|
-
"version": "0.3.
|
13
|
+
"version": "0.3.16-alpha.1",
|
14
14
|
"author": "100ms",
|
15
15
|
"license": "MIT",
|
16
16
|
"repository": {
|
@@ -75,12 +75,12 @@
|
|
75
75
|
"react": ">=17.0.2 <19.0.0"
|
76
76
|
},
|
77
77
|
"dependencies": {
|
78
|
-
"@100mslive/hls-player": "0.3.
|
78
|
+
"@100mslive/hls-player": "0.3.16-alpha.1",
|
79
79
|
"@100mslive/hms-noise-cancellation": "0.0.1",
|
80
|
-
"@100mslive/hms-virtual-background": "1.13.
|
81
|
-
"@100mslive/hms-whiteboard": "0.0.
|
82
|
-
"@100mslive/react-icons": "0.10.
|
83
|
-
"@100mslive/react-sdk": "0.10.
|
80
|
+
"@100mslive/hms-virtual-background": "1.13.16-alpha.1",
|
81
|
+
"@100mslive/hms-whiteboard": "0.0.6-alpha.1",
|
82
|
+
"@100mslive/react-icons": "0.10.16-alpha.1",
|
83
|
+
"@100mslive/react-sdk": "0.10.16-alpha.1",
|
84
84
|
"@100mslive/types-prebuilt": "0.12.9",
|
85
85
|
"@emoji-mart/data": "^1.0.6",
|
86
86
|
"@emoji-mart/react": "^1.0.1",
|
@@ -104,6 +104,7 @@
|
|
104
104
|
"eventemitter2": "^6.4.9",
|
105
105
|
"lodash.merge": "^4.6.2",
|
106
106
|
"qrcode.react": "^3.1.0",
|
107
|
+
"react-dom": "^18.2.0",
|
107
108
|
"react-draggable": "^4.4.5",
|
108
109
|
"react-intersection-observer": "^9.4.3",
|
109
110
|
"react-swipeable": "^7.0.1",
|
@@ -116,5 +117,5 @@
|
|
116
117
|
"uuid": "^8.3.2",
|
117
118
|
"worker-timers": "^7.0.40"
|
118
119
|
},
|
119
|
-
"gitHead": "
|
120
|
+
"gitHead": "2ae950c4b6a1aed199e246ce1b8f9d2c1c2ebe7c"
|
120
121
|
}
|
@@ -109,6 +109,9 @@ const useNoiseCancellationWithPlugin = () => {
|
|
109
109
|
if (inProgress) {
|
110
110
|
return;
|
111
111
|
}
|
112
|
+
if (!krispPlugin.checkSupport().isSupported) {
|
113
|
+
throw Error('Krisp plugin is not supported');
|
114
|
+
}
|
112
115
|
setInProgress(true);
|
113
116
|
if (enabled) {
|
114
117
|
await actions.addPluginToAudioTrack(krispPlugin);
|
@@ -281,13 +284,17 @@ export const AudioVideoToggle = ({ hideOptions = false }: { hideOptions?: boolea
|
|
281
284
|
useEffect(() => {
|
282
285
|
(async () => {
|
283
286
|
if (isNoiseCancellationEnabled && !isKrispPluginAdded && !inProgress && localPeer?.audioTrack) {
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
287
|
+
try {
|
288
|
+
await setNoiseCancellationWithPlugin(true);
|
289
|
+
ToastManager.addToast({
|
290
|
+
title: `Noise Reduction Enabled`,
|
291
|
+
variant: 'standard',
|
292
|
+
duration: 2000,
|
293
|
+
icon: <AudioLevelIcon />,
|
294
|
+
});
|
295
|
+
} catch (error) {
|
296
|
+
console.error(error);
|
297
|
+
}
|
291
298
|
}
|
292
299
|
})();
|
293
300
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
@@ -28,18 +28,9 @@ import { useRoomLayoutConferencingScreen } from '../../provider/roomLayoutProvid
|
|
28
28
|
// @ts-ignore: No implicit Any
|
29
29
|
import { useSetSubscribedChatSelector } from '../AppData/useUISettings';
|
30
30
|
import { usePinnedBy } from '../hooks/usePinnedBy';
|
31
|
+
import { formatTime } from './utils';
|
31
32
|
import { CHAT_SELECTOR, SESSION_STORE_KEY } from '../../common/constants';
|
32
33
|
|
33
|
-
const formatTime = (date: Date) => {
|
34
|
-
if (!(date instanceof Date)) {
|
35
|
-
return '';
|
36
|
-
}
|
37
|
-
const hours = date.getHours();
|
38
|
-
const minutes = date.getMinutes();
|
39
|
-
const suffix = hours > 11 ? 'PM' : 'AM';
|
40
|
-
return `${hours < 10 ? '0' : ''}${hours}:${minutes < 10 ? '0' : ''}${minutes} ${suffix}`;
|
41
|
-
};
|
42
|
-
|
43
34
|
const rowHeights: Record<number, { size: number; id: string }> = {};
|
44
35
|
let listInstance: VariableSizeList | null = null; //eslint-disable-line
|
45
36
|
function getRowHeight(index: number) {
|
@@ -109,7 +100,7 @@ const MessageTypeContainer = ({ left, right }: { left?: string; right?: string }
|
|
109
100
|
);
|
110
101
|
};
|
111
102
|
|
112
|
-
const MessageType = ({
|
103
|
+
export const MessageType = ({
|
113
104
|
roles,
|
114
105
|
hasCurrentUserSent,
|
115
106
|
receiver,
|
@@ -172,7 +163,8 @@ const getMessageType = ({ roles, receiver }: { roles?: HMSRoleName[]; receiver?:
|
|
172
163
|
}
|
173
164
|
return receiver ? 'private' : '';
|
174
165
|
};
|
175
|
-
|
166
|
+
|
167
|
+
export const SenderName = styled(Text, {
|
176
168
|
overflow: 'hidden',
|
177
169
|
textOverflow: 'ellipsis',
|
178
170
|
whiteSpace: 'nowrap',
|
@@ -20,10 +20,9 @@ import { useIsPeerBlacklisted } from '../hooks/useChatBlacklist';
|
|
20
20
|
// @ts-ignore: No implicit any
|
21
21
|
import { useEmojiPickerStyles } from './useEmojiPickerStyles';
|
22
22
|
import { useDefaultChatSelection, useLandscapeHLSStream, useMobileHLSStream } from '../../common/hooks';
|
23
|
+
import { CHAT_MESSAGE_LIMIT } from './utils';
|
23
24
|
import { CHAT_SELECTOR, SESSION_STORE_KEY } from '../../common/constants';
|
24
25
|
|
25
|
-
const CHAT_MESSAGE_LIMIT = 2000;
|
26
|
-
|
27
26
|
const TextArea = styled('textarea', {
|
28
27
|
width: '100%',
|
29
28
|
bg: 'transparent',
|
@@ -76,7 +75,7 @@ function EmojiPicker({ onSelect }: { onSelect: (emoji: any) => void }) {
|
|
76
75
|
);
|
77
76
|
}
|
78
77
|
|
79
|
-
export const ChatFooter = ({ onSend, children }: { onSend: (count: number) => void; children
|
78
|
+
export const ChatFooter = ({ onSend, children }: { onSend: (count: number) => void; children?: ReactNode }) => {
|
80
79
|
const hmsActions = useHMSActions();
|
81
80
|
const inputRef = useRef<HTMLTextAreaElement>(null);
|
82
81
|
const [draftMessage, setDraftMessage] = useChatDraftMessage();
|
@@ -0,0 +1,11 @@
|
|
1
|
+
export const formatTime = (date: Date) => {
|
2
|
+
if (!(date instanceof Date)) {
|
3
|
+
return '';
|
4
|
+
}
|
5
|
+
const hours = date.getHours();
|
6
|
+
const minutes = date.getMinutes();
|
7
|
+
const suffix = hours > 11 ? 'PM' : 'AM';
|
8
|
+
return `${hours < 10 ? '0' : ''}${hours}:${minutes < 10 ? '0' : ''}${minutes} ${suffix}`;
|
9
|
+
};
|
10
|
+
|
11
|
+
export const CHAT_MESSAGE_LIMIT = 2000;
|
@@ -122,7 +122,21 @@ export const RoleAccordion = ({
|
|
122
122
|
<Accordion.Content contentStyles={{ border: '1px solid $border_default', borderTop: 'none' }}>
|
123
123
|
<FixedSizeList
|
124
124
|
itemSize={ROW_HEIGHT}
|
125
|
-
itemData={{
|
125
|
+
itemData={{
|
126
|
+
peerList: isHandRaisedAccordion
|
127
|
+
? peersInAccordion.sort((a, b) => {
|
128
|
+
try {
|
129
|
+
const aHandRaisedAt = JSON.parse(a.metadata || '{}').handRaisedAt;
|
130
|
+
const bHandRaisedAt = JSON.parse(b.metadata || '{}').handRaisedAt;
|
131
|
+
return aHandRaisedAt - bHandRaisedAt;
|
132
|
+
} catch (err) {
|
133
|
+
return 0;
|
134
|
+
}
|
135
|
+
})
|
136
|
+
: peersInAccordion,
|
137
|
+
isConnected,
|
138
|
+
isHandRaisedAccordion,
|
139
|
+
}}
|
126
140
|
itemKey={itemKey}
|
127
141
|
itemCount={peersInAccordion.length}
|
128
142
|
width={width}
|
@@ -1,7 +1,7 @@
|
|
1
1
|
import React, { useEffect, useState } from 'react';
|
2
2
|
import { selectDominantSpeaker, useHMSStore } from '@100mslive/react-sdk';
|
3
3
|
import { VolumeOneIcon } from '@100mslive/react-icons';
|
4
|
-
import { Flex, styled, Text, textEllipsis
|
4
|
+
import { Flex, styled, Text, textEllipsis } from '../../../';
|
5
5
|
import { useRoomLayout } from '../../provider/roomLayoutProvider';
|
6
6
|
|
7
7
|
export const SpeakerTag = () => {
|
@@ -14,9 +14,8 @@ export const SpeakerTag = () => {
|
|
14
14
|
justify="center"
|
15
15
|
css={{ flex: '1 1 0', color: '$on_surface_high', '@md': { display: 'none' } }}
|
16
16
|
>
|
17
|
-
<VerticalDivider css={{ ml: '$8' }} />
|
18
17
|
<VolumeOneIcon />
|
19
|
-
<Text variant="
|
18
|
+
<Text variant="sm" css={{ ...textEllipsis(200), ml: '$2' }} title={dominantSpeaker.name}>
|
20
19
|
{dominantSpeaker.name}
|
21
20
|
</Text>
|
22
21
|
</Flex>
|
@@ -10,6 +10,7 @@ import { DesktopOptions } from './SplitComponents/DesktopOptions';
|
|
10
10
|
// @ts-ignore: No implicit Any
|
11
11
|
import { MwebOptions } from './SplitComponents/MwebOptions';
|
12
12
|
import { config as cssConfig } from '../../..';
|
13
|
+
import { PIPProvider } from '../PIP/PIPProvider';
|
13
14
|
import { useLandscapeHLSStream } from '../../common/hooks';
|
14
15
|
|
15
16
|
export const MoreSettings = ({
|
@@ -24,6 +25,8 @@ export const MoreSettings = ({
|
|
24
25
|
return isMobile || isLandscapeHLSStream ? (
|
25
26
|
<MwebOptions elements={elements} screenType={screenType} />
|
26
27
|
) : (
|
27
|
-
<
|
28
|
+
<PIPProvider>
|
29
|
+
<DesktopOptions elements={elements} screenType={screenType} />
|
30
|
+
</PIPProvider>
|
28
31
|
);
|
29
32
|
};
|
@@ -28,8 +28,11 @@ import { Checkbox, Dropdown, Flex, Switch, Text, Tooltip } from '../../../..';
|
|
28
28
|
import IconButton from '../../../IconButton';
|
29
29
|
// @ts-ignore: No implicit any
|
30
30
|
import { PIP } from '../../PIP';
|
31
|
+
import { PIPChat } from '../../PIP/PIPChat';
|
31
32
|
// @ts-ignore: No implicit any
|
33
|
+
import { PIPChatOption } from '../../PIP/PIPChatOption';
|
32
34
|
import { PictureInPicture } from '../../PIP/PIPManager';
|
35
|
+
import { PIPWindow } from '../../PIP/PIPWindow';
|
33
36
|
// @ts-ignore: No implicit any
|
34
37
|
import { RoleChangeModal } from '../../RoleChangeModal';
|
35
38
|
// @ts-ignore: No implicit any
|
@@ -48,6 +51,8 @@ import { MuteAllModal } from '../MuteAllModal';
|
|
48
51
|
import { useDropdownList } from '../../hooks/useDropdownList';
|
49
52
|
import { useMyMetadata } from '../../hooks/useMetadata';
|
50
53
|
// @ts-ignore: No implicit any
|
54
|
+
import { usePIPChat } from '../../PIP/usePIPChat';
|
55
|
+
// @ts-ignore: No implicit any
|
51
56
|
import { APP_DATA, isMacOS } from '../../../common/constants';
|
52
57
|
|
53
58
|
const MODALS = {
|
@@ -79,6 +84,8 @@ export const DesktopOptions = ({
|
|
79
84
|
const isBRBEnabled = !!elements?.brb;
|
80
85
|
const isTranscriptionAllowed = useHMSStore(selectIsTranscriptionAllowedByMode(HMSTranscriptionMode.CAPTION));
|
81
86
|
const isTranscriptionEnabled = useHMSStore(selectIsTranscriptionEnabled);
|
87
|
+
const { isSupported, pipWindow, requestPipWindow } = usePIPChat();
|
88
|
+
const showPipChatOption = !!elements?.chat && isSupported;
|
82
89
|
|
83
90
|
useDropdownList({ open: openModals.size > 0, name: 'MoreSettings' });
|
84
91
|
|
@@ -98,6 +105,11 @@ export const DesktopOptions = ({
|
|
98
105
|
|
99
106
|
return (
|
100
107
|
<Fragment>
|
108
|
+
{isSupported && pipWindow ? (
|
109
|
+
<PIPWindow pipWindow={pipWindow}>
|
110
|
+
<PIPChat />
|
111
|
+
</PIPWindow>
|
112
|
+
) : null}
|
101
113
|
<Dropdown.Root
|
102
114
|
open={openModals.has(MODALS.MORE_SETTINGS)}
|
103
115
|
onOpenChange={value => updateState(MODALS.MORE_SETTINGS, value)}
|
@@ -168,13 +180,9 @@ export const DesktopOptions = ({
|
|
168
180
|
</Dropdown.Item>
|
169
181
|
) : null}
|
170
182
|
|
183
|
+
<PIPChatOption showPIPChat={showPipChatOption} openChat={async () => await requestPipWindow(350, 500)} />
|
171
184
|
<FullScreenItem />
|
172
|
-
{/* {isAllowedToPublish.screen && isEmbedEnabled && (
|
173
|
-
<EmbedUrl setShowOpenUrl={() => updateState(MODALS.EMBED_URL, true)} />
|
174
|
-
)} */}
|
175
|
-
|
176
185
|
<Dropdown.ItemSeparator css={{ mx: 0 }} />
|
177
|
-
|
178
186
|
<Dropdown.Item onClick={() => updateState(MODALS.DEVICE_SETTINGS, true)} data-testid="device_settings_btn">
|
179
187
|
<SettingsIcon />
|
180
188
|
<Text variant="sm" css={{ ml: '$4' }}>
|
@@ -0,0 +1,225 @@
|
|
1
|
+
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
2
|
+
import {
|
3
|
+
selectHMSMessages,
|
4
|
+
selectLocalPeerID,
|
5
|
+
selectSessionStore,
|
6
|
+
selectUnreadHMSMessagesCount,
|
7
|
+
useHMSStore,
|
8
|
+
} from '@100mslive/react-sdk';
|
9
|
+
import { SendIcon } from '@100mslive/react-icons';
|
10
|
+
import { Box, Flex } from '../../../Layout';
|
11
|
+
import { Text } from '../../../Text';
|
12
|
+
import { TextArea } from '../../../TextArea';
|
13
|
+
import { Tooltip } from '../../../Tooltip';
|
14
|
+
import IconButton from '../../IconButton';
|
15
|
+
import { AnnotisedMessage } from '../Chat/ChatBody';
|
16
|
+
import { useRoomLayoutConferencingScreen } from '../../provider/roomLayoutProvider/hooks/useRoomLayoutScreen';
|
17
|
+
import { CHAT_MESSAGE_LIMIT, formatTime } from '../Chat/utils';
|
18
|
+
import { SESSION_STORE_KEY } from '../../common/constants';
|
19
|
+
|
20
|
+
export const PIPChat = () => {
|
21
|
+
const messages = useHMSStore(selectHMSMessages);
|
22
|
+
const localPeerID = useHMSStore(selectLocalPeerID);
|
23
|
+
const count = useHMSStore(selectUnreadHMSMessagesCount);
|
24
|
+
const [unreadMessageCount, setUnreadMessageCount] = useState(0);
|
25
|
+
|
26
|
+
const getSenderName = useCallback(
|
27
|
+
(senderName: string, senderID?: string) => {
|
28
|
+
const slicedName = senderName.length > 10 ? senderName.slice(0, 10) + '...' : senderName;
|
29
|
+
return slicedName + (senderID === localPeerID ? ' (You)' : '');
|
30
|
+
},
|
31
|
+
[localPeerID],
|
32
|
+
);
|
33
|
+
|
34
|
+
useEffect(() => {
|
35
|
+
const timeoutId = setTimeout(() => {
|
36
|
+
setUnreadMessageCount(count);
|
37
|
+
}, 100);
|
38
|
+
return () => clearTimeout(timeoutId);
|
39
|
+
}, [count]);
|
40
|
+
|
41
|
+
const blacklistedMessageIDs = useHMSStore(selectSessionStore(SESSION_STORE_KEY.CHAT_MESSAGE_BLACKLIST));
|
42
|
+
const filteredMessages = useMemo(() => {
|
43
|
+
const blacklistedMessageIDSet = new Set(blacklistedMessageIDs || []);
|
44
|
+
return messages?.filter(message => message.type === 'chat' && !blacklistedMessageIDSet.has(message.id)) || [];
|
45
|
+
}, [blacklistedMessageIDs, messages]);
|
46
|
+
const { elements } = useRoomLayoutConferencingScreen();
|
47
|
+
const message_placeholder = elements?.chat?.message_placeholder || 'Send a message';
|
48
|
+
const canSendChatMessages = !!elements?.chat?.public_chat_enabled || !!elements?.chat?.roles_whitelist?.length;
|
49
|
+
|
50
|
+
return (
|
51
|
+
<div style={{ height: '100%' }}>
|
52
|
+
<Box
|
53
|
+
id="chat-container"
|
54
|
+
css={{
|
55
|
+
bg: '$surface_dim',
|
56
|
+
overflowY: 'auto',
|
57
|
+
// Subtracting height of footer
|
58
|
+
h: canSendChatMessages ? 'calc(100% - 90px)' : '100%',
|
59
|
+
position: 'relative',
|
60
|
+
}}
|
61
|
+
>
|
62
|
+
{unreadMessageCount ? (
|
63
|
+
<Box
|
64
|
+
id="new-message-notif"
|
65
|
+
style={{
|
66
|
+
position: 'fixed',
|
67
|
+
bottom: '76px',
|
68
|
+
right: '4px',
|
69
|
+
}}
|
70
|
+
>
|
71
|
+
<Text
|
72
|
+
variant="xs"
|
73
|
+
css={{ cursor: 'pointer' }}
|
74
|
+
style={{ color: 'white', background: 'gray', padding: '4px', borderRadius: '4px' }}
|
75
|
+
>
|
76
|
+
{unreadMessageCount === 1 ? 'New message' : `${unreadMessageCount} new messages`}
|
77
|
+
</Text>
|
78
|
+
</Box>
|
79
|
+
) : (
|
80
|
+
''
|
81
|
+
)}
|
82
|
+
{filteredMessages.length === 0 ? (
|
83
|
+
<div
|
84
|
+
style={{ display: 'flex', height: '100%', width: '100%', alignItems: 'center', justifyContent: 'center' }}
|
85
|
+
>
|
86
|
+
<Text>No messages here yet</Text>
|
87
|
+
</div>
|
88
|
+
) : (
|
89
|
+
filteredMessages.map(message => (
|
90
|
+
<Box className="pip-message" key={message.id} id={message.id} style={{ padding: '8px 0.75rem' }}>
|
91
|
+
<Flex style={{ width: '100%', alignItems: 'center', justifyContent: 'between' }}>
|
92
|
+
<Text
|
93
|
+
style={{ display: 'flex', justifyContent: 'between', width: '100%', alignItems: 'center' }}
|
94
|
+
css={{
|
95
|
+
color: '$on_surface_high',
|
96
|
+
fontWeight: '$semiBold',
|
97
|
+
}}
|
98
|
+
>
|
99
|
+
<Flex style={{ flexGrow: 1, gap: '2px', alignItems: 'center' }}>
|
100
|
+
{message.senderName === 'You' || !message.senderName ? (
|
101
|
+
<Text as="span" variant="sub2" css={{ color: '$on_surface_high', fontWeight: '$semiBold' }}>
|
102
|
+
{message.senderName || 'Anonymous'}
|
103
|
+
</Text>
|
104
|
+
) : (
|
105
|
+
<Tooltip title={message.senderName} side="top" align="start">
|
106
|
+
<Text as="span" variant="sub2" css={{ color: '$on_surface_high', fontWeight: '$semiBold' }}>
|
107
|
+
{getSenderName(message.senderName, message?.sender)}
|
108
|
+
</Text>
|
109
|
+
</Tooltip>
|
110
|
+
)}
|
111
|
+
{message.recipientRoles ? (
|
112
|
+
<Text as="span" variant="sub2" css={{ color: '$on_surface_high', fontWeight: '$semiBold' }}>
|
113
|
+
to {message.recipientRoles} (Group)
|
114
|
+
</Text>
|
115
|
+
) : null}
|
116
|
+
{message.recipientPeer ? (
|
117
|
+
<Text as="span" variant="sub2" css={{ color: '$on_surface_high', fontWeight: '$semiBold' }}>
|
118
|
+
(DM)
|
119
|
+
</Text>
|
120
|
+
) : null}
|
121
|
+
</Flex>
|
122
|
+
|
123
|
+
<Text
|
124
|
+
variant="xs"
|
125
|
+
css={{
|
126
|
+
color: '$on_surface_medium',
|
127
|
+
flexShrink: 0,
|
128
|
+
p: '$2',
|
129
|
+
whitespace: 'nowrap',
|
130
|
+
}}
|
131
|
+
>
|
132
|
+
{formatTime(message.time)}
|
133
|
+
</Text>
|
134
|
+
</Text>
|
135
|
+
</Flex>
|
136
|
+
<Text
|
137
|
+
variant="sm"
|
138
|
+
css={{
|
139
|
+
w: '100%',
|
140
|
+
mt: '$2',
|
141
|
+
wordBreak: 'break-word',
|
142
|
+
whiteSpace: 'pre-wrap',
|
143
|
+
userSelect: 'all',
|
144
|
+
color: '$on_surface_high',
|
145
|
+
}}
|
146
|
+
>
|
147
|
+
<AnnotisedMessage message={message.message} />
|
148
|
+
</Text>
|
149
|
+
</Box>
|
150
|
+
))
|
151
|
+
)}
|
152
|
+
<div id="marker" style={{ height: filteredMessages.length ? '1px' : 0 }} />
|
153
|
+
</Box>
|
154
|
+
{canSendChatMessages && (
|
155
|
+
<Box css={{ bg: '$surface_dim' }}>
|
156
|
+
<Flex css={{ px: '$4', pb: '3px', gap: '$2', alignItems: 'center' }}>
|
157
|
+
<Text variant="caption">To:</Text>
|
158
|
+
<Flex css={{ bg: '$primary_bright', color: '$on_primary_high', r: '$2' }}>
|
159
|
+
<select
|
160
|
+
id="selector"
|
161
|
+
style={{
|
162
|
+
background: 'inherit',
|
163
|
+
color: 'inherit',
|
164
|
+
border: 'none',
|
165
|
+
outline: 'none',
|
166
|
+
borderRadius: '4px',
|
167
|
+
padding: '0 2px',
|
168
|
+
}}
|
169
|
+
defaultValue={elements.chat?.public_chat_enabled ? 'Everyone' : elements.chat?.roles_whitelist?.[0]}
|
170
|
+
>
|
171
|
+
{elements.chat?.roles_whitelist?.map(role => (
|
172
|
+
<option key={role} value={role}>
|
173
|
+
{role}
|
174
|
+
</option>
|
175
|
+
))}
|
176
|
+
{elements.chat?.public_chat_enabled ? <option value="Everyone">Everyone</option> : ''}
|
177
|
+
</select>
|
178
|
+
</Flex>
|
179
|
+
</Flex>
|
180
|
+
<Flex
|
181
|
+
align="center"
|
182
|
+
css={{
|
183
|
+
bg: '$surface_default',
|
184
|
+
minHeight: '$16',
|
185
|
+
width: '100%',
|
186
|
+
py: '$6',
|
187
|
+
pl: '$4',
|
188
|
+
boxSizing: 'border-box',
|
189
|
+
gap: '$2',
|
190
|
+
r: '$2',
|
191
|
+
}}
|
192
|
+
>
|
193
|
+
<TextArea
|
194
|
+
id="chat-input"
|
195
|
+
maxLength={CHAT_MESSAGE_LIMIT}
|
196
|
+
style={{ border: 'none', resize: 'none' }}
|
197
|
+
css={{
|
198
|
+
w: '100%',
|
199
|
+
c: '$on_surface_high',
|
200
|
+
padding: '0.25rem !important',
|
201
|
+
}}
|
202
|
+
placeholder={message_placeholder}
|
203
|
+
required
|
204
|
+
autoComplete="off"
|
205
|
+
aria-autocomplete="none"
|
206
|
+
/>
|
207
|
+
|
208
|
+
<IconButton
|
209
|
+
id="send-btn"
|
210
|
+
css={{
|
211
|
+
ml: 'auto',
|
212
|
+
height: 'max-content',
|
213
|
+
mr: '$4',
|
214
|
+
'&:hover': { c: '$on_surface_medium' },
|
215
|
+
}}
|
216
|
+
data-testid="send_msg_btn"
|
217
|
+
>
|
218
|
+
<SendIcon />
|
219
|
+
</IconButton>
|
220
|
+
</Flex>
|
221
|
+
</Box>
|
222
|
+
)}
|
223
|
+
</div>
|
224
|
+
);
|
225
|
+
};
|
@@ -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.overflowX = 'hidden';
|
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);
|