@100mslive/roomkit-react 0.1.6-alpha.0 → 0.1.6-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-PY2FKWX3.js → HLSView-HNVYG5VE.js} +208 -118
- package/dist/HLSView-HNVYG5VE.js.map +7 -0
- package/dist/Prebuilt/AppContext.d.ts +1 -1
- package/dist/Prebuilt/components/Chat/ChatFooter.d.ts +7 -0
- package/dist/Prebuilt/components/Connection/ConnectionIndicator.d.ts +6 -0
- package/dist/Prebuilt/components/Connection/TileConnection.d.ts +10 -0
- package/dist/Prebuilt/components/Footer/ChatToggle.d.ts +4 -0
- package/dist/Prebuilt/components/Footer/RoleAccordion.d.ts +14 -0
- package/dist/Prebuilt/components/Footer/RoleOptions.d.ts +6 -0
- package/dist/Prebuilt/components/Header/StreamActions.d.ts +11 -0
- package/dist/Prebuilt/components/Leave/DesktopLeaveRoom.d.ts +4 -3
- package/dist/Prebuilt/components/Leave/EndSessionContent.d.ts +4 -3
- package/dist/Prebuilt/components/Leave/LeaveCard.d.ts +1 -2
- package/dist/Prebuilt/components/Leave/LeaveSessionContent.d.ts +3 -1
- package/dist/Prebuilt/components/Leave/MwebLeaveRoom.d.ts +4 -3
- package/dist/Prebuilt/components/MoreSettings/SplitComponents/MwebOptions.d.ts +6 -0
- package/dist/Prebuilt/components/Preview/PreviewContainer.d.ts +3 -0
- package/dist/Prebuilt/components/Preview/PreviewJoin.d.ts +16 -0
- package/dist/Prebuilt/components/RoleChangeRequestModal.d.ts +2 -0
- package/dist/Prebuilt/components/SecondaryTiles.d.ts +1 -1
- package/dist/Prebuilt/components/VideoLayouts/EqualProminence.d.ts +1 -1
- package/dist/Prebuilt/components/VideoLayouts/Grid.d.ts +1 -0
- package/dist/Prebuilt/components/VideoLayouts/GridLayout.d.ts +5 -3
- package/dist/Prebuilt/components/VideoLayouts/ProminenceLayout.d.ts +6 -3
- package/dist/Prebuilt/components/VideoLayouts/RoleProminence.d.ts +1 -1
- package/dist/Prebuilt/components/VideoLayouts/ScreenshareLayout.d.ts +1 -1
- package/dist/Prebuilt/components/VideoLayouts/interface.d.ts +1 -0
- package/dist/Prebuilt/components/hooks/useAutoStartStreaming.d.ts +1 -0
- package/dist/Prebuilt/components/hooks/useRedirectToLeave.d.ts +3 -0
- package/dist/Prebuilt/components/hooks/useTileLayout.d.ts +2 -1
- package/dist/Prebuilt/components/hooks/useVideoTileLayout.d.ts +2 -0
- package/dist/Prebuilt/layouts/SidePane.d.ts +4 -1
- package/dist/Prebuilt/layouts/VideoStreamingSection.d.ts +2 -1
- package/dist/{VirtualBackground-AYDHYLIZ.js → VirtualBackground-UM2FOUHQ.js} +3 -3
- package/dist/{chunk-E2M2ZSOL.js → chunk-364HP22I.js} +2 -2
- package/dist/{chunk-RXTHJUMZ.js → chunk-LYSAET4G.js} +946 -390
- package/dist/chunk-LYSAET4G.js.map +7 -0
- package/dist/{chunk-GQD2AGWW.js → chunk-POE7H4IE.js} +12 -2
- package/dist/{chunk-GQD2AGWW.js.map → chunk-POE7H4IE.js.map} +2 -2
- package/dist/{conference-V2XZGTKU.js → conference-UWLJHMB2.js} +1116 -1316
- package/dist/conference-UWLJHMB2.js.map +7 -0
- package/dist/index.cjs.js +6080 -5631
- package/dist/index.cjs.js.map +4 -4
- package/dist/index.js +2 -2
- package/dist/meta.cjs.json +741 -493
- package/dist/meta.esbuild.json +782 -529
- package/package.json +8 -7
- package/src/Prebuilt/App.tsx +10 -21
- package/src/Prebuilt/AppContext.tsx +1 -1
- package/src/Prebuilt/IconButton.jsx +10 -0
- package/src/Prebuilt/common/PeersSorter.ts +1 -1
- package/src/Prebuilt/common/constants.js +1 -2
- package/src/Prebuilt/common/utils.js +1 -1
- package/src/Prebuilt/components/AppData/AppData.jsx +8 -2
- package/src/Prebuilt/components/AppData/useUISettings.js +6 -6
- package/src/Prebuilt/components/AudioVideoToggle.jsx +8 -6
- package/src/Prebuilt/components/Chat/Chat.jsx +23 -6
- package/src/Prebuilt/components/Chat/ChatBody.jsx +20 -21
- package/src/Prebuilt/components/Chat/{ChatFooter.jsx → ChatFooter.tsx} +38 -13
- package/src/Prebuilt/components/Chat/ChatParticipantHeader.jsx +38 -27
- package/src/Prebuilt/components/Chat/useEmojiPickerStyles.js +5 -4
- package/src/Prebuilt/components/Connection/{ConnectionIndicator.jsx → ConnectionIndicator.tsx} +12 -4
- package/src/Prebuilt/components/Connection/{TileConnection.jsx → TileConnection.tsx} +20 -6
- package/src/Prebuilt/components/EmojiReaction.jsx +2 -6
- package/src/Prebuilt/components/Footer/{ChatToggle.jsx → ChatToggle.tsx} +13 -3
- package/src/Prebuilt/components/Footer/Footer.tsx +15 -6
- package/src/Prebuilt/components/Footer/ParticipantList.jsx +15 -47
- package/src/Prebuilt/components/Footer/{RoleAccordion.jsx → RoleAccordion.tsx} +33 -17
- package/src/Prebuilt/components/Footer/RoleOptions.tsx +155 -0
- package/src/Prebuilt/components/FullPageProgress.jsx +3 -3
- package/src/Prebuilt/components/HMSVideo/Controls.jsx +1 -0
- package/src/Prebuilt/components/HMSVideo/HLSQualitySelector.jsx +39 -17
- package/src/Prebuilt/components/HMSVideo/HMSVideo.jsx +2 -2
- package/src/Prebuilt/components/HMSVideo/VideoProgress.jsx +5 -6
- package/src/Prebuilt/components/HMSVideo/VolumeControl.jsx +1 -1
- package/src/Prebuilt/components/Header/{StreamActions.jsx → StreamActions.tsx} +23 -9
- package/src/Prebuilt/components/Header/common.jsx +5 -2
- package/src/Prebuilt/components/IconButtonWithOptions/IconButtonWithOptions.jsx +6 -1
- package/src/Prebuilt/components/InsetTile.tsx +14 -8
- package/src/Prebuilt/components/Leave/DesktopLeaveRoom.tsx +21 -11
- package/src/Prebuilt/components/Leave/EndSessionContent.tsx +2 -5
- package/src/Prebuilt/components/Leave/LeaveCard.tsx +1 -3
- package/src/Prebuilt/components/Leave/LeaveRoom.tsx +28 -25
- package/src/Prebuilt/components/Leave/LeaveSessionContent.tsx +8 -2
- package/src/Prebuilt/components/Leave/MwebLeaveRoom.tsx +8 -8
- package/src/Prebuilt/components/MoreSettings/ChangeNameContent.jsx +4 -0
- package/src/Prebuilt/components/MoreSettings/MoreSettings.tsx +1 -1
- package/src/Prebuilt/components/MoreSettings/SplitComponents/DesktopOptions.tsx +9 -23
- package/src/Prebuilt/components/MoreSettings/SplitComponents/{MwebOptions.jsx → MwebOptions.tsx} +88 -27
- package/src/Prebuilt/components/Notifications/Notifications.jsx +30 -21
- package/src/Prebuilt/components/Notifications/ReconnectNotifications.jsx +5 -11
- package/src/Prebuilt/components/Pagination.tsx +14 -12
- package/src/Prebuilt/components/Preview/{PreviewContainer.jsx → PreviewContainer.tsx} +11 -2
- package/src/Prebuilt/components/Preview/PreviewForm.tsx +6 -8
- package/src/Prebuilt/components/Preview/{PreviewJoin.jsx → PreviewJoin.tsx} +43 -19
- package/src/Prebuilt/components/{RoleChangeRequestModal.jsx → RoleChangeRequestModal.tsx} +32 -15
- package/src/Prebuilt/components/ScreenshareTile.jsx +6 -7
- package/src/Prebuilt/components/SecondaryTiles.tsx +12 -10
- package/src/Prebuilt/components/TileMenu/TileMenu.jsx +1 -1
- package/src/Prebuilt/components/TileMenu/TileMenuContent.jsx +14 -10
- package/src/Prebuilt/components/Toast/ToastConfig.jsx +5 -4
- package/src/Prebuilt/components/VideoLayouts/EqualProminence.tsx +13 -10
- package/src/Prebuilt/components/VideoLayouts/Grid.tsx +36 -34
- package/src/Prebuilt/components/VideoLayouts/GridLayout.tsx +33 -15
- package/src/Prebuilt/components/VideoLayouts/ProminenceLayout.tsx +45 -31
- package/src/Prebuilt/components/VideoLayouts/RoleProminence.tsx +12 -9
- package/src/Prebuilt/components/VideoLayouts/ScreenshareLayout.tsx +25 -9
- package/src/Prebuilt/components/VideoLayouts/interface.ts +1 -0
- package/src/Prebuilt/components/VideoTile.jsx +45 -53
- package/src/Prebuilt/components/conference.jsx +71 -74
- package/src/Prebuilt/components/hooks/useAutoStartStreaming.tsx +57 -0
- package/src/Prebuilt/components/hooks/useMetadata.jsx +12 -3
- package/src/Prebuilt/components/hooks/useRedirectToLeave.tsx +34 -0
- package/src/Prebuilt/components/hooks/useRoleProminencePeers.tsx +1 -1
- package/src/Prebuilt/components/hooks/useTileLayout.tsx +24 -18
- package/src/Prebuilt/components/hooks/useVideoTileLayout.ts +4 -0
- package/src/Prebuilt/layouts/EmbedView.jsx +1 -11
- package/src/Prebuilt/layouts/HLSView.jsx +152 -82
- package/src/Prebuilt/layouts/SidePane.tsx +15 -3
- package/src/Prebuilt/layouts/VideoStreamingSection.tsx +11 -47
- package/src/Prebuilt/plugins/FlyingEmoji.jsx +14 -2
- package/src/Prebuilt/services/FeatureFlags.jsx +0 -1
- package/src/VideoTile/StyledVideoTile.tsx +1 -0
- package/dist/HLSView-PY2FKWX3.js.map +0 -7
- package/dist/chunk-RXTHJUMZ.js.map +0 -7
- package/dist/conference-V2XZGTKU.js.map +0 -7
- package/src/Prebuilt/components/AudioLevel/BeamSpeakerLabelsLogging.jsx +0 -16
- package/src/Prebuilt/components/VideoList.jsx +0 -73
- /package/dist/{VirtualBackground-AYDHYLIZ.js.map → VirtualBackground-UM2FOUHQ.js.map} +0 -0
- /package/dist/{chunk-E2M2ZSOL.js.map → chunk-364HP22I.js.map} +0 -0
|
@@ -1,13 +1,17 @@
|
|
|
1
|
-
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
|
1
|
+
import React, { ReactNode, useCallback, useEffect, useRef, useState } from 'react';
|
|
2
2
|
import { useMedia } from 'react-use';
|
|
3
3
|
import data from '@emoji-mart/data';
|
|
4
4
|
import Picker from '@emoji-mart/react';
|
|
5
5
|
import { useHMSActions } from '@100mslive/react-sdk';
|
|
6
6
|
import { EmojiIcon, SendIcon } from '@100mslive/react-icons';
|
|
7
|
-
import { Box, config as cssConfig, Flex, IconButton as BaseIconButton, Popover, styled } from '
|
|
7
|
+
import { Box, config as cssConfig, Flex, IconButton as BaseIconButton, Popover, styled } from '../../..';
|
|
8
|
+
// @ts-ignore
|
|
8
9
|
import { ToastManager } from '../Toast/ToastManager';
|
|
10
|
+
import { useRoomLayoutConferencingScreen } from '../../provider/roomLayoutProvider/hooks/useRoomLayoutScreen';
|
|
9
11
|
// import { ChatSelectorContainer } from './ChatSelectorContainer';
|
|
12
|
+
// @ts-ignore
|
|
10
13
|
import { useChatDraftMessage } from '../AppData/useChatState';
|
|
14
|
+
// @ts-ignore
|
|
11
15
|
import { useEmojiPickerStyles } from './useEmojiPickerStyles';
|
|
12
16
|
|
|
13
17
|
const TextArea = styled('textarea', {
|
|
@@ -28,7 +32,7 @@ const TextArea = styled('textarea', {
|
|
|
28
32
|
},
|
|
29
33
|
});
|
|
30
34
|
|
|
31
|
-
function EmojiPicker({ onSelect }) {
|
|
35
|
+
function EmojiPicker({ onSelect }: { onSelect: (emoji: any) => void }) {
|
|
32
36
|
const [showEmoji, setShowEmoji] = useState(false);
|
|
33
37
|
const ref = useEmojiPickerStyles(showEmoji);
|
|
34
38
|
return (
|
|
@@ -62,14 +66,26 @@ function EmojiPicker({ onSelect }) {
|
|
|
62
66
|
);
|
|
63
67
|
}
|
|
64
68
|
|
|
65
|
-
export const ChatFooter = ({
|
|
69
|
+
export const ChatFooter = ({
|
|
70
|
+
role,
|
|
71
|
+
peerId,
|
|
72
|
+
onSend,
|
|
73
|
+
children /* onSelect, selection, screenType */,
|
|
74
|
+
}: {
|
|
75
|
+
role: any;
|
|
76
|
+
peerId: string;
|
|
77
|
+
onSend: any;
|
|
78
|
+
children: ReactNode;
|
|
79
|
+
}) => {
|
|
66
80
|
const hmsActions = useHMSActions();
|
|
67
|
-
const inputRef = useRef(null);
|
|
81
|
+
const inputRef = useRef<HTMLTextAreaElement>(null);
|
|
68
82
|
const [draftMessage, setDraftMessage] = useChatDraftMessage();
|
|
69
83
|
const isMobile = useMedia(cssConfig.media.md);
|
|
84
|
+
const { elements } = useRoomLayoutConferencingScreen();
|
|
85
|
+
const isOverlayChat = elements?.chat?.is_overlay;
|
|
70
86
|
|
|
71
87
|
const sendMessage = useCallback(async () => {
|
|
72
|
-
const message = inputRef
|
|
88
|
+
const message = inputRef?.current?.value;
|
|
73
89
|
if (!message || !message.trim().length) {
|
|
74
90
|
return;
|
|
75
91
|
}
|
|
@@ -86,7 +102,8 @@ export const ChatFooter = ({ role, peerId, onSend, children /* onSelect, selecti
|
|
|
86
102
|
onSend();
|
|
87
103
|
}, 0);
|
|
88
104
|
} catch (error) {
|
|
89
|
-
|
|
105
|
+
const err = error as Error;
|
|
106
|
+
ToastManager.addToast({ title: err.message });
|
|
90
107
|
}
|
|
91
108
|
}, [role, peerId, hmsActions, onSend]);
|
|
92
109
|
|
|
@@ -113,7 +130,7 @@ export const ChatFooter = ({ role, peerId, onSend, children /* onSelect, selecti
|
|
|
113
130
|
<Flex
|
|
114
131
|
align="center"
|
|
115
132
|
css={{
|
|
116
|
-
bg: isMobile ? '$surface_dim' : '$surface_default',
|
|
133
|
+
bg: isOverlayChat && isMobile ? '$surface_dim' : '$surface_default',
|
|
117
134
|
minHeight: '$16',
|
|
118
135
|
maxHeight: '$24',
|
|
119
136
|
position: 'relative',
|
|
@@ -130,9 +147,15 @@ export const ChatFooter = ({ role, peerId, onSend, children /* onSelect, selecti
|
|
|
130
147
|
>
|
|
131
148
|
{children}
|
|
132
149
|
<TextArea
|
|
150
|
+
css={{
|
|
151
|
+
c: '$on_surface_high',
|
|
152
|
+
'&:valid ~ .send-msg': { color: '$on_surface_high' },
|
|
153
|
+
'& ~ .send-msg': { color: '$on_surface_low' },
|
|
154
|
+
}}
|
|
133
155
|
placeholder="Send a message...."
|
|
134
156
|
ref={inputRef}
|
|
135
|
-
|
|
157
|
+
required
|
|
158
|
+
autoFocus={!isMobile}
|
|
136
159
|
onKeyPress={async event => {
|
|
137
160
|
if (event.key === 'Enter') {
|
|
138
161
|
if (!event.shiftKey) {
|
|
@@ -149,19 +172,21 @@ export const ChatFooter = ({ role, peerId, onSend, children /* onSelect, selecti
|
|
|
149
172
|
/>
|
|
150
173
|
{!isMobile ? (
|
|
151
174
|
<EmojiPicker
|
|
152
|
-
onSelect={emoji => {
|
|
153
|
-
inputRef.current
|
|
175
|
+
onSelect={(emoji: any) => {
|
|
176
|
+
if (inputRef.current) {
|
|
177
|
+
inputRef.current.value += ` ${emoji.native} `;
|
|
178
|
+
}
|
|
154
179
|
}}
|
|
155
180
|
/>
|
|
156
181
|
) : null}
|
|
157
182
|
<BaseIconButton
|
|
183
|
+
className="send-msg"
|
|
158
184
|
onClick={sendMessage}
|
|
159
185
|
css={{
|
|
160
186
|
ml: 'auto',
|
|
161
187
|
height: 'max-content',
|
|
162
188
|
mr: '$4',
|
|
163
|
-
|
|
164
|
-
'&:hover': { c: '$on_surface_high' },
|
|
189
|
+
'&:hover': { c: isMobile ? '' : '$on_surface_medium' },
|
|
165
190
|
}}
|
|
166
191
|
data-testid="send_msg_btn"
|
|
167
192
|
>
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import React, { useState } from 'react';
|
|
2
2
|
import { selectPeerCount, useHMSStore } from '@100mslive/react-sdk';
|
|
3
3
|
import { CrossIcon } from '@100mslive/react-icons';
|
|
4
|
-
import { Flex, IconButton, Tabs } from '../../..';
|
|
4
|
+
import { Flex, IconButton, Tabs, Text } from '../../..';
|
|
5
|
+
import { useRoomLayoutConferencingScreen } from '../../provider/roomLayoutProvider/hooks/useRoomLayoutScreen';
|
|
5
6
|
import { useSidepaneToggle } from '../AppData/useSidepane';
|
|
6
7
|
import { SIDE_PANE_OPTIONS } from '../../common/constants';
|
|
7
8
|
|
|
@@ -19,6 +20,10 @@ export const ChatParticipantHeader = React.memo(({ activeTabValue = SIDE_PANE_OP
|
|
|
19
20
|
const toggleParticipants = useSidepaneToggle(SIDE_PANE_OPTIONS.PARTICIPANTS);
|
|
20
21
|
const [activeTab, setActiveTab] = useState(activeTabValue);
|
|
21
22
|
const peerCount = useHMSStore(selectPeerCount);
|
|
23
|
+
const { elements } = useRoomLayoutConferencingScreen();
|
|
24
|
+
const showChat = !!elements?.chat;
|
|
25
|
+
const showParticipants = !!elements?.participant_list;
|
|
26
|
+
const hideTabs = !(showChat && showParticipants);
|
|
22
27
|
|
|
23
28
|
return (
|
|
24
29
|
<Flex
|
|
@@ -28,32 +33,38 @@ export const ChatParticipantHeader = React.memo(({ activeTabValue = SIDE_PANE_OP
|
|
|
28
33
|
h: '$16',
|
|
29
34
|
}}
|
|
30
35
|
>
|
|
31
|
-
|
|
32
|
-
<
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
36
|
+
{hideTabs ? (
|
|
37
|
+
<Text variant="sm" css={{ fontWeight: '$semiBold', c: '$on_surface_high' }}>
|
|
38
|
+
{showChat ? 'Chat' : `Participants (${peerCount})`}
|
|
39
|
+
</Text>
|
|
40
|
+
) : (
|
|
41
|
+
<Flex css={{ w: '100%', bg: '$surface_default', borderRadius: '$2' }}>
|
|
42
|
+
<Tabs.Root value={activeTab} onValueChange={setActiveTab} css={{ w: '100%' }}>
|
|
43
|
+
<Tabs.List css={{ w: '100%', p: '$2' }}>
|
|
44
|
+
<Tabs.Trigger
|
|
45
|
+
value={SIDE_PANE_OPTIONS.CHAT}
|
|
46
|
+
onClick={toggleChat}
|
|
47
|
+
css={{
|
|
48
|
+
...tabTriggerCSS,
|
|
49
|
+
color: activeTab !== SIDE_PANE_OPTIONS.CHAT ? '$on_surface_low' : '$on_surface_high',
|
|
50
|
+
}}
|
|
51
|
+
>
|
|
52
|
+
Chat
|
|
53
|
+
</Tabs.Trigger>
|
|
54
|
+
<Tabs.Trigger
|
|
55
|
+
value={SIDE_PANE_OPTIONS.PARTICIPANTS}
|
|
56
|
+
onClick={toggleParticipants}
|
|
57
|
+
css={{
|
|
58
|
+
...tabTriggerCSS,
|
|
59
|
+
color: activeTab !== SIDE_PANE_OPTIONS.PARTICIPANTS ? '$on_surface_low' : '$on_surface_high',
|
|
60
|
+
}}
|
|
61
|
+
>
|
|
62
|
+
Participants ({peerCount})
|
|
63
|
+
</Tabs.Trigger>
|
|
64
|
+
</Tabs.List>
|
|
65
|
+
</Tabs.Root>
|
|
66
|
+
</Flex>
|
|
67
|
+
)}
|
|
57
68
|
<IconButton
|
|
58
69
|
css={{ ml: 'auto' }}
|
|
59
70
|
onClick={e => {
|
|
@@ -9,16 +9,17 @@ export const useEmojiPickerStyles = showing => {
|
|
|
9
9
|
const style = document.createElement('style');
|
|
10
10
|
style.textContent = `
|
|
11
11
|
#root {
|
|
12
|
-
--em-rgb-color: var(--hms-ui-colors-
|
|
12
|
+
--em-rgb-color: var(--hms-ui-colors-on_surface_high);
|
|
13
13
|
--em-rgb-input: var(--hms-ui-colors-on_primary_high);
|
|
14
|
-
--em-color-border: var(--hms-ui-colors-
|
|
15
|
-
--color-b: var(--hms-ui-colors-
|
|
14
|
+
--em-color-border: var(--hms-ui-colors-surface_bright);
|
|
15
|
+
--color-b: var(--hms-ui-colors-on_surface_high);
|
|
16
16
|
--rgb-background: transparent;
|
|
17
|
-
color: var(--hms-ui-colors-
|
|
17
|
+
color: var(--hms-ui-colors-on_surface_high);
|
|
18
18
|
font-family: var(--hms-ui-fonts-sans);
|
|
19
19
|
}
|
|
20
20
|
.sticky {
|
|
21
21
|
background-color: var(--hms-ui-colors-surface_bright);
|
|
22
|
+
margin-top: 0.5rem;
|
|
22
23
|
}
|
|
23
24
|
`;
|
|
24
25
|
root?.appendChild(style);
|
package/src/Prebuilt/components/Connection/{ConnectionIndicator.jsx → ConnectionIndicator.tsx}
RENAMED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { selectConnectionQualityByPeerID, useHMSStore } from '@100mslive/react-sdk';
|
|
3
3
|
import { PoorConnectivityIcon } from '@100mslive/react-icons';
|
|
4
|
-
import { styled, Tooltip, useTheme } from '
|
|
4
|
+
import { styled, Tooltip, useTheme } from '../../..';
|
|
5
|
+
// @ts-ignore
|
|
5
6
|
import { getColor, getTooltipText } from './connectionQualityUtils';
|
|
6
7
|
|
|
7
8
|
const Wrapper = styled('span', {
|
|
@@ -10,7 +11,6 @@ const Wrapper = styled('span', {
|
|
|
10
11
|
display: 'flex',
|
|
11
12
|
alignItems: 'center',
|
|
12
13
|
justifyContent: 'center',
|
|
13
|
-
backgroundColor: '$background_dim',
|
|
14
14
|
borderRadius: '$round',
|
|
15
15
|
variants: {
|
|
16
16
|
isTile: {
|
|
@@ -22,7 +22,15 @@ const Wrapper = styled('span', {
|
|
|
22
22
|
},
|
|
23
23
|
});
|
|
24
24
|
|
|
25
|
-
export const ConnectionIndicator = ({
|
|
25
|
+
export const ConnectionIndicator = ({
|
|
26
|
+
peerId,
|
|
27
|
+
isTile = false,
|
|
28
|
+
hideBg = false,
|
|
29
|
+
}: {
|
|
30
|
+
peerId: string;
|
|
31
|
+
isTile?: boolean;
|
|
32
|
+
hideBg?: boolean;
|
|
33
|
+
}) => {
|
|
26
34
|
const downlinkQuality = useHMSStore(selectConnectionQualityByPeerID(peerId))?.downlinkQuality;
|
|
27
35
|
const { theme } = useTheme();
|
|
28
36
|
const defaultColor = theme.colors.surface_brighter;
|
|
@@ -41,7 +49,7 @@ export const ConnectionIndicator = ({ peerId, isTile = false }) => {
|
|
|
41
49
|
const size = isTile ? 12 : 16;
|
|
42
50
|
return (
|
|
43
51
|
<Tooltip title={getTooltipText(downlinkQuality)}>
|
|
44
|
-
<Wrapper isTile={isTile} data-testid="tile_network">
|
|
52
|
+
<Wrapper isTile={isTile} data-testid="tile_network" css={{ backgroundColor: hideBg ? '' : '$surface_bright' }}>
|
|
45
53
|
<svg
|
|
46
54
|
width={size}
|
|
47
55
|
height={size}
|
|
@@ -1,9 +1,23 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { PinIcon, SpotlightIcon } from '@100mslive/react-icons';
|
|
3
|
-
import { Flex, styled, Text, textEllipsis } from '
|
|
3
|
+
import { Flex, styled, Text, textEllipsis } from '../../..';
|
|
4
4
|
import { ConnectionIndicator } from './ConnectionIndicator';
|
|
5
5
|
|
|
6
|
-
const TileConnection = ({
|
|
6
|
+
const TileConnection = ({
|
|
7
|
+
name,
|
|
8
|
+
peerId,
|
|
9
|
+
hideLabel,
|
|
10
|
+
width,
|
|
11
|
+
spotlighted,
|
|
12
|
+
pinned,
|
|
13
|
+
}: {
|
|
14
|
+
name: string;
|
|
15
|
+
peerId: string;
|
|
16
|
+
hideLabel: boolean;
|
|
17
|
+
width?: number;
|
|
18
|
+
spotlighted?: boolean;
|
|
19
|
+
pinned?: boolean;
|
|
20
|
+
}) => {
|
|
7
21
|
return (
|
|
8
22
|
<Wrapper>
|
|
9
23
|
{!hideLabel ? (
|
|
@@ -11,26 +25,26 @@ const TileConnection = ({ name, peerId, hideLabel, width, spotlighted, pinned })
|
|
|
11
25
|
<Flex align="center">
|
|
12
26
|
{pinned && (
|
|
13
27
|
<IconWrapper>
|
|
14
|
-
<PinIcon width="15" height="15"
|
|
28
|
+
<PinIcon width="15" height="15" />
|
|
15
29
|
</IconWrapper>
|
|
16
30
|
)}
|
|
17
31
|
{spotlighted && (
|
|
18
32
|
<IconWrapper>
|
|
19
|
-
<SpotlightIcon width="15" height="15"
|
|
33
|
+
<SpotlightIcon width="15" height="15" />
|
|
20
34
|
</IconWrapper>
|
|
21
35
|
)}
|
|
22
36
|
<Text
|
|
23
37
|
css={{
|
|
24
38
|
c: '$on_surface_high',
|
|
25
39
|
verticalAlign: 'baseline',
|
|
26
|
-
...textEllipsis(width - 60),
|
|
40
|
+
...(width ? textEllipsis(width - 60) : {}),
|
|
27
41
|
}}
|
|
28
42
|
variant="xs"
|
|
29
43
|
>
|
|
30
44
|
{name}
|
|
31
45
|
</Text>
|
|
32
46
|
</Flex>
|
|
33
|
-
<ConnectionIndicator isTile peerId={peerId} />
|
|
47
|
+
<ConnectionIndicator isTile peerId={peerId} hideBg />
|
|
34
48
|
</>
|
|
35
49
|
) : null}
|
|
36
50
|
</Wrapper>
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React, { Fragment,
|
|
1
|
+
import React, { Fragment, useState } from 'react';
|
|
2
2
|
import { useMedia } from 'react-use';
|
|
3
3
|
import data from '@emoji-mart/data/sets/14/apple.json';
|
|
4
4
|
import { init } from 'emoji-mart';
|
|
@@ -34,13 +34,8 @@ export const EmojiReaction = () => {
|
|
|
34
34
|
// const { isStreamingOn } = useRecordingStreaming();
|
|
35
35
|
const isMobile = useMedia(cssConfig.media.md);
|
|
36
36
|
|
|
37
|
-
const onEmojiEvent = useCallback(data => {
|
|
38
|
-
window.showFlyingEmoji(data?.emojiId, data?.senderId);
|
|
39
|
-
}, []);
|
|
40
|
-
|
|
41
37
|
const { sendEvent } = useCustomEvent({
|
|
42
38
|
type: EMOJI_REACTION_TYPE,
|
|
43
|
-
onEvent: onEmojiEvent,
|
|
44
39
|
});
|
|
45
40
|
|
|
46
41
|
const sendReaction = async emojiId => {
|
|
@@ -51,6 +46,7 @@ export const EmojiReaction = () => {
|
|
|
51
46
|
};
|
|
52
47
|
// TODO: RT find a way to figure out hls-viewer roles
|
|
53
48
|
sendEvent(data, { roleNames: roles });
|
|
49
|
+
window.showFlyingEmoji?.({ emojiId, senderId: localPeerId });
|
|
54
50
|
/* if (isStreamingOn) {
|
|
55
51
|
try {
|
|
56
52
|
await hmsActions.sendHLSTimedMetadata([
|
|
@@ -1,16 +1,26 @@
|
|
|
1
|
-
import React from 'react';
|
|
1
|
+
import React, { useEffect } from 'react';
|
|
2
2
|
import { selectUnreadHMSMessagesCount, useHMSStore } from '@100mslive/react-sdk';
|
|
3
3
|
import { ChatIcon, ChatUnreadIcon } from '@100mslive/react-icons';
|
|
4
|
-
import { Tooltip } from '
|
|
4
|
+
import { Tooltip } from '../../..';
|
|
5
|
+
// @ts-ignore: No implicit Any
|
|
5
6
|
import IconButton from '../../IconButton';
|
|
7
|
+
// @ts-ignore: No implicit Any
|
|
6
8
|
import { useIsSidepaneTypeOpen, useSidepaneToggle } from '../AppData/useSidepane';
|
|
9
|
+
// @ts-ignore: No implicit Any
|
|
7
10
|
import { SIDE_PANE_OPTIONS } from '../../common/constants';
|
|
8
11
|
|
|
9
|
-
export const ChatToggle = () => {
|
|
12
|
+
export const ChatToggle = ({ openByDefault }: { openByDefault: boolean }) => {
|
|
10
13
|
const countUnreadMessages = useHMSStore(selectUnreadHMSMessagesCount);
|
|
11
14
|
const isChatOpen = useIsSidepaneTypeOpen(SIDE_PANE_OPTIONS.CHAT);
|
|
12
15
|
const toggleChat = useSidepaneToggle(SIDE_PANE_OPTIONS.CHAT);
|
|
13
16
|
|
|
17
|
+
useEffect(() => {
|
|
18
|
+
if (!isChatOpen && openByDefault) {
|
|
19
|
+
toggleChat();
|
|
20
|
+
}
|
|
21
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
22
|
+
}, [toggleChat, openByDefault]);
|
|
23
|
+
|
|
14
24
|
return (
|
|
15
25
|
<Tooltip key="chat" title={`${isChatOpen ? 'Close' : 'Open'} chat`}>
|
|
16
26
|
<IconButton onClick={toggleChat} active={!isChatOpen} data-testid="chat_btn">
|
|
@@ -1,10 +1,12 @@
|
|
|
1
|
-
import React from 'react';
|
|
1
|
+
import React, { Suspense } from 'react';
|
|
2
2
|
import { useMedia } from 'react-use';
|
|
3
3
|
import {
|
|
4
4
|
ConferencingScreen,
|
|
5
5
|
DefaultConferencingScreen_Elements,
|
|
6
6
|
HLSLiveStreamingScreen_Elements,
|
|
7
7
|
} from '@100mslive/types-prebuilt';
|
|
8
|
+
import { Chat_ChatState } from '@100mslive/types-prebuilt/elements/chat';
|
|
9
|
+
import { selectIsLocalVideoEnabled, useHMSStore } from '@100mslive/react-sdk';
|
|
8
10
|
import { config as cssConfig, Footer as AppFooter } from '../../..';
|
|
9
11
|
// @ts-ignore: No implicit Any
|
|
10
12
|
import { AudioVideoToggle } from '../AudioVideoToggle';
|
|
@@ -22,6 +24,8 @@ import { ScreenshareToggle } from '../ScreenShareToggle';
|
|
|
22
24
|
import { ChatToggle } from './ChatToggle';
|
|
23
25
|
// @ts-ignore: No implicit Any
|
|
24
26
|
import { ParticipantCount } from './ParticipantList';
|
|
27
|
+
// @ts-ignore: No implicit Any
|
|
28
|
+
const VirtualBackground = React.lazy(() => import('../../plugins/VirtualBackground/VirtualBackground'));
|
|
25
29
|
|
|
26
30
|
export const Footer = ({
|
|
27
31
|
screenType,
|
|
@@ -31,6 +35,9 @@ export const Footer = ({
|
|
|
31
35
|
elements: DefaultConferencingScreen_Elements | HLSLiveStreamingScreen_Elements;
|
|
32
36
|
}) => {
|
|
33
37
|
const isMobile = useMedia(cssConfig.media.md);
|
|
38
|
+
const isOverlayChat = !!elements?.chat?.is_overlay;
|
|
39
|
+
const openByDefault = elements?.chat?.initial_state === Chat_ChatState.CHAT_STATE_OPEN;
|
|
40
|
+
const isVideoOn = useHMSStore(selectIsLocalVideoEnabled);
|
|
34
41
|
|
|
35
42
|
return (
|
|
36
43
|
<AppFooter.Root
|
|
@@ -40,7 +47,8 @@ export const Footer = ({
|
|
|
40
47
|
justifyContent: 'center',
|
|
41
48
|
gap: '$10',
|
|
42
49
|
position: 'relative',
|
|
43
|
-
|
|
50
|
+
// To prevent it from showing over the sidepane if chat type is not overlay
|
|
51
|
+
zIndex: isOverlayChat ? 20 : 1,
|
|
44
52
|
},
|
|
45
53
|
}}
|
|
46
54
|
>
|
|
@@ -55,6 +63,7 @@ export const Footer = ({
|
|
|
55
63
|
>
|
|
56
64
|
{isMobile ? <LeaveRoom screenType={screenType} /> : null}
|
|
57
65
|
<AudioVideoToggle />
|
|
66
|
+
{isMobile ? null : <Suspense fallback={<></>}>{isVideoOn ? <VirtualBackground /> : null}</Suspense>}
|
|
58
67
|
</AppFooter.Left>
|
|
59
68
|
<AppFooter.Center
|
|
60
69
|
css={{
|
|
@@ -67,21 +76,21 @@ export const Footer = ({
|
|
|
67
76
|
{isMobile ? (
|
|
68
77
|
<>
|
|
69
78
|
{screenType === 'hls_live_streaming' ? <RaiseHand /> : null}
|
|
70
|
-
{elements?.chat && <ChatToggle />}
|
|
79
|
+
{elements?.chat && <ChatToggle openByDefault={openByDefault} />}
|
|
71
80
|
<MoreSettings elements={elements} screenType={screenType} />
|
|
72
81
|
</>
|
|
73
82
|
) : (
|
|
74
83
|
<>
|
|
75
84
|
<ScreenshareToggle />
|
|
76
|
-
|
|
85
|
+
<RaiseHand />
|
|
77
86
|
{elements?.emoji_reactions && <EmojiReaction />}
|
|
78
87
|
<LeaveRoom screenType={screenType} />
|
|
79
88
|
</>
|
|
80
89
|
)}
|
|
81
90
|
</AppFooter.Center>
|
|
82
91
|
<AppFooter.Right>
|
|
83
|
-
{elements?.chat && <ChatToggle />}
|
|
84
|
-
<ParticipantCount />
|
|
92
|
+
{!isMobile && elements?.chat && <ChatToggle openByDefault={openByDefault} />}
|
|
93
|
+
{elements?.participant_list && <ParticipantCount />}
|
|
85
94
|
<MoreSettings elements={elements} screenType={screenType} />
|
|
86
95
|
</AppFooter.Right>
|
|
87
96
|
</AppFooter.Root>
|
|
@@ -21,12 +21,11 @@ import {
|
|
|
21
21
|
} from '@100mslive/react-icons';
|
|
22
22
|
import { Box, config as cssConfig, Dropdown, Flex, Input, Text, textEllipsis } from '../../..';
|
|
23
23
|
import IconButton from '../../IconButton';
|
|
24
|
-
import { useRoomLayout } from '../../provider/roomLayoutProvider';
|
|
25
24
|
import { ChatParticipantHeader } from '../Chat/ChatParticipantHeader';
|
|
26
25
|
import { ConnectionIndicator } from '../Connection/ConnectionIndicator';
|
|
27
|
-
import { RoleChangeModal } from '../RoleChangeModal';
|
|
28
26
|
import { ToastManager } from '../Toast/ToastManager';
|
|
29
27
|
import { RoleAccordion } from './RoleAccordion';
|
|
28
|
+
import { useRoomLayoutConferencingScreen } from '../../provider/roomLayoutProvider/hooks/useRoomLayoutScreen';
|
|
30
29
|
import { useIsSidepaneTypeOpen, useSidepaneToggle } from '../AppData/useSidepane';
|
|
31
30
|
import { useParticipants } from '../../common/hooks';
|
|
32
31
|
import { isInternalRole } from '../../common/utils';
|
|
@@ -46,7 +45,6 @@ export const ParticipantList = () => {
|
|
|
46
45
|
peersOrderedByRoles[participant.roleName].push(participant);
|
|
47
46
|
});
|
|
48
47
|
|
|
49
|
-
const [selectedPeerId, setSelectedPeerId] = useState(null);
|
|
50
48
|
const onSearch = useCallback(value => {
|
|
51
49
|
setFilter(filterValue => {
|
|
52
50
|
if (!filterValue) {
|
|
@@ -75,16 +73,7 @@ export const ParticipantList = () => {
|
|
|
75
73
|
handRaisedList={handRaisedPeers}
|
|
76
74
|
isConnected={isConnected}
|
|
77
75
|
filter={filter}
|
|
78
|
-
setSelectedPeerId={setSelectedPeerId}
|
|
79
76
|
/>
|
|
80
|
-
{selectedPeerId && (
|
|
81
|
-
<RoleChangeModal
|
|
82
|
-
peerId={selectedPeerId}
|
|
83
|
-
onOpenChange={value => {
|
|
84
|
-
!value && setSelectedPeerId(null);
|
|
85
|
-
}}
|
|
86
|
-
/>
|
|
87
|
-
)}
|
|
88
77
|
</Flex>
|
|
89
78
|
</Fragment>
|
|
90
79
|
);
|
|
@@ -126,13 +115,7 @@ export const ParticipantCount = () => {
|
|
|
126
115
|
);
|
|
127
116
|
};
|
|
128
117
|
|
|
129
|
-
const VirtualizedParticipants = ({
|
|
130
|
-
peersOrderedByRoles = {},
|
|
131
|
-
isConnected,
|
|
132
|
-
setSelectedPeerId,
|
|
133
|
-
filter,
|
|
134
|
-
handRaisedList = [],
|
|
135
|
-
}) => {
|
|
118
|
+
const VirtualizedParticipants = ({ peersOrderedByRoles = {}, isConnected, filter, handRaisedList = [] }) => {
|
|
136
119
|
return (
|
|
137
120
|
<Flex
|
|
138
121
|
direction="column"
|
|
@@ -149,7 +132,6 @@ const VirtualizedParticipants = ({
|
|
|
149
132
|
roleName="Hand Raised"
|
|
150
133
|
filter={filter}
|
|
151
134
|
isConnected={isConnected}
|
|
152
|
-
setSelectedPeerId={setSelectedPeerId}
|
|
153
135
|
isHandRaisedAccordion
|
|
154
136
|
/>
|
|
155
137
|
{Object.keys(peersOrderedByRoles).map(role => (
|
|
@@ -158,7 +140,6 @@ const VirtualizedParticipants = ({
|
|
|
158
140
|
peerList={peersOrderedByRoles[role]}
|
|
159
141
|
roleName={role}
|
|
160
142
|
isConnected={isConnected}
|
|
161
|
-
setSelectedPeerId={setSelectedPeerId}
|
|
162
143
|
filter={filter}
|
|
163
144
|
/>
|
|
164
145
|
))}
|
|
@@ -166,7 +147,7 @@ const VirtualizedParticipants = ({
|
|
|
166
147
|
);
|
|
167
148
|
};
|
|
168
149
|
|
|
169
|
-
export const Participant = ({ peer, isConnected
|
|
150
|
+
export const Participant = ({ peer, isConnected }) => {
|
|
170
151
|
const localPeerId = useHMSStore(selectLocalPeerID);
|
|
171
152
|
return (
|
|
172
153
|
<Flex
|
|
@@ -186,14 +167,7 @@ export const Participant = ({ peer, isConnected, setSelectedPeerId }) => {
|
|
|
186
167
|
{peer.name} {localPeerId === peer.id ? '(You)' : ''}
|
|
187
168
|
</Text>
|
|
188
169
|
{isConnected ? (
|
|
189
|
-
<ParticipantActions
|
|
190
|
-
peerId={peer.id}
|
|
191
|
-
isLocal={peer.id === localPeerId}
|
|
192
|
-
role={peer.roleName}
|
|
193
|
-
onSettings={() => {
|
|
194
|
-
setSelectedPeerId(peer.id);
|
|
195
|
-
}}
|
|
196
|
-
/>
|
|
170
|
+
<ParticipantActions peerId={peer.id} isLocal={peer.id === localPeerId} role={peer.roleName} />
|
|
197
171
|
) : null}
|
|
198
172
|
</Flex>
|
|
199
173
|
);
|
|
@@ -202,7 +176,7 @@ export const Participant = ({ peer, isConnected, setSelectedPeerId }) => {
|
|
|
202
176
|
/**
|
|
203
177
|
* shows settings to change for a participant like changing their role
|
|
204
178
|
*/
|
|
205
|
-
const ParticipantActions = React.memo(({
|
|
179
|
+
const ParticipantActions = React.memo(({ peerId, role, isLocal }) => {
|
|
206
180
|
const isHandRaised = useHMSStore(selectPeerMetadata(peerId))?.isHandRaised;
|
|
207
181
|
const canChangeRole = useHMSStore(selectPermissions)?.changeRole;
|
|
208
182
|
const shouldShowMoreActions = canChangeRole;
|
|
@@ -214,7 +188,6 @@ const ParticipantActions = React.memo(({ onSettings, peerId, role, isLocal }) =>
|
|
|
214
188
|
css={{
|
|
215
189
|
flexShrink: 0,
|
|
216
190
|
gap: '$8',
|
|
217
|
-
mt: '$2',
|
|
218
191
|
}}
|
|
219
192
|
>
|
|
220
193
|
<ConnectionIndicator peerId={peerId} />
|
|
@@ -238,24 +211,26 @@ const ParticipantActions = React.memo(({ onSettings, peerId, role, isLocal }) =>
|
|
|
238
211
|
) : null}
|
|
239
212
|
|
|
240
213
|
{shouldShowMoreActions && !isInternalRole(role) && !isLocal ? (
|
|
241
|
-
<ParticipantMoreActions
|
|
214
|
+
<ParticipantMoreActions peerId={peerId} role={role} />
|
|
242
215
|
) : null}
|
|
243
216
|
</Flex>
|
|
244
217
|
);
|
|
245
218
|
});
|
|
246
219
|
|
|
247
|
-
const ParticipantMoreActions = ({
|
|
220
|
+
const ParticipantMoreActions = ({ peerId, role }) => {
|
|
248
221
|
const hmsActions = useHMSActions();
|
|
249
222
|
const { changeRole: canChangeRole, removeOthers: canRemoveOthers } = useHMSStore(selectPermissions);
|
|
250
|
-
const
|
|
223
|
+
const { elements } = useRoomLayoutConferencingScreen();
|
|
251
224
|
const {
|
|
252
225
|
bring_to_stage_label,
|
|
253
226
|
remove_from_stage_label,
|
|
254
227
|
on_stage_role,
|
|
255
228
|
off_stage_roles = [],
|
|
256
|
-
} =
|
|
257
|
-
const canBringToStage = off_stage_roles.includes(role);
|
|
229
|
+
} = elements.on_stage_exp || {};
|
|
258
230
|
const isInStage = role === on_stage_role;
|
|
231
|
+
const shouldShowStageRoleChange =
|
|
232
|
+
canChangeRole &&
|
|
233
|
+
((isInStage && remove_from_stage_label) || (off_stage_roles?.includes(role) && bring_to_stage_label));
|
|
259
234
|
const prevRole = useHMSStore(selectPeerMetadata(peerId))?.prevRole;
|
|
260
235
|
const localPeerId = useHMSStore(selectLocalPeerID);
|
|
261
236
|
const isLocal = localPeerId === peerId;
|
|
@@ -263,7 +238,7 @@ const ParticipantMoreActions = ({ onRoleChange, peerId, role }) => {
|
|
|
263
238
|
|
|
264
239
|
const handleStageAction = async () => {
|
|
265
240
|
if (isInStage) {
|
|
266
|
-
hmsActions.changeRoleOfPeer(peerId, prevRole
|
|
241
|
+
prevRole && hmsActions.changeRoleOfPeer(peerId, prevRole, true);
|
|
267
242
|
} else {
|
|
268
243
|
await hmsActions.changeRoleOfPeer(peerId, on_stage_role);
|
|
269
244
|
}
|
|
@@ -296,21 +271,14 @@ const ParticipantMoreActions = ({ onRoleChange, peerId, role }) => {
|
|
|
296
271
|
</Dropdown.Trigger>
|
|
297
272
|
<Dropdown.Portal>
|
|
298
273
|
<Dropdown.Content align="end" sideOffset={8} css={{ w: '$64', bg: '$surface_default' }}>
|
|
299
|
-
{
|
|
274
|
+
{shouldShowStageRoleChange ? (
|
|
300
275
|
<Dropdown.Item css={{ bg: '$surface_default' }} onClick={() => handleStageAction()}>
|
|
301
276
|
<ChangeRoleIcon />
|
|
302
277
|
<Text variant="sm" css={{ ml: '$4', fontWeight: '$semiBold', c: '$on_surface_high' }}>
|
|
303
278
|
{isInStage ? remove_from_stage_label : bring_to_stage_label}
|
|
304
279
|
</Text>
|
|
305
280
|
</Dropdown.Item>
|
|
306
|
-
) :
|
|
307
|
-
<Dropdown.Item css={{ bg: '$surface_default' }} onClick={() => onRoleChange(peerId)}>
|
|
308
|
-
<ChangeRoleIcon />
|
|
309
|
-
<Text variant="sm" css={{ ml: '$4', fontWeight: '$semiBold', c: '$on_surface_high' }}>
|
|
310
|
-
Change Role
|
|
311
|
-
</Text>
|
|
312
|
-
</Dropdown.Item>
|
|
313
|
-
)}
|
|
281
|
+
) : null}
|
|
314
282
|
|
|
315
283
|
{!isLocal && canRemoveOthers && (
|
|
316
284
|
<Dropdown.Item
|