@limrun/ui 0.9.0-rc.8 → 0.13.3-rc.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/README.md +25 -8
- package/dist/app-store-relay/index.cjs +9 -0
- package/dist/app-store-relay/index.d.ts +102 -0
- package/dist/app-store-relay/index.js +430 -0
- package/dist/app-store-relay/react.cjs +1 -0
- package/dist/app-store-relay/react.d.ts +15 -0
- package/dist/app-store-relay/react.js +65 -0
- package/dist/browser-storage-BJ__DGJ6.mjs +202 -0
- package/dist/browser-storage-C1jQLXat.js +1 -0
- package/dist/client-Bk1N3XIF.mjs +228 -0
- package/dist/client-CnbCWvCs.js +1 -0
- package/dist/components/device-install/index.d.ts +1 -2
- package/dist/core/device-install/apple/provisioning.d.ts +55 -4
- package/dist/core/device-install/operations/limbuild-client.d.ts +15 -1
- package/dist/core/device-install/storage/browser-storage.d.ts +9 -5
- package/dist/core/device-install/types.d.ts +4 -1
- package/dist/device-build/index.cjs +1 -0
- package/dist/device-build/index.d.ts +4 -0
- package/dist/device-build/index.js +84 -0
- package/dist/device-build/react.cjs +1 -0
- package/dist/device-build/react.d.ts +20 -0
- package/dist/device-build/react.js +66 -0
- package/dist/device-build/signing.d.ts +20 -0
- package/dist/device-install/index.cjs +1 -1
- package/dist/device-install/index.d.ts +18 -3
- package/dist/device-install/index.js +570 -76
- package/dist/device-install/react.cjs +1 -1
- package/dist/device-install/react.d.ts +23 -1
- package/dist/device-install/react.js +93 -2
- package/dist/hooks/index.d.ts +1 -1
- package/dist/index-BXg7HdIs.mjs +11547 -0
- package/dist/index-CMeQfhYy.js +22 -0
- package/dist/index.cjs +1 -1
- package/dist/index.d.ts +0 -2
- package/dist/index.js +502 -495
- package/dist/limbuild-client-CFJhYsRx.mjs +79 -0
- package/dist/limbuild-client-C_CMNLYV.js +1 -0
- package/dist/provisioning-CdseoMJQ.mjs +239 -0
- package/dist/provisioning-D2ZZQeyX.js +1 -0
- package/package.json +21 -1
- package/src/app-store-relay/index.test.ts +74 -0
- package/src/app-store-relay/index.ts +447 -0
- package/src/app-store-relay/react.ts +125 -0
- package/src/components/device-install/index.ts +1 -2
- package/src/core/device-install/apple/provisioning.test.ts +84 -0
- package/src/core/device-install/apple/provisioning.ts +91 -7
- package/src/core/device-install/operations/limbuild-client.ts +32 -2
- package/src/core/device-install/storage/browser-storage.ts +29 -14
- package/src/core/device-install/types.ts +5 -1
- package/src/device-build/index.ts +42 -0
- package/src/device-build/react.ts +128 -0
- package/src/device-build/signing.ts +94 -0
- package/src/device-install/index.ts +49 -3
- package/src/device-install/react.ts +180 -1
- package/src/hooks/index.ts +1 -1
- package/src/index.ts +1 -4
- package/vite.config.ts +4 -0
- package/dist/components/device-install/device-install-dialog.d.ts +0 -5
- package/dist/device-install-dialog-DGn2ZdBB.js +0 -2
- package/dist/device-install-dialog-DgWsZF6o.mjs +0 -443
- package/dist/device-install-dialog.css +0 -1
- package/dist/hooks/use-device-install.d.ts +0 -73
- package/dist/use-device-install-ByUSmeYz.js +0 -31
- package/dist/use-device-install-Y42p84we.mjs +0 -13624
- package/src/components/device-install/device-install-dialog.css +0 -325
- package/src/components/device-install/device-install-dialog.tsx +0 -495
- package/src/hooks/use-device-install.ts +0 -1219
|
@@ -1,495 +0,0 @@
|
|
|
1
|
-
import { useEffect, useId, useState, type ChangeEvent, type ReactNode } from 'react';
|
|
2
|
-
import { clsx } from 'clsx';
|
|
3
|
-
import { useDeviceInstall, type UseDeviceInstallOptions } from '../../hooks/use-device-install';
|
|
4
|
-
import type { DeviceInstallStep, DeviceInstallStepStatus } from '../../core/device-install';
|
|
5
|
-
import './device-install-dialog.css';
|
|
6
|
-
|
|
7
|
-
export type DeviceInstallDialogProps = UseDeviceInstallOptions & {
|
|
8
|
-
disabled?: boolean;
|
|
9
|
-
};
|
|
10
|
-
|
|
11
|
-
const steps: Array<{ id: DeviceInstallStep; title: string; description: string }> = [
|
|
12
|
-
{
|
|
13
|
-
id: 'signing',
|
|
14
|
-
title: 'Prepare signing',
|
|
15
|
-
description: 'Choose Apple ID login or upload certificates for a registered developer device.',
|
|
16
|
-
},
|
|
17
|
-
{
|
|
18
|
-
id: 'build',
|
|
19
|
-
title: 'Build for device',
|
|
20
|
-
description: 'Start the signed iPhone build before connecting over USB.',
|
|
21
|
-
},
|
|
22
|
-
{
|
|
23
|
-
id: 'connect',
|
|
24
|
-
title: 'Connect and pair',
|
|
25
|
-
description: 'After the build succeeds, connect the iPhone with WebUSB and pair this browser.',
|
|
26
|
-
},
|
|
27
|
-
{
|
|
28
|
-
id: 'install',
|
|
29
|
-
title: 'Start installation',
|
|
30
|
-
description: 'Relay the last successful device build to the paired iPhone.',
|
|
31
|
-
},
|
|
32
|
-
];
|
|
33
|
-
|
|
34
|
-
type SigningSection = 'apple-id' | 'upload';
|
|
35
|
-
|
|
36
|
-
export function DeviceInstallDialog({
|
|
37
|
-
disabled,
|
|
38
|
-
...hookOptions
|
|
39
|
-
}: DeviceInstallDialogProps) {
|
|
40
|
-
const [open, setOpen] = useState(false);
|
|
41
|
-
const [openStep, setOpenStep] = useState<DeviceInstallStep>('signing');
|
|
42
|
-
const [signingSection, setSigningSection] = useState<SigningSection>();
|
|
43
|
-
const [appleAccountName, setAppleAccountName] = useState('');
|
|
44
|
-
const [applePassword, setApplePassword] = useState('');
|
|
45
|
-
const [appleTwoFactorCode, setAppleTwoFactorCode] = useState('');
|
|
46
|
-
const dialogTitleId = useId();
|
|
47
|
-
const deviceInstall = useDeviceInstall(hookOptions);
|
|
48
|
-
|
|
49
|
-
useEffect(() => {
|
|
50
|
-
setOpenStep(deviceInstall.currentStep);
|
|
51
|
-
}, [deviceInstall.currentStep]);
|
|
52
|
-
|
|
53
|
-
const updateSigningFiles = (field: 'certificateFile' | 'provisioningProfileFile', event: ChangeEvent<HTMLInputElement>) => {
|
|
54
|
-
deviceInstall.setSigningFiles({
|
|
55
|
-
[field]: event.currentTarget.files?.[0],
|
|
56
|
-
});
|
|
57
|
-
};
|
|
58
|
-
|
|
59
|
-
return (
|
|
60
|
-
<div className="lr-device-install">
|
|
61
|
-
<button
|
|
62
|
-
type="button"
|
|
63
|
-
className="lr-device-install__trigger"
|
|
64
|
-
disabled={disabled || !hookOptions.apiUrl}
|
|
65
|
-
onClick={() => setOpen(true)}
|
|
66
|
-
>
|
|
67
|
-
Install to iPhone
|
|
68
|
-
</button>
|
|
69
|
-
|
|
70
|
-
{open && (
|
|
71
|
-
<div className="lr-device-install__backdrop" role="presentation">
|
|
72
|
-
<section
|
|
73
|
-
aria-labelledby={dialogTitleId}
|
|
74
|
-
aria-modal="true"
|
|
75
|
-
className="lr-device-install__dialog"
|
|
76
|
-
role="dialog"
|
|
77
|
-
>
|
|
78
|
-
<header className="lr-device-install__header">
|
|
79
|
-
<div>
|
|
80
|
-
<h2 id={dialogTitleId}>Install to a real iPhone</h2>
|
|
81
|
-
<p>Prepare signing, build for the registered device, connect and pair, then install from this browser.</p>
|
|
82
|
-
</div>
|
|
83
|
-
<button type="button" className="lr-device-install__icon-button" onClick={() => setOpen(false)}>
|
|
84
|
-
Close
|
|
85
|
-
</button>
|
|
86
|
-
</header>
|
|
87
|
-
|
|
88
|
-
{deviceInstall.error && <div className="lr-device-install__error">{deviceInstall.error}</div>}
|
|
89
|
-
|
|
90
|
-
<div className="lr-device-install__steps">
|
|
91
|
-
{steps.map((step, index) => (
|
|
92
|
-
<StepCard
|
|
93
|
-
key={step.id}
|
|
94
|
-
index={index + 1}
|
|
95
|
-
step={step}
|
|
96
|
-
active={deviceInstall.currentStep === step.id}
|
|
97
|
-
open={openStep === step.id}
|
|
98
|
-
status={deviceInstall.stepStatuses[step.id]}
|
|
99
|
-
onToggle={() => setOpenStep(step.id)}
|
|
100
|
-
>
|
|
101
|
-
{step.id === 'signing' && (
|
|
102
|
-
<div className="lr-device-install__step-body">
|
|
103
|
-
<div className="lr-device-install__choice-grid">
|
|
104
|
-
<button
|
|
105
|
-
type="button"
|
|
106
|
-
className={clsx(
|
|
107
|
-
'lr-device-install__choice',
|
|
108
|
-
signingSection === 'apple-id' && 'lr-device-install__choice--active',
|
|
109
|
-
)}
|
|
110
|
-
onClick={() => setSigningSection('apple-id')}
|
|
111
|
-
>
|
|
112
|
-
<strong>Apple ID login</strong>
|
|
113
|
-
<span>Sign in, choose team, bundle ID, and registered devices, then generate signing assets.</span>
|
|
114
|
-
</button>
|
|
115
|
-
<button
|
|
116
|
-
type="button"
|
|
117
|
-
className={clsx(
|
|
118
|
-
'lr-device-install__choice',
|
|
119
|
-
signingSection === 'upload' && 'lr-device-install__choice--active',
|
|
120
|
-
)}
|
|
121
|
-
onClick={() => setSigningSection('upload')}
|
|
122
|
-
>
|
|
123
|
-
<strong>Upload certificates</strong>
|
|
124
|
-
<span>Use an existing .p12 certificate and provisioning profile.</span>
|
|
125
|
-
</button>
|
|
126
|
-
</div>
|
|
127
|
-
|
|
128
|
-
{signingSection === 'apple-id' && (
|
|
129
|
-
<div className="lr-device-install__section-panel">
|
|
130
|
-
<div className="lr-device-install__grid">
|
|
131
|
-
<label className="lr-device-install__field">
|
|
132
|
-
<span>Apple ID</span>
|
|
133
|
-
<input
|
|
134
|
-
type="email"
|
|
135
|
-
autoComplete="username"
|
|
136
|
-
placeholder="name@example.com"
|
|
137
|
-
value={appleAccountName}
|
|
138
|
-
onChange={(event) => setAppleAccountName(event.currentTarget.value)}
|
|
139
|
-
/>
|
|
140
|
-
</label>
|
|
141
|
-
<label className="lr-device-install__field">
|
|
142
|
-
<span>Apple ID password</span>
|
|
143
|
-
<input
|
|
144
|
-
type="password"
|
|
145
|
-
autoComplete="current-password"
|
|
146
|
-
placeholder="Password stays in this browser"
|
|
147
|
-
value={applePassword}
|
|
148
|
-
onChange={(event) => setApplePassword(event.currentTarget.value)}
|
|
149
|
-
/>
|
|
150
|
-
</label>
|
|
151
|
-
{!deviceInstall.hasReusableAppleCertificate && (
|
|
152
|
-
<label className="lr-device-install__field">
|
|
153
|
-
<span>Generated .p12 password</span>
|
|
154
|
-
<input
|
|
155
|
-
type="password"
|
|
156
|
-
placeholder="Used when exporting Apple certificate"
|
|
157
|
-
onChange={(event) =>
|
|
158
|
-
deviceInstall.setSigningFiles({ certificatePassword: event.currentTarget.value })
|
|
159
|
-
}
|
|
160
|
-
/>
|
|
161
|
-
</label>
|
|
162
|
-
)}
|
|
163
|
-
</div>
|
|
164
|
-
<div className="lr-device-install__actions">
|
|
165
|
-
<button
|
|
166
|
-
type="button"
|
|
167
|
-
className="lr-device-install__secondary"
|
|
168
|
-
disabled={
|
|
169
|
-
disabled ||
|
|
170
|
-
!hookOptions.apiUrl ||
|
|
171
|
-
!appleAccountName ||
|
|
172
|
-
!applePassword ||
|
|
173
|
-
deviceInstall.busyAction === 'signing'
|
|
174
|
-
}
|
|
175
|
-
onClick={() =>
|
|
176
|
-
void deviceInstall.startAppleIDLogin({
|
|
177
|
-
accountName: appleAccountName,
|
|
178
|
-
password: applePassword,
|
|
179
|
-
})
|
|
180
|
-
}
|
|
181
|
-
>
|
|
182
|
-
{deviceInstall.appleSigningStatus === 'authenticating'
|
|
183
|
-
? 'Signing in...'
|
|
184
|
-
: 'Sign in with Apple ID'}
|
|
185
|
-
</button>
|
|
186
|
-
<span className="lr-device-install__hint">
|
|
187
|
-
Apple password is used only by browser-side SRP. Status: {deviceInstall.appleSigningStatus}
|
|
188
|
-
</span>
|
|
189
|
-
</div>
|
|
190
|
-
{deviceInstall.appleSigningStatus === 'two-factor-required' && (
|
|
191
|
-
<div className="lr-device-install__grid">
|
|
192
|
-
<label className="lr-device-install__field">
|
|
193
|
-
<span>Two-factor code</span>
|
|
194
|
-
<input
|
|
195
|
-
type="text"
|
|
196
|
-
inputMode="numeric"
|
|
197
|
-
autoComplete="one-time-code"
|
|
198
|
-
value={appleTwoFactorCode}
|
|
199
|
-
onChange={(event) => setAppleTwoFactorCode(event.currentTarget.value)}
|
|
200
|
-
/>
|
|
201
|
-
</label>
|
|
202
|
-
<button
|
|
203
|
-
type="button"
|
|
204
|
-
className="lr-device-install__secondary"
|
|
205
|
-
disabled={!appleTwoFactorCode || deviceInstall.busyAction === 'signing'}
|
|
206
|
-
onClick={() => void deviceInstall.submitAppleTwoFactorCode(appleTwoFactorCode)}
|
|
207
|
-
>
|
|
208
|
-
Submit Apple ID code
|
|
209
|
-
</button>
|
|
210
|
-
</div>
|
|
211
|
-
)}
|
|
212
|
-
{deviceInstall.appleTeams.length > 0 && (
|
|
213
|
-
<label className="lr-device-install__field">
|
|
214
|
-
<span>Apple Developer team</span>
|
|
215
|
-
<select
|
|
216
|
-
value={deviceInstall.selectedAppleTeamID ?? ''}
|
|
217
|
-
onChange={(event) =>
|
|
218
|
-
deviceInstall.setSelectedAppleTeamID(event.currentTarget.value || undefined)
|
|
219
|
-
}
|
|
220
|
-
>
|
|
221
|
-
{deviceInstall.appleTeams.map((team, index) => {
|
|
222
|
-
const teamID =
|
|
223
|
-
team.teamId ??
|
|
224
|
-
(team.providerId === undefined ? undefined : String(team.providerId)) ??
|
|
225
|
-
team.publicProviderId ??
|
|
226
|
-
'';
|
|
227
|
-
return (
|
|
228
|
-
<option key={`${teamID}-${index}`} value={teamID}>
|
|
229
|
-
{team.name ?? 'Apple Developer Team'} {teamID ? `(${teamID})` : ''}
|
|
230
|
-
</option>
|
|
231
|
-
);
|
|
232
|
-
})}
|
|
233
|
-
</select>
|
|
234
|
-
</label>
|
|
235
|
-
)}
|
|
236
|
-
{deviceInstall.appleDevices.length > 0 && (
|
|
237
|
-
<label className="lr-device-install__field">
|
|
238
|
-
<span>Apple Developer devices</span>
|
|
239
|
-
<select
|
|
240
|
-
multiple
|
|
241
|
-
value={deviceInstall.selectedAppleDeviceIDs}
|
|
242
|
-
onChange={(event) =>
|
|
243
|
-
deviceInstall.setSelectedAppleDeviceIDs(
|
|
244
|
-
Array.from(event.currentTarget.selectedOptions).map((option) => option.value),
|
|
245
|
-
)
|
|
246
|
-
}
|
|
247
|
-
>
|
|
248
|
-
{deviceInstall.appleDevices.map((appleDevice) => (
|
|
249
|
-
<option
|
|
250
|
-
key={appleDevice.deviceId ?? appleDevice.deviceNumber}
|
|
251
|
-
value={appleDevice.deviceId ?? ''}
|
|
252
|
-
>
|
|
253
|
-
{appleDevice.name ?? appleDevice.model ?? 'Apple device'} {appleDevice.deviceNumber ?? ''}
|
|
254
|
-
</option>
|
|
255
|
-
))}
|
|
256
|
-
</select>
|
|
257
|
-
</label>
|
|
258
|
-
)}
|
|
259
|
-
{deviceInstall.applePortalSummary && (
|
|
260
|
-
<p className="lr-device-install__hint">
|
|
261
|
-
Found {deviceInstall.applePortalSummary.certificateCount} certificates and{' '}
|
|
262
|
-
{deviceInstall.applePortalSummary.profileCount} provisioning profiles.
|
|
263
|
-
</p>
|
|
264
|
-
)}
|
|
265
|
-
{deviceInstall.hasReusableAppleCertificate && (
|
|
266
|
-
<p className="lr-device-install__hint">
|
|
267
|
-
Reusing the certificate and private key stored in this browser.
|
|
268
|
-
</p>
|
|
269
|
-
)}
|
|
270
|
-
<button
|
|
271
|
-
type="button"
|
|
272
|
-
className="lr-device-install__primary"
|
|
273
|
-
disabled={disabled || !deviceInstall.canPrepareAppleSigningAssets}
|
|
274
|
-
onClick={() => void deviceInstall.prepareAppleSigningAssets()}
|
|
275
|
-
>
|
|
276
|
-
{deviceInstall.appleSigningStatus === 'preparing-assets'
|
|
277
|
-
? 'Preparing signing assets...'
|
|
278
|
-
: 'Generate certificate and profile'}
|
|
279
|
-
</button>
|
|
280
|
-
</div>
|
|
281
|
-
)}
|
|
282
|
-
|
|
283
|
-
{signingSection === 'upload' && (
|
|
284
|
-
<div className="lr-device-install__section-panel">
|
|
285
|
-
<div className="lr-device-install__grid">
|
|
286
|
-
<label className="lr-device-install__field">
|
|
287
|
-
<span>Certificate (.p12)</span>
|
|
288
|
-
<input
|
|
289
|
-
type="file"
|
|
290
|
-
accept=".p12,application/x-pkcs12"
|
|
291
|
-
onChange={(event) => updateSigningFiles('certificateFile', event)}
|
|
292
|
-
/>
|
|
293
|
-
</label>
|
|
294
|
-
<label className="lr-device-install__field">
|
|
295
|
-
<span>Provisioning profile</span>
|
|
296
|
-
<input
|
|
297
|
-
type="file"
|
|
298
|
-
accept=".mobileprovision"
|
|
299
|
-
onChange={(event) => updateSigningFiles('provisioningProfileFile', event)}
|
|
300
|
-
/>
|
|
301
|
-
</label>
|
|
302
|
-
<label className="lr-device-install__field">
|
|
303
|
-
<span>Uploaded .p12 password</span>
|
|
304
|
-
<input
|
|
305
|
-
type="password"
|
|
306
|
-
placeholder="Export password"
|
|
307
|
-
onChange={(event) =>
|
|
308
|
-
deviceInstall.setSigningFiles({ certificatePassword: event.currentTarget.value })
|
|
309
|
-
}
|
|
310
|
-
/>
|
|
311
|
-
</label>
|
|
312
|
-
</div>
|
|
313
|
-
<p className="lr-device-install__hint">
|
|
314
|
-
The provisioning profile will be checked against the connected iPhone before installation.
|
|
315
|
-
</p>
|
|
316
|
-
</div>
|
|
317
|
-
)}
|
|
318
|
-
|
|
319
|
-
{deviceInstall.hasSigningAssets && (
|
|
320
|
-
<p>Signing assets are stored in this browser for the selected bundle and device.</p>
|
|
321
|
-
)}
|
|
322
|
-
</div>
|
|
323
|
-
)}
|
|
324
|
-
|
|
325
|
-
{step.id === 'connect' && (
|
|
326
|
-
<div className="lr-device-install__step-body">
|
|
327
|
-
<p>
|
|
328
|
-
WebUSB works in Chromium browsers on secure origins. Once the build succeeds, connect the iPhone
|
|
329
|
-
over USB, approve the browser permission prompt, then pair this browser.
|
|
330
|
-
</p>
|
|
331
|
-
<div className="lr-device-install__actions">
|
|
332
|
-
<button
|
|
333
|
-
type="button"
|
|
334
|
-
className="lr-device-install__primary"
|
|
335
|
-
disabled={disabled || !deviceInstall.canRequestUSBAccess}
|
|
336
|
-
onClick={() => void deviceInstall.requestUSBAccess()}
|
|
337
|
-
>
|
|
338
|
-
{deviceInstall.busyAction === 'usb' ? 'Selecting iPhone...' : 'Allow USB access'}
|
|
339
|
-
</button>
|
|
340
|
-
<button
|
|
341
|
-
type="button"
|
|
342
|
-
className="lr-device-install__secondary"
|
|
343
|
-
disabled={disabled || !deviceInstall.canPairBrowser}
|
|
344
|
-
onClick={() => void deviceInstall.pairBrowser()}
|
|
345
|
-
>
|
|
346
|
-
{deviceInstall.busyAction === 'pair'
|
|
347
|
-
? 'Pairing...'
|
|
348
|
-
: deviceInstall.pairConfirmationRequired
|
|
349
|
-
? 'Confirm pair record'
|
|
350
|
-
: 'Pair browser'}
|
|
351
|
-
</button>
|
|
352
|
-
</div>
|
|
353
|
-
{deviceInstall.device && (
|
|
354
|
-
<div className="lr-device-install__device">
|
|
355
|
-
{`${deviceInstall.device.productName ?? 'iPhone'} ${
|
|
356
|
-
deviceInstall.device.serialNumber ?? ''
|
|
357
|
-
}`.trim()}
|
|
358
|
-
</div>
|
|
359
|
-
)}
|
|
360
|
-
{deviceInstall.pairConfirmationRequired && (
|
|
361
|
-
<p>
|
|
362
|
-
Unlock the iPhone and tap <strong>Trust</strong> in the system dialog, then confirm the pair
|
|
363
|
-
record.
|
|
364
|
-
</p>
|
|
365
|
-
)}
|
|
366
|
-
<p>
|
|
367
|
-
{deviceInstall.hasPairRecord
|
|
368
|
-
? 'Pair record is stored locally. Continue to installation.'
|
|
369
|
-
: 'Pair this browser once before installing.'}
|
|
370
|
-
</p>
|
|
371
|
-
</div>
|
|
372
|
-
)}
|
|
373
|
-
|
|
374
|
-
{step.id === 'build' && (
|
|
375
|
-
<div className="lr-device-install__step-body">
|
|
376
|
-
<div className="lr-device-install__checklist">
|
|
377
|
-
<StatusLine label="Signing assets" ready={deviceInstall.hasSigningInputs} />
|
|
378
|
-
<StatusLine label="Device build" ready={deviceInstall.buildStatus === 'succeeded' ? true : undefined} pendingText="Not started" />
|
|
379
|
-
</div>
|
|
380
|
-
<button
|
|
381
|
-
type="button"
|
|
382
|
-
className="lr-device-install__primary"
|
|
383
|
-
disabled={disabled || !deviceInstall.canBuild}
|
|
384
|
-
onClick={() => void deviceInstall.startDeviceBuild()}
|
|
385
|
-
>
|
|
386
|
-
{deviceInstall.busyAction === 'build' ? 'Starting build...' : 'Start device build'}
|
|
387
|
-
</button>
|
|
388
|
-
<details
|
|
389
|
-
className="lr-device-install__build-logs"
|
|
390
|
-
open={deviceInstall.buildLogPanelOpen}
|
|
391
|
-
onToggle={(event) => deviceInstall.setBuildLogPanelOpen(event.currentTarget.open)}
|
|
392
|
-
>
|
|
393
|
-
<summary>Build logs ({deviceInstall.buildStatus})</summary>
|
|
394
|
-
<pre>
|
|
395
|
-
{deviceInstall.buildLogs.length > 0
|
|
396
|
-
? deviceInstall.buildLogs
|
|
397
|
-
.filter((line) => line.type !== 'meta')
|
|
398
|
-
.map((line) => line.data)
|
|
399
|
-
.join('\n')
|
|
400
|
-
: 'Build logs will appear here while the device build is running.'}
|
|
401
|
-
</pre>
|
|
402
|
-
</details>
|
|
403
|
-
</div>
|
|
404
|
-
)}
|
|
405
|
-
|
|
406
|
-
{step.id === 'install' && (
|
|
407
|
-
<div className="lr-device-install__step-body">
|
|
408
|
-
<button
|
|
409
|
-
type="button"
|
|
410
|
-
className="lr-device-install__primary"
|
|
411
|
-
disabled={disabled || !deviceInstall.canInstall}
|
|
412
|
-
onClick={() => void deviceInstall.startInstallation()}
|
|
413
|
-
>
|
|
414
|
-
{deviceInstall.busyAction === 'install' ? 'Installing...' : 'Install last build'}
|
|
415
|
-
</button>
|
|
416
|
-
<button type="button" className="lr-device-install__secondary" onClick={deviceInstall.stopRelay}>
|
|
417
|
-
Stop relay
|
|
418
|
-
</button>
|
|
419
|
-
</div>
|
|
420
|
-
)}
|
|
421
|
-
</StepCard>
|
|
422
|
-
))}
|
|
423
|
-
</div>
|
|
424
|
-
|
|
425
|
-
<footer className="lr-device-install__logs">
|
|
426
|
-
<h3>Progress</h3>
|
|
427
|
-
<ol>
|
|
428
|
-
{deviceInstall.logs.map((entry, index) => (
|
|
429
|
-
<li key={`${index}-${entry.slice(0, 24)}`}>{entry}</li>
|
|
430
|
-
))}
|
|
431
|
-
</ol>
|
|
432
|
-
</footer>
|
|
433
|
-
</section>
|
|
434
|
-
</div>
|
|
435
|
-
)}
|
|
436
|
-
</div>
|
|
437
|
-
);
|
|
438
|
-
}
|
|
439
|
-
|
|
440
|
-
function StepCard({
|
|
441
|
-
index,
|
|
442
|
-
step,
|
|
443
|
-
active,
|
|
444
|
-
open,
|
|
445
|
-
status,
|
|
446
|
-
onToggle,
|
|
447
|
-
children,
|
|
448
|
-
}: {
|
|
449
|
-
index: number;
|
|
450
|
-
step: { id: DeviceInstallStep; title: string; description: string };
|
|
451
|
-
active: boolean;
|
|
452
|
-
open: boolean;
|
|
453
|
-
status: DeviceInstallStepStatus;
|
|
454
|
-
onToggle: () => void;
|
|
455
|
-
children: ReactNode;
|
|
456
|
-
}) {
|
|
457
|
-
return (
|
|
458
|
-
<article className={clsx('lr-device-install__step', active && 'lr-device-install__step--active')}>
|
|
459
|
-
<button
|
|
460
|
-
type="button"
|
|
461
|
-
className="lr-device-install__step-header"
|
|
462
|
-
aria-expanded={open}
|
|
463
|
-
onClick={onToggle}
|
|
464
|
-
>
|
|
465
|
-
<div className="lr-device-install__step-number">{index}</div>
|
|
466
|
-
<div>
|
|
467
|
-
<h3>{step.title}</h3>
|
|
468
|
-
<p>{step.description}</p>
|
|
469
|
-
</div>
|
|
470
|
-
<span className={clsx('lr-device-install__status', `lr-device-install__status--${status}`)}>
|
|
471
|
-
{status === 'complete' ? '✓ Completed' : status}
|
|
472
|
-
</span>
|
|
473
|
-
</button>
|
|
474
|
-
{open && children}
|
|
475
|
-
</article>
|
|
476
|
-
);
|
|
477
|
-
}
|
|
478
|
-
|
|
479
|
-
function StatusLine({
|
|
480
|
-
label,
|
|
481
|
-
ready,
|
|
482
|
-
pendingText = 'Not ready',
|
|
483
|
-
}: {
|
|
484
|
-
label: string;
|
|
485
|
-
ready?: boolean;
|
|
486
|
-
pendingText?: string;
|
|
487
|
-
}) {
|
|
488
|
-
const text = ready === undefined ? pendingText : ready ? 'Ready' : 'Needs attention';
|
|
489
|
-
return (
|
|
490
|
-
<div className="lr-device-install__check-row">
|
|
491
|
-
<span>{label}</span>
|
|
492
|
-
<strong>{text}</strong>
|
|
493
|
-
</div>
|
|
494
|
-
);
|
|
495
|
-
}
|