@limrun/ui 0.9.0-rc.2 → 0.9.0-rc.5

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.
Files changed (78) hide show
  1. package/README.md +0 -9
  2. package/dist/components/inspect-overlay.d.ts +33 -0
  3. package/dist/components/remote-control.d.ts +86 -0
  4. package/dist/core/ax-fetcher.d.ts +49 -0
  5. package/dist/core/ax-tree.d.ts +99 -0
  6. package/dist/index.cjs +1 -1
  7. package/dist/index.css +1 -1
  8. package/dist/index.d.ts +3 -2
  9. package/dist/index.js +1491 -777
  10. package/package.json +8 -17
  11. package/src/components/inspect-overlay.css +223 -0
  12. package/src/components/inspect-overlay.tsx +437 -0
  13. package/src/components/remote-control.tsx +547 -9
  14. package/src/core/ax-fetcher.test.ts +418 -0
  15. package/src/core/ax-fetcher.ts +377 -0
  16. package/src/core/ax-tree.test.ts +491 -0
  17. package/src/core/ax-tree.ts +416 -0
  18. package/src/demo.tsx +93 -10
  19. package/src/index.ts +17 -2
  20. package/vite.config.ts +2 -6
  21. package/vitest.config.ts +23 -0
  22. package/dist/components/device-install/device-install-dialog.d.ts +0 -5
  23. package/dist/components/device-install/index.d.ts +0 -2
  24. package/dist/core/device-install/apple/client.d.ts +0 -17
  25. package/dist/core/device-install/apple/crypto.d.ts +0 -20
  26. package/dist/core/device-install/apple/gsa-srp.d.ts +0 -26
  27. package/dist/core/device-install/apple/index.d.ts +0 -5
  28. package/dist/core/device-install/apple/provisioning.d.ts +0 -161
  29. package/dist/core/device-install/apple/relay.d.ts +0 -29
  30. package/dist/core/device-install/index.d.ts +0 -4
  31. package/dist/core/device-install/operations/index.d.ts +0 -6
  32. package/dist/core/device-install/operations/limbuild-client.d.ts +0 -28
  33. package/dist/core/device-install/operations/operations.d.ts +0 -32
  34. package/dist/core/device-install/operations/relay-client.d.ts +0 -25
  35. package/dist/core/device-install/operations/relay-protocol.d.ts +0 -27
  36. package/dist/core/device-install/operations/usbmux.d.ts +0 -32
  37. package/dist/core/device-install/operations/webusb.d.ts +0 -21
  38. package/dist/core/device-install/storage/browser-storage.d.ts +0 -43
  39. package/dist/core/device-install/storage/index.d.ts +0 -1
  40. package/dist/core/device-install/types.d.ts +0 -48
  41. package/dist/device-install/index.cjs +0 -1
  42. package/dist/device-install/index.d.ts +0 -3
  43. package/dist/device-install/index.js +0 -77
  44. package/dist/device-install/react.cjs +0 -1
  45. package/dist/device-install/react.d.ts +0 -1
  46. package/dist/device-install/react.js +0 -4
  47. package/dist/device-install-dialog-CSwQgbBm.js +0 -2
  48. package/dist/device-install-dialog-nThj775b.mjs +0 -395
  49. package/dist/device-install-dialog.css +0 -1
  50. package/dist/hooks/index.d.ts +0 -1
  51. package/dist/hooks/use-device-install.d.ts +0 -70
  52. package/dist/use-device-install-C1uVac59.mjs +0 -13541
  53. package/dist/use-device-install-Ca4jcVKU.js +0 -31
  54. package/src/components/device-install/device-install-dialog.css +0 -244
  55. package/src/components/device-install/device-install-dialog.tsx +0 -432
  56. package/src/components/device-install/index.ts +0 -2
  57. package/src/core/device-install/apple/client.ts +0 -152
  58. package/src/core/device-install/apple/crypto.ts +0 -202
  59. package/src/core/device-install/apple/gsa-srp.ts +0 -127
  60. package/src/core/device-install/apple/index.ts +0 -5
  61. package/src/core/device-install/apple/provisioning.ts +0 -298
  62. package/src/core/device-install/apple/relay.ts +0 -221
  63. package/src/core/device-install/index.ts +0 -4
  64. package/src/core/device-install/operations/index.ts +0 -6
  65. package/src/core/device-install/operations/limbuild-client.ts +0 -104
  66. package/src/core/device-install/operations/operations.ts +0 -217
  67. package/src/core/device-install/operations/relay-client.ts +0 -255
  68. package/src/core/device-install/operations/relay-protocol.ts +0 -71
  69. package/src/core/device-install/operations/usbmux.ts +0 -270
  70. package/src/core/device-install/operations/webusb-dom.d.ts +0 -54
  71. package/src/core/device-install/operations/webusb.ts +0 -105
  72. package/src/core/device-install/storage/browser-storage.ts +0 -251
  73. package/src/core/device-install/storage/index.ts +0 -1
  74. package/src/core/device-install/types.ts +0 -65
  75. package/src/device-install/index.ts +0 -3
  76. package/src/device-install/react.ts +0 -1
  77. package/src/hooks/index.ts +0 -1
  78. package/src/hooks/use-device-install.ts +0 -1067
