Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

#42 replace React with SolidJs #66

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 3 additions & 18 deletions .eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,7 @@
},
"extends": [
"eslint:recommended",
"plugin:react/recommended",
"plugin:react/jsx-runtime",
"plugin:react-hooks/recommended"
"plugin:solid/recommended"
],
"ignorePatterns": [
"node_modules",
Expand All @@ -20,13 +18,8 @@
"ecmaVersion": "latest",
"sourceType": "module"
},
"settings": {
"react": {
"version": "detect"
}
},
"plugins": [
"react-refresh"
"solid"
],
"rules": {
"no-constant-condition": [
Expand All @@ -35,14 +28,6 @@
"checkLoops": false
}
],
"no-inner-declarations": "off",
"react/jsx-no-target-blank": "off",
"react/prop-types": "off",
"react-refresh/only-export-components": [
"warn",
{
"allowConstantExport": true
}
]
"no-inner-declarations": "off"
}
}
Binary file modified bun.lockb
Binary file not shown.
13 changes: 4 additions & 9 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,26 +19,21 @@
"comlink": "^4.4.1",
"crc-32": "^1.2.2",
"jssha": "^3.3.1",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"solid-js": "^1.8.15",
"xz-decompress": "^0.2.1"
},
"devDependencies": {
"@tailwindcss/typography": "^0.5.13",
"@testing-library/jest-dom": "^6.4.5",
"@testing-library/react": "^15.0.7",
"@types/react": "^18.3.3",
"@types/react-dom": "^18.3.0",
"@vitejs/plugin-react": "^4.3.0",
"autoprefixer": "10.4.14",
"eslint": "^8.57.0",
"eslint-plugin-react": "^7.34.2",
"eslint-plugin-react-hooks": "^4.6.2",
"eslint-plugin-react-refresh": "^0.4.7",
"eslint-plugin-solid": "^0.13.1",
"jsdom": "^22.1.0",
"postcss": "^8.4.38",
"@solidjs/testing-library": "^0.5.0",
"tailwindcss": "^3.4.3",
"vite": "^5.2.12",
"vite-plugin-solid": "^2.10.1",
"vite-svg-loader": "^5.1.0",
"vitest": "^1.6.0"
}
Expand Down
21 changes: 15 additions & 6 deletions src/app/App.test.jsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,19 @@
import { Suspense } from 'react'
import { expect, test } from 'vitest'
import { render, screen } from '@testing-library/react'
import { Suspense } from 'solid-js'
import { describe, it, expect } from 'vitest'
import { render, screen } from '@solidjs/testing-library'

import App from '.'

