Skip to content

Commit 50de53d

Browse files
committed
refactor: replace react with web components
1 parent c9f39fd commit 50de53d

File tree

15 files changed

+400
-577
lines changed

15 files changed

+400
-577
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,4 +13,4 @@ bun dev
1313

1414
Open [http://localhost:5173](http://localhost:5173) with your browser to see the result.
1515

16-
You can start editing the page by modifying `src/app/index.jsx`. The page auto-updates as you edit the file.
16+
You can start editing the page by modifying `src/app/index.js`. The page auto-updates as you edit the file.

index.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
</head>
1313
<body>
1414
<div id="root"></div>
15-
<script type="module" src="./src/main.jsx"></script>
15+
<script type="module" src="./src/main.js"></script>
1616
<script
1717
defer
1818
data-domain="flash.comma.ai"

package.json

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,17 +16,10 @@
1616
"@commaai/qdl": "git+https://github.com/commaai/qdl.js.git#52021f0b1ace58673ebca1fae740f6900ebff707",
1717
"@fontsource-variable/inter": "^5.2.5",
1818
"@fontsource-variable/jetbrains-mono": "^5.2.5",
19-
"react": "^18.3.1",
20-
"react-dom": "^18.3.1",
2119
"xz-decompress": "^0.2.2"
2220
},
2321
"devDependencies": {
2422
"@tailwindcss/typography": "^0.5.16",
25-
"@testing-library/jest-dom": "^6.6.3",
26-
"@testing-library/react": "^16.3.0",
27-
"@types/react": "^18.3.20",
28-
"@types/react-dom": "^18.3.6",
29-
"@vitejs/plugin-react": "^4.3.4",
3023
"autoprefixer": "10.4.21",
3124
"jsdom": "^26.0.0",
3225
"postcss": "^8.5.3",

src/app/App.test.jsx

Lines changed: 0 additions & 10 deletions
This file was deleted.

src/app/Flash.js

Lines changed: 227 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,227 @@
1+
import { FlashManager, StepCode, ErrorCode } from '../utils/manager'
2+
import { createImageManager } from '../utils/image'
3+
import { isLinux } from '../utils/platform'
4+
import config from '../config'
5+
6+
import bolt from '../assets/bolt.svg'
7+
import cable from '../assets/cable.svg'
8+
import deviceExclamation from '../assets/device_exclamation_c3.svg'
9+
import deviceQuestion from '../assets/device_question_c3.svg'
10+
import done from '../assets/done.svg'
11+
import exclamation from '../assets/exclamation.svg'
12+
import systemUpdate from '../assets/system_update_c3.svg'
13+
14+
const steps = {
15+
[StepCode.INITIALIZING]: {
16+
status: 'Initializing...',
17+
bgColor: 'bg-gray-400 dark:bg-gray-700',
18+
icon: bolt,
19+
},
20+
[StepCode.READY]: {
21+
status: 'Tap to start',
22+
bgColor: 'bg-[#51ff00]',
23+
icon: bolt,
24+
iconStyle: '',
25+
},
26+
[StepCode.CONNECTING]: {
27+
status: 'Waiting for connection',
28+
description: 'Follow the instructions to connect your device to your computer',
29+
bgColor: 'bg-yellow-500',
30+
icon: cable,
31+
},
32+
[StepCode.REPAIR_PARTITION_TABLES]: {
33+
status: 'Repairing partition tables...',
34+
description: 'Do not unplug your device until the process is complete',
35+
bgColor: 'bg-lime-400',
36+
icon: systemUpdate,
37+
},
38+
[StepCode.ERASE_DEVICE]: {
39+
status: 'Erasing device...',
40+
description: 'Do not unplug your device until the process is complete',
41+
bgColor: 'bg-lime-400',
42+
icon: systemUpdate,
43+
},
44+
[StepCode.FLASH_SYSTEM]: {
45+
status: 'Flashing device...',
46+
description: 'Do not unplug your device until the process is complete',
47+
bgColor: 'bg-lime-400',
48+
icon: systemUpdate,
49+
},
50+
[StepCode.FINALIZING]: {
51+
status: 'Finalizing...',
52+
description: 'Do not unplug your device until the process is complete',
53+
bgColor: 'bg-lime-400',
54+
icon: systemUpdate,
55+
},
56+
[StepCode.DONE]: {
57+
status: 'Done',
58+
description: 'Your device was flashed successfully. It should now boot into the openpilot setup.',
59+
bgColor: 'bg-green-500',
60+
icon: done,
61+
},
62+
}
63+
64+
const errors = {
65+
[ErrorCode.UNKNOWN]: {
66+
status: 'Unknown error',
67+
description: 'An unknown error has occurred. Unplug your device, restart your browser and try again.',
68+
bgColor: 'bg-red-500',
69+
icon: exclamation,
70+
},
71+
[ErrorCode.REQUIREMENTS_NOT_MET]: {
72+
status: 'Requirements not met',
73+
description: 'Your system does not meet the requirements to flash your device. Make sure to use a browser which supports WebUSB and is up to date.',
74+
},
75+
[ErrorCode.STORAGE_SPACE]: {
76+
description: 'Your system does not have enough space available to download AGNOS. Your browser may be restricting the available space if you are in a private, incognito or guest session.',
77+
},
78+
[ErrorCode.UNRECOGNIZED_DEVICE]: {
79+
status: 'Unrecognized device',
80+
description: 'The device connected to your computer is not supported. Try using a different cable, USB port, or computer. If the problem persists, join the #hw-three-3x channel on Discord for help.',
81+
bgColor: 'bg-yellow-500',
82+
icon: deviceQuestion,
83+
},
84+
[ErrorCode.LOST_CONNECTION]: {
85+
status: 'Lost connection',
86+
description: 'The connection to your device was lost. Unplug your device and try again.',
87+
icon: cable,
88+
},
89+
[ErrorCode.REPAIR_PARTITION_TABLES_FAILED]: {
90+
status: 'Repairing partition tables failed',
91+
description: 'Your device\'s partition tables could not be repaired. Try using a different cable, USB port, or computer. If the problem persists, join the #hw-three-3x channel on Discord for help.',
92+
icon: deviceExclamation,
93+
},
94+
[ErrorCode.ERASE_FAILED]: {
95+
status: 'Erase failed',
96+
description: 'The device could not be erased. Try using a different cable, USB port, or computer. If the problem persists, join the #hw-three-3x channel on Discord for help.',
97+
icon: deviceExclamation,
98+
},
99+
[ErrorCode.FLASH_SYSTEM_FAILED]: {
100+
status: 'Flash failed',
101+
description: 'AGNOS could not be flashed to your device. Try using a different cable, USB port, or computer. If the problem persists, join the #hw-three-3x channel on Discord for help.',
102+
icon: deviceExclamation,
103+
},
104+
}
105+
106+
if (isLinux) {
107+
errors[ErrorCode.LOST_CONNECTION].description += ' Did you forget to unbind the device from qcserial?'
108+
}
109+
110+
function beforeUnloadListener(event) {
111+
event.preventDefault()
112+
return (event.returnValue = "Flash in progress. Are you sure you want to leave?")
113+
}
114+
115+
class FlashView extends HTMLElement {
116+
constructor() {
117+
super()
118+
this.step = StepCode.INITIALIZING
119+
this.message = ''
120+
this.progress = -1
121+
this.error = ErrorCode.NONE
122+
this.connected = false
123+
this.serial = null
124+
125+
this.qdlManager = null
126+
this.imageManager = createImageManager()
127+
}
128+
129+
connectedCallback() {
130+
this.render()
131+
fetch(config.loader.url)
132+
.then(res => res.arrayBuffer())
133+
.then(programmer => {
134+
this.qdlManager = new FlashManager(config.manifests.release, programmer, {
135+
onStepChange: s => { this.step = s; this.render() },
136+
onMessageChange: m => { this.message = m; this.render() },
137+
onProgressChange: p => { this.progress = p; this.render() },
138+
onErrorChange: e => { this.error = e; this.render() },
139+
onConnectionChange: c => { this.connected = c; this.render() },
140+
onSerialChange: s => { this.serial = s; this.render() },
141+
})
142+
return this.qdlManager.initialize(this.imageManager)
143+
})
144+
.catch(err => {
145+
console.error('Error initializing Flash manager:', err)
146+
this.error = ErrorCode.UNKNOWN
147+
this.render()
148+
})
149+
150+
this.addEventListener('click', this.handleClick)
151+
}
152+
153+
disconnectedCallback() {
154+
this.removeEventListener('click', this.handleClick)
155+
window.removeEventListener('beforeunload', beforeUnloadListener, { capture: true })
156+
}
157+
158+
handleClick = (e) => {
159+
const { id } = e.target
160+
if (id === 'flash-start' && this.canStart()) {
161+
this.qdlManager?.start()
162+
} else if (id === 'flash-retry') {
163+
window.location.reload()
164+
}
165+
}
166+
167+
canStart() {
168+
return this.step === StepCode.READY && !this.error
169+
}
170+
171+
render() {
172+
const uiState = { ...steps[this.step] }
173+
if (this.error) {
174+
Object.assign(uiState, errors[ErrorCode.UNKNOWN], errors[this.error])
175+
}
176+
const { status, description, bgColor, icon, iconStyle = 'invert' } = uiState
177+
178+
let title
179+
if (this.message && !this.error) {
180+
title = this.message + '...'
181+
if (this.progress >= 0) {
182+
title += ` (${(this.progress * 100).toFixed(0)}%)`
183+
}
184+
} else if (this.error === ErrorCode.STORAGE_SPACE) {
185+
title = this.message
186+
} else {
187+
title = status
188+
}
189+
190+
if (this.step >= StepCode.REPAIR_PARTITION_TABLES && this.step <= StepCode.FINALIZING) {
191+
window.addEventListener('beforeunload', beforeUnloadListener, { capture: true })
192+
} else {
193+
window.removeEventListener('beforeunload', beforeUnloadListener, { capture: true })
194+
}
195+
196+
this.innerHTML = `
197+
<div class="relative flex flex-col gap-8 justify-center items-center h-full">
198+
<div id="flash-start" class="p-8 rounded-full ${bgColor}" style="cursor: ${this.canStart() ? 'pointer' : 'default'}">
199+
<img src="${icon}" alt="cable" width="128" height="128" class="${iconStyle} ${!this.error && this.step !== StepCode.DONE ? 'animate-pulse' : ''}" />
200+
</div>
201+
<div class="w-full max-w-3xl px-8 transition-opacity duration-300" style="opacity: ${this.progress === -1 ? 0 : 1}">
202+
<div class="relative w-full h-2 bg-gray-200 rounded-full overflow-hidden">
203+
<div class="absolute top-0 bottom-0 left-0 w-full transition-all ${bgColor}" style="transform: translateX(${(this.progress * 100) - 100}%)"></div>
204+
</div>
205+
</div>
206+
<span class="text-3xl dark:text-white font-mono font-light">${title}</span>
207+
<span class="text-xl dark:text-white px-8 max-w-xl">${description || ''}</span>
208+
${this.error ? `<button id="flash-retry" class="px-4 py-2 rounded-md bg-gray-200 hover:bg-gray-300 dark:bg-gray-700 dark:hover:bg-gray-600 text-gray-800 dark:text-gray-200 transition-colors">Retry</button>` : ''}
209+
${this.connected ? `
210+
<div class="absolute bottom-0 m-0 lg:m-4 p-4 w-full sm:w-auto sm:min-w-[350px] sm:border sm:border-gray-200 dark:sm:border-gray-600 bg-white dark:bg-gray-700 text-black dark:text-white rounded-md flex flex-row gap-2" style="left:50%; transform:translate(-50%, -50%);">
211+
<div class="flex flex-row gap-2">
212+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 96 960 960" class="text-green-500 dark:text-[#51ff00]" height="24" width="24"><path fill="currentColor" d="M480 976q-32 0-52-20t-20-52q0-22 11-40t31-29V724H302q-24 0-42-18t-18-42V555q-20-9-31-26.609-11-17.608-11-40.108Q200 456 220 436t52-20q32 0 52 20t20 52.411Q344 511 333 528.5T302 555v109h148V324h-80l110-149 110 149h-80v340h148V560h-42V416h144v144h-42v104q0 24-18 42t-42 18H510v111q19.95 10.652 30.975 29.826Q552 884 552 904q0 32-20 52t-52 20Z"/></svg>
213+
Device connected
214+
</div>
215+
<span class="text-gray-400">|</span>
216+
<div class="flex flex-row gap-2">
217+
<span>Serial:<span class="ml-2 font-mono">${this.serial || 'unknown'}</span></span>
218+
</div>
219+
</div>` : ''}
220+
</div>
221+
`
222+
}
223+
}
224+
225+
customElements.define('flash-view', FlashView)
226+
export default FlashView
227+

0 commit comments

Comments
 (0)