@100mslive/roomkit-react 0.3.13 → 0.3.14-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/Diagnostics/AudioTest.d.ts +2 -0
- package/dist/Diagnostics/ConnectivityTest.d.ts +7 -0
- package/dist/Diagnostics/Diagnostics.d.ts +2 -0
- package/dist/Diagnostics/VideoTest.d.ts +2 -0
- package/dist/Diagnostics/components.d.ts +16 -0
- package/dist/Diagnostics/hms.d.ts +9 -0
- package/dist/Diagnostics/index.d.ts +1 -0
- package/dist/{HLSView-MSHPSUUJ.css → HLSView-772PCEEZ.css} +3 -3
- package/dist/{HLSView-MSHPSUUJ.css.map → HLSView-772PCEEZ.css.map} +1 -1
- package/dist/{HLSView-U4CRLAPX.js → HLSView-VSU7IPCJ.js} +2 -2
- package/dist/Prebuilt/App.d.ts +3 -1
- package/dist/Stats/index.d.ts +1 -0
- package/dist/{chunk-XIFK5DB3.js → chunk-VE34B77C.js} +12337 -1498
- package/dist/chunk-VE34B77C.js.map +7 -0
- package/dist/index.cjs.css +2 -2
- package/dist/index.cjs.css.map +1 -1
- package/dist/index.cjs.js +14570 -3643
- package/dist/index.cjs.js.map +4 -4
- package/dist/index.css +2 -2
- package/dist/index.css.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +5 -1
- package/dist/meta.cjs.json +904 -213
- package/dist/meta.esbuild.json +916 -221
- package/package.json +7 -7
- package/src/Diagnostics/AudioTest.tsx +153 -0
- package/src/Diagnostics/ConnectivityTest.tsx +355 -0
- package/src/Diagnostics/DeviceSelector.jsx +71 -0
- package/src/Diagnostics/Diagnostics.tsx +112 -0
- package/src/Diagnostics/VideoTest.tsx +60 -0
- package/src/Diagnostics/components.tsx +84 -0
- package/src/Diagnostics/hms.ts +9 -0
- package/src/Diagnostics/index.ts +1 -0
- package/src/Prebuilt/App.tsx +12 -1
- package/src/Prebuilt/components/Chat/ChatFooter.tsx +16 -2
- package/src/Prebuilt/components/MoreSettings/SplitComponents/MwebOptions.tsx +2 -2
- package/src/Prebuilt/components/StatsForNerds.jsx +1 -13
- package/src/Prebuilt/components/VideoLayouts/GridLayout.tsx +0 -17
- package/src/Stats/index.tsx +1 -0
- package/src/index.ts +1 -0
- package/dist/chunk-XIFK5DB3.js.map +0 -7
- /package/dist/{HLSView-U4CRLAPX.js.map → HLSView-VSU7IPCJ.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.14-alpha.1",
|
14
14
|
"author": "100ms",
|
15
15
|
"license": "MIT",
|
16
16
|
"repository": {
|
@@ -74,12 +74,12 @@
|
|
74
74
|
"react": ">=17.0.2 <19.0.0"
|
75
75
|
},
|
76
76
|
"dependencies": {
|
77
|
-
"@100mslive/hls-player": "0.3.
|
77
|
+
"@100mslive/hls-player": "0.3.14-alpha.1",
|
78
78
|
"@100mslive/hms-noise-cancellation": "0.0.1",
|
79
|
-
"@100mslive/hms-virtual-background": "1.13.
|
80
|
-
"@100mslive/hms-whiteboard": "0.0.
|
81
|
-
"@100mslive/react-icons": "0.10.
|
82
|
-
"@100mslive/react-sdk": "0.10.
|
79
|
+
"@100mslive/hms-virtual-background": "1.13.14-alpha.1",
|
80
|
+
"@100mslive/hms-whiteboard": "0.0.4-alpha.1",
|
81
|
+
"@100mslive/react-icons": "0.10.14-alpha.1",
|
82
|
+
"@100mslive/react-sdk": "0.10.14-alpha.1",
|
83
83
|
"@100mslive/types-prebuilt": "0.12.9",
|
84
84
|
"@emoji-mart/data": "^1.0.6",
|
85
85
|
"@emoji-mart/react": "^1.0.1",
|
@@ -115,5 +115,5 @@
|
|
115
115
|
"uuid": "^8.3.2",
|
116
116
|
"worker-timers": "^7.0.40"
|
117
117
|
},
|
118
|
-
"gitHead": "
|
118
|
+
"gitHead": "92efc31642f74553fa513d53112e9e680c88bc8b"
|
119
119
|
}
|
@@ -0,0 +1,153 @@
|
|
1
|
+
/* eslint-disable react/prop-types */
|
2
|
+
import React, { useEffect, useState } from 'react';
|
3
|
+
import {
|
4
|
+
selectDevices,
|
5
|
+
selectLocalAudioTrackID,
|
6
|
+
selectLocalMediaSettings,
|
7
|
+
selectTrackAudioByID,
|
8
|
+
useHMSActions,
|
9
|
+
useHMSStore,
|
10
|
+
} from '@100mslive/react-sdk';
|
11
|
+
import { MicOnIcon, SpeakerIcon } from '@100mslive/react-icons';
|
12
|
+
import { TestContainer, TestFooter } from './components';
|
13
|
+
import { Button } from '../Button';
|
14
|
+
import { Box, Flex } from '../Layout';
|
15
|
+
import { Progress } from '../Progress';
|
16
|
+
import { Text } from '../Text';
|
17
|
+
// @ts-ignore: No implicit any
|
18
|
+
import { DeviceSelector } from './DeviceSelector';
|
19
|
+
import { hmsDiagnostics } from './hms';
|
20
|
+
import { useAudioOutputTest } from '../Prebuilt/components/hooks/useAudioOutputTest';
|
21
|
+
import { TEST_AUDIO_URL } from '../Prebuilt/common/constants';
|
22
|
+
|
23
|
+
const SelectContainer = ({ children }: { children: React.ReactNode }) => (
|
24
|
+
<Box css={{ w: '50%', '@lg': { w: '100%' } }}>{children}</Box>
|
25
|
+
);
|
26
|
+
|
27
|
+
const MicTest = () => {
|
28
|
+
const devices = useHMSStore(selectDevices);
|
29
|
+
const [isRecording, setIsRecording] = useState(false);
|
30
|
+
const { audioInputDeviceId } = useHMSStore(selectLocalMediaSettings);
|
31
|
+
const [selectedMic, setSelectedMic] = useState(audioInputDeviceId || 'default');
|
32
|
+
const trackID = useHMSStore(selectLocalAudioTrackID);
|
33
|
+
const audioLevel = useHMSStore(selectTrackAudioByID(trackID));
|
34
|
+
|
35
|
+
return (
|
36
|
+
<SelectContainer>
|
37
|
+
<DeviceSelector
|
38
|
+
title="Microphone(Input)"
|
39
|
+
devices={devices.audioInput}
|
40
|
+
selection={selectedMic}
|
41
|
+
icon={<MicOnIcon />}
|
42
|
+
onChange={(deviceId: string) => {
|
43
|
+
setSelectedMic(deviceId);
|
44
|
+
hmsDiagnostics.stopMicCheck();
|
45
|
+
setIsRecording(false);
|
46
|
+
}}
|
47
|
+
/>
|
48
|
+
<Flex css={{ gap: '$6', alignItems: 'center' }}>
|
49
|
+
<Button
|
50
|
+
onClick={() =>
|
51
|
+
hmsDiagnostics
|
52
|
+
.startMicCheck(selectedMic, () => {
|
53
|
+
setIsRecording(false);
|
54
|
+
})
|
55
|
+
.then(() => {
|
56
|
+
setIsRecording(true);
|
57
|
+
})
|
58
|
+
}
|
59
|
+
disabled={isRecording}
|
60
|
+
>
|
61
|
+
{isRecording ? 'Recording...' : 'Record'}
|
62
|
+
</Button>
|
63
|
+
{isRecording && (
|
64
|
+
<>
|
65
|
+
<Text>
|
66
|
+
<MicOnIcon />
|
67
|
+
</Text>
|
68
|
+
<Progress.Root value={audioLevel} css={{ h: '$2' }}>
|
69
|
+
<Progress.Content
|
70
|
+
style={{
|
71
|
+
transform: `translateX(-${100 - audioLevel}%)`,
|
72
|
+
transition: 'transform 0.3s',
|
73
|
+
}}
|
74
|
+
/>
|
75
|
+
</Progress.Root>
|
76
|
+
</>
|
77
|
+
)}
|
78
|
+
</Flex>
|
79
|
+
</SelectContainer>
|
80
|
+
);
|
81
|
+
};
|
82
|
+
|
83
|
+
const SpeakerTest = () => {
|
84
|
+
const actions = useHMSActions();
|
85
|
+
const devices = useHMSStore(selectDevices);
|
86
|
+
const { audioOutputDeviceId } = useHMSStore(selectLocalMediaSettings);
|
87
|
+
const { playing, setPlaying, audioRef } = useAudioOutputTest({ deviceId: audioOutputDeviceId || 'default' });
|
88
|
+
|
89
|
+
return (
|
90
|
+
<SelectContainer>
|
91
|
+
<DeviceSelector
|
92
|
+
title="Speaker(output)"
|
93
|
+
devices={devices.audioOutput}
|
94
|
+
selection={audioOutputDeviceId || 'default'}
|
95
|
+
icon={<SpeakerIcon />}
|
96
|
+
onChange={(deviceId: string) => {
|
97
|
+
actions.setAudioOutputDevice(deviceId);
|
98
|
+
}}
|
99
|
+
/>
|
100
|
+
<Button
|
101
|
+
onClick={() => {
|
102
|
+
if (audioRef.current) {
|
103
|
+
audioRef.current.src = hmsDiagnostics.getRecordedAudio() || TEST_AUDIO_URL;
|
104
|
+
audioRef.current.play();
|
105
|
+
}
|
106
|
+
}}
|
107
|
+
disabled={playing}
|
108
|
+
>
|
109
|
+
<SpeakerIcon />
|
110
|
+
<Text css={{ ml: '$4' }}>{playing ? 'Playing' : 'Playback'}</Text>
|
111
|
+
</Button>
|
112
|
+
<audio
|
113
|
+
ref={audioRef}
|
114
|
+
onEnded={() => setPlaying(false)}
|
115
|
+
onPlay={() => setPlaying(true)}
|
116
|
+
style={{ display: 'none' }}
|
117
|
+
/>
|
118
|
+
</SelectContainer>
|
119
|
+
);
|
120
|
+
};
|
121
|
+
|
122
|
+
export const AudioTest = () => {
|
123
|
+
const [error, setError] = useState<Error | undefined>();
|
124
|
+
useEffect(() => {
|
125
|
+
hmsDiagnostics.requestPermission({ audio: true }).catch(error => setError(error));
|
126
|
+
}, []);
|
127
|
+
|
128
|
+
return (
|
129
|
+
<>
|
130
|
+
<TestContainer>
|
131
|
+
<Text variant="body2" css={{ c: '$on_primary_medium' }}>
|
132
|
+
Record an audio clip and play it back to check that your microphone and speaker are working. If they aren't,
|
133
|
+
make sure your volume is turned up, try a different speaker or microphone, or check your bluetooth settings.
|
134
|
+
</Text>
|
135
|
+
|
136
|
+
<Flex
|
137
|
+
css={{
|
138
|
+
mt: '$10',
|
139
|
+
gap: '$10',
|
140
|
+
'@lg': {
|
141
|
+
flexDirection: 'column',
|
142
|
+
gap: '$8',
|
143
|
+
},
|
144
|
+
}}
|
145
|
+
>
|
146
|
+
{!error && <MicTest />}
|
147
|
+
<SpeakerTest />
|
148
|
+
</Flex>
|
149
|
+
</TestContainer>
|
150
|
+
<TestFooter error={error} ctaText="Does your audio sound good?" />
|
151
|
+
</>
|
152
|
+
);
|
153
|
+
};
|
@@ -0,0 +1,355 @@
|
|
1
|
+
import React, { useState } from 'react';
|
2
|
+
import { ConnectivityCheckResult, ConnectivityState, DiagnosticsRTCStats } from '@100mslive/react-sdk';
|
3
|
+
import { CheckCircleIcon, CrossCircleIcon, LinkIcon } from '@100mslive/react-icons';
|
4
|
+
import { TestContainer, TestFooter } from './components';
|
5
|
+
import { Button } from '../Button';
|
6
|
+
import { Box, Flex } from '../Layout';
|
7
|
+
import { Loading } from '../Loading';
|
8
|
+
import { formatBytes } from '../Stats';
|
9
|
+
import { Text } from '../Text';
|
10
|
+
import { hmsDiagnostics } from './hms';
|
11
|
+
|
12
|
+
const Regions = {
|
13
|
+
in: 'India',
|
14
|
+
eu: 'Europe',
|
15
|
+
us: 'United States',
|
16
|
+
};
|
17
|
+
|
18
|
+
const ConnectivityStateMessage = {
|
19
|
+
[ConnectivityState.STARTING]: 'Fetching Init',
|
20
|
+
[ConnectivityState.INIT_FETCHED]: 'Connecting to signal server',
|
21
|
+
[ConnectivityState.SIGNAL_CONNECTED]: 'Establishing ICE connection',
|
22
|
+
[ConnectivityState.ICE_ESTABLISHED]: 'Capturing Media',
|
23
|
+
[ConnectivityState.MEDIA_CAPTURED]: 'Publishing Media',
|
24
|
+
[ConnectivityState.MEDIA_PUBLISHED]: 'Finishing Up',
|
25
|
+
[ConnectivityState.COMPLETED]: 'Completed',
|
26
|
+
};
|
27
|
+
|
28
|
+
export const ConnectivityTestStepResult = ({
|
29
|
+
title,
|
30
|
+
success,
|
31
|
+
children,
|
32
|
+
}: {
|
33
|
+
title: string;
|
34
|
+
success?: boolean;
|
35
|
+
children: React.ReactNode;
|
36
|
+
}) => {
|
37
|
+
return (
|
38
|
+
<Box css={{ my: '$10', p: '$10', r: '$1', bg: '$surface_bright' }}>
|
39
|
+
<Text css={{ c: '$on_primary_medium', mb: '$6' }}>{title}</Text>
|
40
|
+
{success ? (
|
41
|
+
<Flex>
|
42
|
+
<Text css={{ c: '$alert_success' }}>
|
43
|
+
<CheckCircleIcon width="1.5rem" height="1.5rem" />
|
44
|
+
</Text>
|
45
|
+
<Text variant="lg" css={{ ml: '$4' }}>
|
46
|
+
Connected
|
47
|
+
</Text>
|
48
|
+
</Flex>
|
49
|
+
) : (
|
50
|
+
<Flex>
|
51
|
+
<Text css={{ c: '$alert_error_bright' }}>
|
52
|
+
<CrossCircleIcon width="1.5rem" height="1.5rem" />
|
53
|
+
</Text>
|
54
|
+
<Text variant="lg" css={{ ml: '$4' }}>
|
55
|
+
Failed
|
56
|
+
</Text>
|
57
|
+
</Flex>
|
58
|
+
)}
|
59
|
+
<Box>{children}</Box>
|
60
|
+
</Box>
|
61
|
+
);
|
62
|
+
};
|
63
|
+
|
64
|
+
const DetailedInfo = ({
|
65
|
+
title,
|
66
|
+
value,
|
67
|
+
Icon,
|
68
|
+
}: {
|
69
|
+
title: string;
|
70
|
+
value: string;
|
71
|
+
Icon?: (props: React.SVGProps<SVGSVGElement>) => React.JSX.Element;
|
72
|
+
}) => {
|
73
|
+
return (
|
74
|
+
<Box css={{ flex: '50%', mt: '$6' }}>
|
75
|
+
<Text variant="caption" css={{ fontWeight: '$semiBold', c: '$on_primary_medium' }}>
|
76
|
+
{title}
|
77
|
+
</Text>
|
78
|
+
<Flex css={{ mt: '$xs', alignItems: 'flex-start' }}>
|
79
|
+
{Icon && (
|
80
|
+
<Text css={{ mr: '$4' }}>
|
81
|
+
<Icon width="1rem" height="1rem" />
|
82
|
+
</Text>
|
83
|
+
)}
|
84
|
+
<Text variant="caption">{value}</Text>
|
85
|
+
</Flex>
|
86
|
+
</Box>
|
87
|
+
);
|
88
|
+
};
|
89
|
+
|
90
|
+
const MediaServerResult = ({ result }: { result?: ConnectivityCheckResult['mediaServerReport'] }) => {
|
91
|
+
return (
|
92
|
+
<ConnectivityTestStepResult
|
93
|
+
title="Media server connection test"
|
94
|
+
success={result?.isPublishICEConnected && result.isSubscribeICEConnected}
|
95
|
+
>
|
96
|
+
<Flex css={{ flexWrap: 'wrap' }}>
|
97
|
+
<DetailedInfo
|
98
|
+
title="Media Captured"
|
99
|
+
value={result?.stats?.audio.bytesSent ? 'Yes' : 'No'}
|
100
|
+
Icon={result?.stats?.audio.bytesSent ? CheckCircleIcon : CrossCircleIcon}
|
101
|
+
/>
|
102
|
+
<DetailedInfo
|
103
|
+
title="Media Published"
|
104
|
+
value={result?.stats?.audio.bitrateSent ? 'Yes' : 'No'}
|
105
|
+
Icon={result?.stats?.audio.bytesSent ? CheckCircleIcon : CrossCircleIcon}
|
106
|
+
/>
|
107
|
+
{result?.connectionQualityScore ? (
|
108
|
+
<DetailedInfo
|
109
|
+
title="Connection Quality Score (CQS)"
|
110
|
+
value={`${result.connectionQualityScore.toFixed(2)} (out of 5)`}
|
111
|
+
/>
|
112
|
+
) : null}
|
113
|
+
</Flex>
|
114
|
+
</ConnectivityTestStepResult>
|
115
|
+
);
|
116
|
+
};
|
117
|
+
|
118
|
+
const SignallingResult = ({ result }: { result?: ConnectivityCheckResult['signallingReport'] }) => {
|
119
|
+
return (
|
120
|
+
<ConnectivityTestStepResult title="Signalling server connection test" success={result?.isConnected}>
|
121
|
+
<Flex css={{ flexWrap: 'wrap' }}>
|
122
|
+
<DetailedInfo
|
123
|
+
title="Signalling Gateway"
|
124
|
+
value={result?.isConnected ? 'Reachable' : 'Unreachable'}
|
125
|
+
Icon={result?.isConnected ? CheckCircleIcon : CrossCircleIcon}
|
126
|
+
/>
|
127
|
+
<DetailedInfo title="Websocket URL" value={result?.websocketUrl || 'N/A'} Icon={LinkIcon} />
|
128
|
+
</Flex>
|
129
|
+
</ConnectivityTestStepResult>
|
130
|
+
);
|
131
|
+
};
|
132
|
+
|
133
|
+
const AudioStats = ({ stats }: { stats: DiagnosticsRTCStats | undefined }) => {
|
134
|
+
return (
|
135
|
+
<ConnectivityTestStepResult title="Audio" success={!!stats?.bytesSent}>
|
136
|
+
{stats && (
|
137
|
+
<Flex css={{ flexWrap: 'wrap' }}>
|
138
|
+
<DetailedInfo title="Bytes Sent" value={formatBytes(stats.bytesSent)} />
|
139
|
+
<DetailedInfo title="Bytes Received" value={formatBytes(stats.bytesReceived)} />
|
140
|
+
<DetailedInfo title="Packets Received" value={stats.packetsReceived.toString()} />
|
141
|
+
<DetailedInfo title="Packets Lost" value={stats.packetsLost.toString()} />
|
142
|
+
<DetailedInfo title="Bitrate Sent" value={formatBytes(stats.bitrateSent, 'b/s')} />
|
143
|
+
<DetailedInfo title="Bitrate Received" value={formatBytes(stats.bitrateReceived, 'b/s')} />
|
144
|
+
<DetailedInfo title="Round Trip Time" value={`${stats.roundTripTime} ms`} />
|
145
|
+
</Flex>
|
146
|
+
)}
|
147
|
+
</ConnectivityTestStepResult>
|
148
|
+
);
|
149
|
+
};
|
150
|
+
|
151
|
+
const VideoStats = ({ stats }: { stats: DiagnosticsRTCStats | undefined }) => {
|
152
|
+
return (
|
153
|
+
<ConnectivityTestStepResult title="Video" success={!!stats?.bytesSent}>
|
154
|
+
{stats && (
|
155
|
+
<Flex css={{ flexWrap: 'wrap' }}>
|
156
|
+
<DetailedInfo title="Bytes Sent" value={formatBytes(stats.bytesSent)} />
|
157
|
+
<DetailedInfo title="Bytes Received" value={formatBytes(stats.bytesReceived)} />
|
158
|
+
<DetailedInfo title="Packets Received" value={stats.packetsReceived.toString()} />
|
159
|
+
<DetailedInfo title="Packets Lost" value={stats.packetsLost.toString()} />
|
160
|
+
<DetailedInfo title="Bitrate Sent" value={formatBytes(stats.bitrateSent, 'b/s')} />
|
161
|
+
<DetailedInfo title="Bitrate Received" value={formatBytes(stats.bitrateReceived, 'b/s')} />
|
162
|
+
<DetailedInfo title="Round Trip Time" value={`${stats.roundTripTime} ms`} />
|
163
|
+
</Flex>
|
164
|
+
)}
|
165
|
+
</ConnectivityTestStepResult>
|
166
|
+
);
|
167
|
+
};
|
168
|
+
|
169
|
+
const Footer = ({
|
170
|
+
error,
|
171
|
+
result,
|
172
|
+
restart,
|
173
|
+
}: {
|
174
|
+
result?: ConnectivityCheckResult;
|
175
|
+
restart: () => void;
|
176
|
+
error?: Error;
|
177
|
+
}) => {
|
178
|
+
return (
|
179
|
+
<TestFooter error={error}>
|
180
|
+
<Flex css={{ gap: '$8', '@lg': { flexDirection: 'column' } }}>
|
181
|
+
<Button variant="standard" onClick={restart}>
|
182
|
+
Restart Test
|
183
|
+
</Button>
|
184
|
+
<Button disabled={!result} onClick={() => result && downloadJson(result, 'hms_diagnostics_results')}>
|
185
|
+
Download Test Report
|
186
|
+
</Button>
|
187
|
+
</Flex>
|
188
|
+
</TestFooter>
|
189
|
+
);
|
190
|
+
};
|
191
|
+
|
192
|
+
const ConnectivityTestReport = ({
|
193
|
+
error,
|
194
|
+
result,
|
195
|
+
progress,
|
196
|
+
startTest,
|
197
|
+
}: {
|
198
|
+
error?: Error;
|
199
|
+
result?: ConnectivityCheckResult;
|
200
|
+
progress?: ConnectivityState;
|
201
|
+
startTest: () => void;
|
202
|
+
}) => {
|
203
|
+
if (error) {
|
204
|
+
return (
|
205
|
+
<>
|
206
|
+
<TestContainer css={{ textAlign: 'center' }}>
|
207
|
+
<Text css={{ c: '$alert_error_default', mb: '$4' }}>
|
208
|
+
<CrossCircleIcon />
|
209
|
+
</Text>
|
210
|
+
<Text variant="h6">Connectivity Test Failed</Text>
|
211
|
+
<Text variant="body2" css={{ c: '$on_primary_medium' }}>
|
212
|
+
{error.message}
|
213
|
+
</Text>
|
214
|
+
</TestContainer>
|
215
|
+
<Footer restart={startTest} error={error} />
|
216
|
+
</>
|
217
|
+
);
|
218
|
+
}
|
219
|
+
|
220
|
+
if (result) {
|
221
|
+
return (
|
222
|
+
<>
|
223
|
+
<TestContainer>
|
224
|
+
<Text css={{ c: '$on_primary_medium' }}>Connectivity test has been completed.</Text>
|
225
|
+
<SignallingResult result={result?.signallingReport} />
|
226
|
+
<MediaServerResult result={result?.mediaServerReport} />
|
227
|
+
<AudioStats stats={result?.mediaServerReport?.stats?.audio} />
|
228
|
+
<VideoStats stats={result?.mediaServerReport?.stats?.video} />
|
229
|
+
</TestContainer>
|
230
|
+
<Footer result={result} restart={startTest} error={error} />
|
231
|
+
</>
|
232
|
+
);
|
233
|
+
}
|
234
|
+
|
235
|
+
if (progress) {
|
236
|
+
return (
|
237
|
+
<TestContainer css={{ textAlign: 'center' }}>
|
238
|
+
<Text css={{ c: '$primary_bright' }}>
|
239
|
+
<Loading size="3.5rem" color="currentColor" />
|
240
|
+
</Text>
|
241
|
+
<Text variant="h6" css={{ mt: '$8' }}>
|
242
|
+
Checking your connection...
|
243
|
+
</Text>
|
244
|
+
<Text
|
245
|
+
variant="body2"
|
246
|
+
css={{ c: '$on_primary_medium', mt: '$4' }}
|
247
|
+
>{`${ConnectivityStateMessage[progress]}...`}</Text>
|
248
|
+
</TestContainer>
|
249
|
+
);
|
250
|
+
}
|
251
|
+
|
252
|
+
return null;
|
253
|
+
};
|
254
|
+
|
255
|
+
const RegionSelector = ({
|
256
|
+
region,
|
257
|
+
setRegion,
|
258
|
+
startTest,
|
259
|
+
}: {
|
260
|
+
region?: string;
|
261
|
+
startTest?: () => void;
|
262
|
+
setRegion: (region: string) => void;
|
263
|
+
}) => {
|
264
|
+
return (
|
265
|
+
<TestContainer css={{ borderBottom: '1px solid $border_default' }}>
|
266
|
+
<Text variant="body1">Select a region</Text>
|
267
|
+
<Text variant="body2" css={{ c: '$on_secondary_low' }}>
|
268
|
+
Select the closest region for best results
|
269
|
+
</Text>
|
270
|
+
<Flex
|
271
|
+
justify="between"
|
272
|
+
css={{
|
273
|
+
mt: '$md',
|
274
|
+
'@lg': {
|
275
|
+
flexDirection: 'column',
|
276
|
+
gap: '$8',
|
277
|
+
},
|
278
|
+
}}
|
279
|
+
>
|
280
|
+
<Flex
|
281
|
+
css={{
|
282
|
+
gap: '$4',
|
283
|
+
'@lg': {
|
284
|
+
flexDirection: 'column',
|
285
|
+
},
|
286
|
+
}}
|
287
|
+
>
|
288
|
+
{Object.entries(Regions).map(([key, value]) => (
|
289
|
+
<Button
|
290
|
+
key={key}
|
291
|
+
outlined={region !== key}
|
292
|
+
variant={region === key ? 'primary' : 'standard'}
|
293
|
+
css={region === key ? { bg: '$primary_dim' } : {}}
|
294
|
+
onClick={() => setRegion(key)}
|
295
|
+
>
|
296
|
+
{value}
|
297
|
+
</Button>
|
298
|
+
))}
|
299
|
+
</Flex>
|
300
|
+
<Flex css={{ '@lg': { flexDirection: 'column' } }}>
|
301
|
+
<Button variant="primary" onClick={startTest} disabled={!startTest}>
|
302
|
+
{startTest ? 'Start Test' : 'Testing...'}
|
303
|
+
</Button>
|
304
|
+
</Flex>
|
305
|
+
</Flex>
|
306
|
+
</TestContainer>
|
307
|
+
);
|
308
|
+
};
|
309
|
+
|
310
|
+
export const ConnectivityTest = () => {
|
311
|
+
const [region, setRegion] = useState<string | undefined>(Object.keys(Regions)[0]);
|
312
|
+
const [error, setError] = useState<Error | undefined>();
|
313
|
+
const [progress, setProgress] = useState<ConnectivityState>();
|
314
|
+
const [result, setResult] = useState<ConnectivityCheckResult | undefined>();
|
315
|
+
|
316
|
+
const startTest = () => {
|
317
|
+
setError(undefined);
|
318
|
+
setProgress(ConnectivityState.STARTING);
|
319
|
+
setResult(undefined);
|
320
|
+
hmsDiagnostics
|
321
|
+
.startConnectivityCheck(
|
322
|
+
state => {
|
323
|
+
setProgress(state);
|
324
|
+
},
|
325
|
+
result => {
|
326
|
+
setResult(result);
|
327
|
+
},
|
328
|
+
region,
|
329
|
+
)
|
330
|
+
.catch(error => {
|
331
|
+
setError(error);
|
332
|
+
});
|
333
|
+
};
|
334
|
+
|
335
|
+
return (
|
336
|
+
<>
|
337
|
+
<RegionSelector
|
338
|
+
region={region}
|
339
|
+
setRegion={setRegion}
|
340
|
+
startTest={!progress || progress === ConnectivityState.COMPLETED ? startTest : undefined}
|
341
|
+
/>
|
342
|
+
<ConnectivityTestReport error={error} result={result} progress={progress} startTest={startTest} />
|
343
|
+
</>
|
344
|
+
);
|
345
|
+
};
|
346
|
+
|
347
|
+
const downloadJson = (obj: object, fileName: string) => {
|
348
|
+
const a = document.createElement('a');
|
349
|
+
const file = new Blob([JSON.stringify(obj, null, 2)], {
|
350
|
+
type: 'application/json',
|
351
|
+
});
|
352
|
+
a.href = URL.createObjectURL(file);
|
353
|
+
a.download = `${fileName}.json`;
|
354
|
+
a.click();
|
355
|
+
};
|
@@ -0,0 +1,71 @@
|
|
1
|
+
import React, { useRef, useState } from 'react';
|
2
|
+
import PropTypes from 'prop-types';
|
3
|
+
import { Dropdown } from '../Dropdown';
|
4
|
+
import { Box, Flex } from '../Layout';
|
5
|
+
import { DialogDropdownTrigger } from '../Prebuilt/primitives/DropdownTrigger';
|
6
|
+
import { Text } from '../Text';
|
7
|
+
|
8
|
+
export const DeviceSelector = ({ title, devices, selection, onChange, icon, children = null }) => {
|
9
|
+
const [open, setOpen] = useState(false);
|
10
|
+
const ref = useRef(null);
|
11
|
+
return (
|
12
|
+
<Box css={{ mb: '$6' }}>
|
13
|
+
<Text css={{ mb: '$4' }}>{title}</Text>
|
14
|
+
<Flex
|
15
|
+
align="center"
|
16
|
+
css={{
|
17
|
+
gap: '$4',
|
18
|
+
'@md': {
|
19
|
+
flexDirection: children ? 'column' : 'row',
|
20
|
+
alignItems: children ? 'start' : 'center',
|
21
|
+
},
|
22
|
+
}}
|
23
|
+
>
|
24
|
+
<Dropdown.Root open={open} onOpenChange={setOpen}>
|
25
|
+
<DialogDropdownTrigger
|
26
|
+
ref={ref}
|
27
|
+
icon={icon}
|
28
|
+
title={devices.find(({ deviceId }) => deviceId === selection)?.label || 'Select device from list'}
|
29
|
+
open={open}
|
30
|
+
/>
|
31
|
+
<Dropdown.Portal>
|
32
|
+
<Dropdown.Content
|
33
|
+
align="start"
|
34
|
+
sideOffset={8}
|
35
|
+
css={{
|
36
|
+
w:
|
37
|
+
// @ts-ignore
|
38
|
+
ref.current?.clientWidth,
|
39
|
+
zIndex: 1001,
|
40
|
+
}}
|
41
|
+
>
|
42
|
+
{devices.map(device => {
|
43
|
+
return (
|
44
|
+
<Dropdown.Item
|
45
|
+
key={device.label}
|
46
|
+
onSelect={() => onChange(device.deviceId)}
|
47
|
+
css={{
|
48
|
+
px: '$9',
|
49
|
+
}}
|
50
|
+
>
|
51
|
+
{device.label}
|
52
|
+
</Dropdown.Item>
|
53
|
+
);
|
54
|
+
})}
|
55
|
+
</Dropdown.Content>
|
56
|
+
</Dropdown.Portal>
|
57
|
+
</Dropdown.Root>
|
58
|
+
{children}
|
59
|
+
</Flex>
|
60
|
+
</Box>
|
61
|
+
);
|
62
|
+
};
|
63
|
+
|
64
|
+
DeviceSelector.propTypes = {
|
65
|
+
title: PropTypes.string.isRequired,
|
66
|
+
devices: PropTypes.array.isRequired,
|
67
|
+
selection: PropTypes.string,
|
68
|
+
onChange: PropTypes.func.isRequired,
|
69
|
+
icon: PropTypes.node,
|
70
|
+
children: PropTypes.node,
|
71
|
+
};
|