test('renders without crashing', () => {
render(<Suspense fallback="loading"><App /></Suspense>)
expect(screen.getByText('flash.comma.ai')).toBeInTheDocument()
//todo-breaking test due to React -> SolidJS migration
describe('App', () => {
it('renders without crashing', () => {
const { unmount } = render(() => (
<Suspense fallback="loading">
<App />
</Suspense>
))

expect(screen.getByText('flash.comma.ai')).toBeInTheDocument()
unmount()
})
})
148 changes: 75 additions & 73 deletions src/app/Flash.jsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useCallback, useState } from 'react'
import { createSignal, createMemo, onCleanup, createEffect } from 'solid-js'

import { Step, Error, useQdl } from '@/utils/flash'

Expand Down Expand Up @@ -121,12 +121,13 @@ const detachScript = [

const isLinux = navigator.userAgent.toLowerCase().includes('linux');

function LinearProgress({ value, barColor }) {
function LinearProgress(props) {
let value = props.value
if (value === -1 || value > 100) value = 100
return (
<div className="relative w-full h-2 bg-gray-200 rounded-full overflow-hidden">
<div class="relative w-full h-2 bg-gray-200 rounded-full overflow-hidden">
<div
className={`absolute top-0 bottom-0 left-0 w-full transition-all ${barColor}`}
class={`absolute top-0 bottom-0 left-0 w-full transition-all ${props.barColor}`}
style={{ transform: `translateX(${value - 100}%)` }}
/>
</div>
Expand All @@ -135,11 +136,11 @@ function LinearProgress({ value, barColor }) {


function USBIndicator() {
return <div className="flex flex-row gap-2">
return <div class="flex flex-row gap-2">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 96 960 960"
className="text-green-500 dark:text-[#51ff00]"
class="text-green-500 dark:text-[#51ff00]"
height="24"
width="24"
>
Expand All @@ -153,32 +154,31 @@ function USBIndicator() {
}


function SerialIndicator({ serial }) {
return <div className="flex flex-row gap-2">
function SerialIndicator(props) {
return <div class="flex flex-row gap-2">
<span>
Serial:
<span className="ml-2 font-mono">{serial || 'unknown'}</span>
<span class="ml-2 font-mono">{props.serial || 'unknown'}</span>
</span>
</div>
}


function DeviceState({ serial }) {
function DeviceState(props) {
return (
<div
className="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"
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%)' }}
>
<USBIndicator />
<span className="text-gray-400">|</span>
<SerialIndicator serial={serial} />
<span class="text-gray-400">|</span>
<SerialIndicator serial={props.serial} />
</div>
)
}


function beforeUnloadListener(event) {
// NOTE: not all browsers will show this message
event.preventDefault()
return (event.returnValue = "Flash in progress. Are you sure you want to leave?")
}
Expand All @@ -190,96 +190,98 @@ export default function Flash() {
message,
progress,
error,

onContinue,
onRetry,

connected,
serial,
} = useQdl()

const handleContinue = useCallback(() => {
onContinue?.()
}, [onContinue])

const handleRetry = useCallback(() => {
onRetry?.()
}, [onRetry])
const [copied, setCopied] = createSignal(false);

const uiState = steps[step]
if (error) {
Object.assign(uiState, errors[Error.UNKNOWN], errors[error])
}
const { status, description, bgColor, icon, iconStyle = 'invert' } = uiState

let title
if (message && !error) {
title = message + '...'
if (progress >= 0) {
title += ` (${(progress * 100).toFixed(0)}%)`
}
} else {
title = status
}

// warn the user if they try to leave the page while flashing
if (Step.DOWNLOADING <= step && step <= Step.ERASING) {
window.addEventListener("beforeunload", beforeUnloadListener, { capture: true })
} else {
window.removeEventListener("beforeunload", beforeUnloadListener, { capture: true })
}

const [copied, setCopied] = useState(false);
const handleCopy = () => {
setCopied(true);
setTimeout(() => {
setCopied(false);
}, 1000);
};

const uiState = createMemo(() => {
const currentStep = steps[step()]
if (error()) {
return { ...currentStep, ...errors[Error.UNKNOWN], ...errors[error()] }
}
return currentStep
})

const title = createMemo(() => {
if (message() && !error()) {
let t = message() + '...'
if (progress() >= 0) {
t += ` (${(progress() * 100).toFixed(0)}%)`
}
return t
}
return uiState().status
})

createEffect(() => {
if (Step.DOWNLOADING <= step() && step() <= Step.ERASING) {
window.addEventListener("beforeunload", beforeUnloadListener, { capture: true })
} else {
window.removeEventListener("beforeunload", beforeUnloadListener, { capture: true })
}
})

onCleanup(() => {
window.removeEventListener("beforeunload", beforeUnloadListener, { capture: true })
})

return (
<div id="flash" className="relative flex flex-col gap-8 justify-center items-center h-full">
<div id="flash" class="relative flex flex-col gap-8 justify-center items-center h-full">
<div
className={`p-8 rounded-full ${bgColor}`}
class="p-8 rounded-full"
classList={{ [uiState().bgColor]: true }}
style={{ cursor: onContinue ? 'pointer' : 'default' }}
onClick={handleContinue}
>
onClick={onContinue}
>
<img
src={icon}
src={uiState().icon}
alt="cable"
width={128}
height={128}
className={`${iconStyle} ${!error && step !== Step.DONE ? 'animate-pulse' : ''}`}
classList={{
[uiState().iconStyle]: true,
'animate-pulse': !error() && step() !== Step.DONE
}}
/>
</div>
<div className="w-full max-w-3xl px-8 transition-opacity duration-300" style={{ opacity: progress === -1 ? 0 : 1 }}>
<LinearProgress value={progress * 100} barColor={bgColor} />
<div class="w-full max-w-3xl px-8 transition-opacity duration-300" style={{ opacity: () => progress() === -1 ? 0 : 1 }}>
<LinearProgress value={() => progress() * 100} barColor={() => uiState().bgColor} />
</div>
<span className={`text-3xl dark:text-white font-mono font-light`}>{title}</span>
<span className={`text-xl dark:text-white px-8 max-w-xl`}>{description}</span>
{(title === "Lost connection" || title === "Ready") && isLinux && (
<span class="text-3xl dark:text-white font-mono font-light">{title()}</span>
<span class="text-xl dark:text-white px-8 max-w-xl">{() => uiState().description}</span>
{() => (title() === "Lost connection" || title() === "Ready") && isLinux && (
<>
<span className={`text-l dark:text-white px-2 max-w-xl`}>
It seems that you&apos;re on Linux, make sure to run the script below in your terminal after plugging in your device.
<span class="text-l dark:text-white px-2 max-w-xl">
It seems that you're on Linux, make sure to run the script below in your terminal after plugging in your device.
</span>
<div className="relative mt-2 max-w-3xl">
<div className="bg-gray-200 dark:bg-gray-800 rounded-md overflow-x-auto">
<div className="relative">
<pre className="font-mono text-sm text-gray-800 dark:text-gray-200 bg-gray-300 dark:bg-gray-700 rounded-md p-6 flex-grow max-w-m text-wrap">
<div class="relative mt-2 max-w-3xl">
<div class="bg-gray-200 dark:bg-gray-800 rounded-md overflow-x-auto">
<div class="relative">
<pre class="font-mono text-sm text-gray-800 dark:text-gray-200 bg-gray-300 dark:bg-gray-700 rounded-md p-6 flex-grow max-w-m text-wrap">
{detachScript.map((line, index) => (
<span key={index} className="block">
<span key={index} class="block">
{line}
</span>
))}
</pre>
<div className="absolute top-2 right-2">
<div class="absolute top-2 right-2">
<button
onClick={() => {
navigator.clipboard.writeText(detachScript.join('\n'));
handleCopy();
}}
className={`bg-${copied ? 'green' : 'blue'}-500 text-white px-1 py-1 rounded-md ml-2 text-sm`}
class={() => `bg-${copied() ? 'green' : 'blue'}-500 text-white px-1 py-1 rounded-md ml-2 text-sm`}
>
Copy
</button>
Expand All @@ -289,15 +291,15 @@ export default function Flash() {
</div>
</>
)}
{error && (
{error() && (
<button
className="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"
onClick={handleRetry}
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"
onClick={onRetry}
>
Retry
</button>
) || false}
{connected && <DeviceState connected={connected} serial={serial} />}
)}
{connected() && <DeviceState connected={connected()} serial={serial()} />}
</div>
)
}
}
19 changes: 10 additions & 9 deletions src/app/index.jsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Suspense, lazy } from 'react'
import { createResource, lazy, Suspense } from 'solid-js'

import comma from '../assets/comma.svg'
import fastbootPorts from '../assets/fastboot-ports.svg'
Expand All @@ -9,12 +9,13 @@ const Flash = lazy(() => import('./Flash'))

export default function App() {
const version = import.meta.env.VITE_PUBLIC_GIT_SHA || 'dev'
console.info(`flash.comma.ai version: ${version}`);
console.info(`flash.comma.ai version: ${version}`)

return (
<div className="flex flex-col lg:flex-row flex-wrap">
<main className="p-12 md:p-16 lg:p-20 xl:p-24 w-screen max-w-none lg:max-w-prose lg:w-auto h-auto lg:h-screen lg:overflow-y-auto prose dark:prose-invert prose-green bg-white dark:bg-gray-900">
<div class="flex flex-col lg:flex-row flex-wrap">
<main class="p-12 md:p-16 lg:p-20 xl:p-24 w-screen max-w-none lg:max-w-prose lg:w-auto h-auto lg:h-screen lg:overflow-y-auto prose dark:prose-invert prose-green bg-white dark:bg-gray-900">
<section>
<img src={comma} alt="comma" width={128} height={128} className="dark:invert" />
<img src={comma} alt="comma" width={128} height={128} class="dark:invert" />
<h1>flash.comma.ai</h1>

<p>This tool allows you to flash AGNOS onto your comma device.</p>
Expand Down Expand Up @@ -140,19 +141,19 @@ export default function App() {
</p>
</section>

<div className="hidden lg:block">
<div class="hidden lg:block">
<hr />
flash.comma.ai version: <code>{version}</code>
</div>
</main>

<div className="lg:flex-1 h-[700px] lg:h-screen bg-gray-100 dark:bg-gray-800">
<Suspense fallback={<p className="text-black dark:text-white">Loading...</p>}>
<div class="lg:flex-1 h-[700px] lg:h-screen bg-gray-100 dark:bg-gray-800">
<Suspense fallback={<p class="text-black dark:text-white">Loading...</p>}>
<Flash />
</Suspense>
</div>

<div className="w-screen max-w-none p-12 md:p-16 prose dark:prose-invert bg-white dark:bg-gray-900 lg:hidden">
<div class="w-screen max-w-none p-12 md:p-16 prose dark:prose-invert bg-white dark:bg-gray-900 lg:hidden">
flash.comma.ai version: <code>{version.substring(0, 7)}</code>
</div>
</div>
Expand Down
Loading
Loading