@@ -1,244 +0,0 @@
1
- .lr-device-install {
2
- font-family:
3
- Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
4
- }
5
-
6
- .lr-device-install__trigger,
7
- .lr-device-install__primary,
8
- .lr-device-install__secondary,
9
- .lr-device-install__icon-button {
10
- border: 1px solid #d1d5db;
11
- border-radius: 8px;
12
- cursor: pointer;
13
- font: inherit;
14
- }
15
-
16
- .lr-device-install__trigger,
17
- .lr-device-install__primary {
18
- background: #111827;
19
- color: #ffffff;
20
- padding: 8px 12px;
21
- }
22
-
23
- .lr-device-install__secondary,
24
- .lr-device-install__icon-button {
25
- background: #ffffff;
26
- color: #111827;
27
- padding: 8px 12px;
28
- }
29
-
30
- .lr-device-install__trigger:disabled,
31
- .lr-device-install__primary:disabled,
32
- .lr-device-install__secondary:disabled {
33
- cursor: not-allowed;
34
- opacity: 0.55;
35
- }
36
-
37
- .lr-device-install__backdrop {
38
- align-items: center;
39
- background: rgb(17 24 39 / 0.55);
40
- display: flex;
41
- inset: 0;
42
- justify-content: center;
43
- padding: 24px;
44
- position: fixed;
45
- z-index: 50;
46
- }
47
-
48
- .lr-device-install__dialog {
49
- background: #ffffff;
50
- border-radius: 16px;
51
- box-shadow: 0 24px 80px rgb(15 23 42 / 0.28);
52
- color: #111827;
53
- max-height: min(90vh, 900px);
54
- max-width: 880px;
55
- overflow: auto;
56
- padding: 24px;
57
- width: 100%;
58
- }
59
-
60
- .lr-device-install__header {
61
- align-items: flex-start;
62
- display: flex;
63
- gap: 16px;
64
- justify-content: space-between;
65
- margin-bottom: 18px;
66
- }
67
-
68
- .lr-device-install__header h2,
69
- .lr-device-install__step h3,
70
- .lr-device-install__logs h3 {
71
- margin: 0;
72
- }
73
-
74
- .lr-device-install__header p,
75
- .lr-device-install__step p,
76
- .lr-device-install__logs {
77
- color: #4b5563;
78
- }
79
-
80
- .lr-device-install__error {
81
- background: #fef2f2;
82
- border: 1px solid #fecaca;
83
- border-radius: 10px;
84
- color: #991b1b;
85
- margin-bottom: 14px;
86
- padding: 10px 12px;
87
- }
88
-
89
- .lr-device-install__steps {
90
- display: grid;
91
- gap: 12px;
92
- }
93
-
94
- .lr-device-install__step {
95
- border: 1px solid #e5e7eb;
96
- border-radius: 12px;
97
- padding: 14px;
98
- }
99
-
100
- .lr-device-install__step--active {
101
- border-color: #111827;
102
- }
103
-
104
- .lr-device-install__step-header {
105
- align-items: flex-start;
106
- display: grid;
107
- gap: 12px;
108
- grid-template-columns: auto 1fr auto;
109
- }
110
-
111
- .lr-device-install__step-number {
112
- align-items: center;
113
- background: #111827;
114
- border-radius: 999px;
115
- color: #ffffff;
116
- display: flex;
117
- font-size: 13px;
118
- font-weight: 700;
119
- height: 28px;
120
- justify-content: center;
121
- width: 28px;
122
- }
123
-
124
- .lr-device-install__status {
125
- border-radius: 999px;
126
- font-size: 12px;
127
- padding: 3px 8px;
128
- text-transform: capitalize;
129
- }
130
-
131
- .lr-device-install__status--idle {
132
- background: #f3f4f6;
133
- color: #4b5563;
134
- }
135
-
136
- .lr-device-install__status--active {
137
- background: #dbeafe;
138
- color: #1d4ed8;
139
- }
140
-
141
- .lr-device-install__status--complete {
142
- background: #dcfce7;
143
- color: #166534;
144
- }
145
-
146
- .lr-device-install__status--error {
147
- background: #fee2e2;
148
- color: #991b1b;
149
- }
150
-
151
- .lr-device-install__step-body {
152
- display: grid;
153
- gap: 12px;
154
- margin-top: 12px;
155
- }
156
-
157
- .lr-device-install__grid {
158
- display: grid;
159
- gap: 10px;
160
- grid-template-columns: repeat(3, minmax(0, 1fr));
161
- }
162
-
163
- .lr-device-install__field {
164
- display: grid;
165
- gap: 6px;
166
- }
167
-
168
- .lr-device-install__field span {
169
- color: #374151;
170
- font-size: 13px;
171
- font-weight: 600;
172
- }
173
-
174
- .lr-device-install__field input {
175
- border: 1px solid #d1d5db;
176
- border-radius: 8px;
177
- font: inherit;
178
- padding: 8px 10px;
179
- }
180
-
181
- .lr-device-install__build-logs {
182
- border: 1px solid #e5e7eb;
183
- border-radius: 10px;
184
- overflow: hidden;
185
- }
186
-
187
- .lr-device-install__build-logs summary {
188
- cursor: pointer;
189
- font-weight: 600;
190
- padding: 10px 12px;
191
- }
192
-
193
- .lr-device-install__build-logs pre {
194
- background: #0f172a;
195
- color: #e5e7eb;
196
- margin: 0;
197
- max-height: 240px;
198
- overflow: auto;
199
- padding: 12px;
200
- white-space: pre-wrap;
201
- }
202
-
203
- .lr-device-install__device {
204
- background: #f9fafb;
205
- border: 1px solid #e5e7eb;
206
- border-radius: 10px;
207
- padding: 10px;
208
- }
209
-
210
- .lr-device-install__logs {
211
- border-top: 1px solid #e5e7eb;
212
- margin-top: 18px;
213
- padding-top: 14px;
214
- }
215
-
216
- .lr-device-install__logs ol {
217
- display: grid;
218
- gap: 6px;
219
- list-style: none;
220
- margin: 8px 0 0;
221
- max-height: 160px;
222
- overflow: auto;
223
- padding: 0;
224
- }
225
-
226
- .lr-device-install__logs li {
227
- font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', monospace;
228
- font-size: 12px;
229
- white-space: pre-wrap;
230
- }
231
-
232
- @media (max-width: 760px) {
233
- .lr-device-install__grid {
234
- grid-template-columns: 1fr;
235
- }
236
-
237
- .lr-device-install__step-header {
238
- grid-template-columns: auto 1fr;
239
- }
240
-
241
- .lr-device-install__status {
242
- grid-column: 2;
243
- }
244
- }
@@ -1,432 +0,0 @@
1
- import { 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: 'build',
14
- title: 'Start a device build',
15
- description: 'Upload signing assets if needed, then follow the live build logs until the device build succeeds.',
16
- },
17
- {
18
- id: 'usb',
19
- title: 'Access USB procedures',
20
- description: 'Allow WebUSB access to the connected iPhone from a Chromium browser on a secure origin.',
21
- },
22
- {
23
- id: 'pair',
24
- title: 'Pair with this browser',
25
- description: 'Pair once and store the pair record locally so future installs can reuse it.',
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
- export function DeviceInstallDialog({
35
- disabled,
36
- ...hookOptions
37
- }: DeviceInstallDialogProps) {
38
- const [open, setOpen] = useState(false);
39
- const [appleAccountName, setAppleAccountName] = useState('');
40
- const [applePassword, setApplePassword] = useState('');
41
- const [appleTwoFactorCode, setAppleTwoFactorCode] = useState('');
42
- const dialogTitleId = useId();
43
- const deviceInstall = useDeviceInstall(hookOptions);
44
-
45
- const updateSigningFiles = (field: 'certificateFile' | 'provisioningProfileFile', event: ChangeEvent<HTMLInputElement>) => {
46
- deviceInstall.setSigningFiles({
47
- [field]: event.currentTarget.files?.[0],
48
- });
49
- };
50
-
51
- return (
52
- <div className="lr-device-install">
53
- <button
54
- type="button"
55
- className="lr-device-install__trigger"
56
- disabled={disabled || !hookOptions.apiUrl}
57
- onClick={() => setOpen(true)}
58
- >
59
- Install to iPhone
60
- </button>
61
-
62
- {open && (
63
- <div className="lr-device-install__backdrop" role="presentation">
64
- <section
65
- aria-labelledby={dialogTitleId}
66
- aria-modal="true"
67
- className="lr-device-install__dialog"
68
- role="dialog"
69
- >
70
- <header className="lr-device-install__header">
71
- <div>
72
- <h2 id={dialogTitleId}>Install to a real iPhone</h2>
73
- <p>Follow each step to build, authorize USB, pair, and install from this browser.</p>
74
- </div>
75
- <button type="button" className="lr-device-install__icon-button" onClick={() => setOpen(false)}>
76
- Close
77
- </button>
78
- </header>
79
-
80
- {deviceInstall.error && <div className="lr-device-install__error">{deviceInstall.error}</div>}
81
-
82
- <div className="lr-device-install__steps">
83
- {steps.map((step, index) => (
84
- <StepCard
85
- key={step.id}
86
- index={index + 1}
87
- step={step}
88
- active={deviceInstall.currentStep === step.id}
89
- status={deviceInstall.stepStatuses[step.id]}
90
- >
91
- {step.id === 'build' && (
92
- <div className="lr-device-install__step-body">
93
- <div className="lr-device-install__grid">
94
- <label className="lr-device-install__field">
95
- <span>Apple ID</span>
96
- <input
97
- type="email"
98
- autoComplete="username"
99
- placeholder="name@example.com"
100
- value={appleAccountName}
101
- onChange={(event) => setAppleAccountName(event.currentTarget.value)}
102
- />
103
- </label>
104
- <label className="lr-device-install__field">
105
- <span>Apple ID password</span>
106
- <input
107
- type="password"
108
- autoComplete="current-password"
109
- placeholder="Password stays in this browser"
110
- value={applePassword}
111
- onChange={(event) => setApplePassword(event.currentTarget.value)}
112
- />
113
- </label>
114
- <label className="lr-device-install__field">
115
- <span>Generated .p12 password</span>
116
- <input
117
- type="password"
118
- placeholder="Used when exporting Apple certificate"
119
- onChange={(event) =>
120
- deviceInstall.setSigningFiles({ certificatePassword: event.currentTarget.value })
121
- }
122
- />
123
- </label>
124
- </div>
125
- <div className="lr-device-install__actions">
126
- <button
127
- type="button"
128
- className="lr-device-install__secondary"
129
- disabled={
130
- disabled ||
131
- !hookOptions.apiUrl ||
132
- !appleAccountName ||
133
- !applePassword ||
134
- deviceInstall.busyAction === 'build'
135
- }
136
- onClick={() =>
137
- void deviceInstall.startAppleIDLogin({
138
- accountName: appleAccountName,
139
- password: applePassword,
140
- })
141
- }
142
- >
143
- {deviceInstall.appleSigningStatus === 'authenticating'
144
- ? 'Signing in...'
145
- : 'Sign in with Apple ID'}
146
- </button>
147
- <span className="lr-device-install__hint">
148
- Apple password is used only by browser-side SRP. Status: {deviceInstall.appleSigningStatus}
149
- </span>
150
- </div>
151
- {deviceInstall.appleSigningStatus === 'two-factor-required' && (
152
- <div className="lr-device-install__grid">
153
- <label className="lr-device-install__field">
154
- <span>Two-factor code</span>
155
- <input
156
- type="text"
157
- inputMode="numeric"
158
- autoComplete="one-time-code"
159
- value={appleTwoFactorCode}
160
- onChange={(event) => setAppleTwoFactorCode(event.currentTarget.value)}
161
- />
162
- </label>
163
- <button
164
- type="button"
165
- className="lr-device-install__secondary"
166
- disabled={!appleTwoFactorCode || deviceInstall.busyAction === 'build'}
167
- onClick={() => void deviceInstall.submitAppleTwoFactorCode(appleTwoFactorCode)}
168
- >
169
- Submit Apple ID code
170
- </button>
171
- </div>
172
- )}
173
- {deviceInstall.appleTeams.length > 0 && (
174
- <>
175
- <label className="lr-device-install__field">
176
- <span>Apple Developer team</span>
177
- <select
178
- value={deviceInstall.selectedAppleTeamID ?? ''}
179
- onChange={(event) =>
180
- deviceInstall.setSelectedAppleTeamID(event.currentTarget.value || undefined)
181
- }
182
- >
183
- {deviceInstall.appleTeams.map((team, index) => {
184
- const teamID =
185
- team.teamId ??
186
- (team.providerId === undefined ? undefined : String(team.providerId)) ??
187
- team.publicProviderId ??
188
- '';
189
- return (
190
- <option key={`${teamID}-${index}`} value={teamID}>
191
- {team.name ?? 'Apple Developer Team'} {teamID ? `(${teamID})` : ''}
192
- </option>
193
- );
194
- })}
195
- </select>
196
- </label>
197
- {deviceInstall.applePortalSummary && (
198
- <p className="lr-device-install__hint">
199
- Found {deviceInstall.applePortalSummary.certificateCount} certificates and{' '}
200
- {deviceInstall.applePortalSummary.profileCount} provisioning profiles.
201
- </p>
202
- )}
203
- </>
204
- )}
205
- {deviceInstall.appleAppIDs.length > 0 && (
206
- <label className="lr-device-install__field">
207
- <span>Bundle ID</span>
208
- <select
209
- value={deviceInstall.appleBundleID}
210
- onChange={(event) => deviceInstall.setAppleBundleID(event.currentTarget.value)}
211
- >
212
- {deviceInstall.appleAppIDs.map((appID, index) => {
213
- const bundleID = appID.identifier ?? appID.bundleId ?? '';
214
- return (
215
- <option key={`${bundleID}-${index}`} value={bundleID}>
216
- {appID.name ?? bundleID} {bundleID ? `(${bundleID})` : ''}
217
- </option>
218
- );
219
- })}
220
- </select>
221
- </label>
222
- )}
223
- <div className="lr-device-install__grid">
224
- <label className="lr-device-install__field">
225
- <span>Certificate (.p12)</span>
226
- <input
227
- type="file"
228
- accept=".p12,application/x-pkcs12"
229
- onChange={(event) => updateSigningFiles('certificateFile', event)}
230
- />
231
- </label>
232
- <label className="lr-device-install__field">
233
- <span>Provisioning profile</span>
234
- <input
235
- type="file"
236
- accept=".mobileprovision"
237
- onChange={(event) => updateSigningFiles('provisioningProfileFile', event)}
238
- />
239
- </label>
240
- <label className="lr-device-install__field">
241
- <span>Uploaded .p12 password</span>
242
- <input
243
- type="password"
244
- placeholder="Export password"
245
- onChange={(event) =>
246
- deviceInstall.setSigningFiles({ certificatePassword: event.currentTarget.value })
247
- }
248
- />
249
- </label>
250
- </div>
251
- <button
252
- type="button"
253
- className="lr-device-install__primary"
254
- disabled={disabled || !deviceInstall.canBuild}
255
- onClick={() => void deviceInstall.startDeviceBuild()}
256
- >
257
- {deviceInstall.busyAction === 'build' ? 'Starting build...' : 'Start device build'}
258
- </button>
259
- <details
260
- className="lr-device-install__build-logs"
261
- open={deviceInstall.buildLogPanelOpen}
262
- onToggle={(event) => deviceInstall.setBuildLogPanelOpen(event.currentTarget.open)}
263
- >
264
- <summary>Build logs ({deviceInstall.buildStatus})</summary>
265
- <pre>
266
- {deviceInstall.buildLogs.length > 0
267
- ? deviceInstall.buildLogs
268
- .filter((line) => line.type !== 'meta')
269
- .map((line) => line.data)
270
- .join('\n')
271
- : 'Build logs will appear here while the device build is running.'}
272
- </pre>
273
- </details>
274
- </div>
275
- )}
276
-
277
- {step.id === 'usb' && (
278
- <div className="lr-device-install__step-body">
279
- <p>
280
- WebUSB works in Chromium browsers on secure origins. Connect the iPhone over USB and approve
281
- the browser permission prompt.
282
- </p>
283
- <button
284
- type="button"
285
- className="lr-device-install__primary"
286
- disabled={disabled || !deviceInstall.canRequestUSBAccess}
287
- onClick={() => void deviceInstall.requestUSBAccess()}
288
- >
289
- {deviceInstall.busyAction === 'usb' ? 'Selecting iPhone...' : 'Allow USB access'}
290
- </button>
291
- {deviceInstall.device && (
292
- <div className="lr-device-install__device">
293
- {`${deviceInstall.device.productName ?? 'iPhone'} ${
294
- deviceInstall.device.serialNumber ?? ''
295
- }`.trim()}
296
- </div>
297
- )}
298
- {deviceInstall.appleDevices.length > 0 && (
299
- <label className="lr-device-install__field">
300
- <span>Apple Developer devices</span>
301
- <select
302
- multiple
303
- value={deviceInstall.selectedAppleDeviceIDs}
304
- onChange={(event) =>
305
- deviceInstall.setSelectedAppleDeviceIDs(
306
- Array.from(event.currentTarget.selectedOptions).map((option) => option.value),
307
- )
308
- }
309
- >
310
- {deviceInstall.appleDevices.map((appleDevice) => (
311
- <option key={appleDevice.deviceId ?? appleDevice.deviceNumber} value={appleDevice.deviceId ?? ''}>
312
- {appleDevice.name ?? appleDevice.model ?? 'Apple device'} {appleDevice.deviceNumber ?? ''}
313
- </option>
314
- ))}
315
- </select>
316
- </label>
317
- )}
318
- {deviceInstall.device && !deviceInstall.connectedAppleDeviceRegistered && (
319
- <button
320
- type="button"
321
- className="lr-device-install__secondary"
322
- disabled={disabled || !!deviceInstall.busyAction}
323
- onClick={() => void deviceInstall.registerConnectedAppleDevice()}
324
- >
325
- Register connected iPhone
326
- </button>
327
- )}
328
- <button
329
- type="button"
330
- className="lr-device-install__secondary"
331
- disabled={disabled || !deviceInstall.canPrepareAppleSigningAssets}
332
- onClick={() => void deviceInstall.prepareAppleSigningAssets()}
333
- >
334
- {deviceInstall.appleSigningStatus === 'preparing-assets'
335
- ? 'Preparing signing assets...'
336
- : 'Generate certificate and profile'}
337
- </button>
338
- {deviceInstall.hasSigningAssets && (
339
- <p>Signing assets are stored in this browser for the selected bundle and device.</p>
340
- )}
341
- </div>
342
- )}
343
-
344
- {step.id === 'pair' && (
345
- <div className="lr-device-install__step-body">
346
- {deviceInstall.pairConfirmationRequired && (
347
- <p>
348
- Unlock the iPhone and tap <strong>Trust</strong> in the system dialog, then confirm the pair
349
- record.
350
- </p>
351
- )}
352
- <button
353
- type="button"
354
- className="lr-device-install__primary"
355
- disabled={disabled || !deviceInstall.canPairBrowser}
356
- onClick={() => void deviceInstall.pairBrowser()}
357
- >
358
- {deviceInstall.busyAction === 'pair'
359
- ? 'Pairing...'
360
- : deviceInstall.pairConfirmationRequired
361
- ? 'Confirm pair record'
362
- : 'Pair browser'}
363
- </button>
364
- <p>
365
- {deviceInstall.hasPairRecord
366
- ? 'Pair record is stored locally. Installation is available.'
367
- : 'Pair this browser once before installing.'}
368
- </p>
369
- </div>
370
- )}
371
-
372
- {step.id === 'install' && (
373
- <div className="lr-device-install__step-body">
374
- <button
375
- type="button"
376
- className="lr-device-install__primary"
377
- disabled={disabled || !deviceInstall.canInstall}
378
- onClick={() => void deviceInstall.startInstallation()}
379
- >
380
- {deviceInstall.busyAction === 'install' ? 'Installing...' : 'Install last build'}
381
- </button>
382
- <button type="button" className="lr-device-install__secondary" onClick={deviceInstall.stopRelay}>
383
- Stop relay
384
- </button>
385
- </div>
386
- )}
387
- </StepCard>
388
- ))}
389
- </div>
390
-
391
- <footer className="lr-device-install__logs">
392
- <h3>Progress</h3>
393
- <ol>
394
- {deviceInstall.logs.map((entry, index) => (
395
- <li key={`${index}-${entry.slice(0, 24)}`}>{entry}</li>
396
- ))}
397
- </ol>
398
- </footer>
399
- </section>
400
- </div>
401
- )}
402
- </div>
403
- );
404
- }
405
-
406
- function StepCard({
407
- index,
408
- step,
409
- active,
410
- status,
411
- children,
412
- }: {
413
- index: number;
414
- step: { id: DeviceInstallStep; title: string; description: string };
415
- active: boolean;
416
- status: DeviceInstallStepStatus;
417
- children: ReactNode;
418
- }) {
419
- return (
420
- <article className={clsx('lr-device-install__step', active && 'lr-device-install__step--active')}>
421
- <div className="lr-device-install__step-header">
422
- <div className="lr-device-install__step-number">{index}</div>
423
- <div>
424
- <h3>{step.title}</h3>
425
- <p>{step.description}</p>
426
- </div>
427
- <span className={clsx('lr-device-install__status', `lr-device-install__status--${status}`)}>{status}</span>
428
- </div>
429
- {children}
430
- </article>
431
- );
432
- }
@@ -1,2 +0,0 @@
1
- export { DeviceInstallDialog, DeviceInstallDialog as DeviceInstallRelay } from './device-install-dialog';
2
- export type { DeviceInstallDialogProps } from './device-install-dialog';