Skip to content

Commit

Permalink
Complete UI redesign.
Browse files Browse the repository at this point in the history
  • Loading branch information
retrixe committed Nov 10, 2024
1 parent ef0e6d2 commit 6814918
Show file tree
Hide file tree
Showing 11 changed files with 267 additions and 166 deletions.
4 changes: 3 additions & 1 deletion .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@
"type": "go",
"request": "launch",
"mode": "auto",
"program": "${fileDirname}"
"program": "${fileDirname}",
"buildFlags": ["-ldflags=-X main.overrideUrl=http://localhost:1234"],
"env": { "DEBUG": "true" }
}
]
}
24 changes: 12 additions & 12 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ import (
webview "github.com/webview/webview_go"
)

// FIXME: Design UI (with live warnings/errors).
// FIXME: Validate written image.
// TODO: Future support for flashing to an internal drive?

Expand Down Expand Up @@ -132,7 +131,6 @@ func main() {
} else if !stat.Mode().IsRegular() {
w.Eval("setDialogReact(" + ParseToJsString("Error: Select a regular file!") + ")")
} else { // Send this back to React.
w.Eval("setFileSizeReact(" + strconv.Itoa(int(stat.Size())) + ")")
w.Eval("setFileReact(" + ParseToJsString(filename) + ")")
}
}
Expand All @@ -142,7 +140,7 @@ func main() {
var inputPipe io.WriteCloser
var cancelled bool = false
var mutex sync.Mutex
w.Bind("flash", func(file string, selectedDevice string) {
w.Bind("flash", func(file string, device string, deviceSize int) {
cancelled = false
stat, err := os.Stat(file)
if err != nil {
Expand All @@ -151,18 +149,19 @@ func main() {
} else if !stat.Mode().IsRegular() {
w.Eval("setDialogReact(" + ParseToJsString("Error: Select a regular file!") + ")")
return
} else {
w.Eval("setFileSizeReact(" + strconv.Itoa(int(stat.Size())) + ")")
} else if stat.Size() > int64(deviceSize) {
w.Eval("setDialogReact(" + ParseToJsString("Error: The disk image is too big to fit on the selected drive!") + ")")
return
}
channel, stdin, err := app.CopyConvert(file, selectedDevice)
fileSizeStr := strconv.Itoa(int(stat.Size()))
channel, stdin, err := app.CopyConvert(file, device)
inputPipe = stdin
if err != nil {
w.Eval("setDialogReact(" + ParseToJsString("Error: "+err.Error()) + ")")
return
} else {
w.Eval("setSpeedReact(" + ParseToJsString("0 MB/s") + ")") // Show progress instantly.
w.Eval("setProgressReact(0)")
}
// Show progress instantly.
w.Eval("setProgressReact({ bytes: 0, total: " + fileSizeStr + ", speed: '0 MB/s' })")
go (func() {
errored := false
for {
Expand All @@ -177,10 +176,11 @@ func main() {
w.Dispatch(func() {
if progress.Error != nil { // Error is always the last emitted.
errored = true
w.Eval("setDialogReact(" + ParseToJsString("Error: "+progress.Error.Error()) + ")")
w.Eval("setProgressReact(" + ParseToJsString("Error: "+progress.Error.Error()) + ")")
} else {
w.Eval("setSpeedReact(" + ParseToJsString(progress.Speed) + ")")
w.Eval("setProgressReact(" + strconv.Itoa(progress.Bytes) + ")")
w.Eval("setProgressReact({ bytes: " + strconv.Itoa(progress.Bytes) +
", total: " + fileSizeStr +
", speed: " + ParseToJsString(progress.Speed) + " })")
}
})
} else {
Expand Down
23 changes: 0 additions & 23 deletions renderer/App.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -11,26 +11,3 @@
width: 100%;
max-width: 540px;
}

.select-container {
display: flex;
padding-top: 0.4em;
padding-bottom: 0.4em;
> :last-child {
margin-left: 0.4em;
}
}

.flash-progress-container {
display: flex;
align-items: center;
padding-top: 0.4em;
}

.full-width {
width: 100%;
}

.spacer {
width: 5px;
}
131 changes: 34 additions & 97 deletions renderer/App.tsx
Original file line number Diff line number Diff line change
@@ -1,118 +1,55 @@
import { DialogContent, DialogTitle, Modal, ModalClose, ModalDialog } from '@mui/joy'
import { useEffect, useState } from 'react'
import JSBI from 'jsbi'
import Dialog from './Dialog'
import * as styles from './App.module.scss'
import { Button, Option, Select, Textarea, Typography, useColorScheme } from '@mui/joy'
import MainScreen from './screens/MainScreen'
import ProgressScreen from './screens/ProgressScreen'

// TODO: Experiment with lg size for the UI.
const App = (): JSX.Element => {
const [file, setFile] = useState('')
const [speed, setSpeed] = useState('')
const [dialog, setDialog] = useState('')
const [confirm, setConfirm] = useState(false)
const [device, setDevice] = useState<string | null>(null)
const [devices, setDevices] = useState<string[]>([])
const [fileSize, setFileSize] = useState(0)
const [progress, setProgress] = useState<number | string | null>(null)
const [selectedDevice, setSelectedDevice] = useState<string | null>(null)
globalThis.setFileReact = setFile
globalThis.setSpeedReact = setSpeed
globalThis.setDialogReact = setDialog
globalThis.setDevicesReact = setDevices
const [dialog, setDialog] = useState('')
globalThis.setDialogReact = setDialog
const [progress, setProgress] = useState<Progress | string | null>(null)
globalThis.setProgressReact = setProgress
globalThis.setFileSizeReact = setFileSize
useColorScheme().setMode('light')
useEffect(() => {
globalThis.refreshDevices()
}, [])
useEffect(() => setSelectedDevice(null), [devices])
useEffect(() => setDevice(null), [devices])

const inProgress = typeof progress === 'number'
useEffect(() => setConfirm(false), [inProgress])
const onFlashButtonClick = (): void => {
if (inProgress) {
// FIXME: A dialog would be better.
if (confirm) {
setConfirm(false)
globalThis.cancelFlash()
} else setConfirm(true)
return
}
setProgress(null)
if (selectedDevice === null) return setDialog('Error: Select a device to flash the ISO to!')
if (file === '') return setDialog('Error: Select an ISO to flash to a device!')
if (JSBI.greaterThan(JSBI.BigInt(fileSize), JSBI.BigInt(selectedDevice.split(' ')[0]))) {
return setDialog('Error: The ISO file is too big to fit on the selected drive!')
}
if (!confirm) return setConfirm(true)
setConfirm(false)
globalThis.flash(file, selectedDevice.split(' ')[1])
}
const onFileInputChange: React.ChangeEventHandler<HTMLTextAreaElement> = event =>
setFile(event.target.value.replace(/\n/g, ''))

const progressPercent = inProgress
? JSBI.divide(JSBI.multiply(JSBI.BigInt(progress), JSBI.BigInt(100)), JSBI.BigInt(fileSize))
: JSBI.BigInt(0)
return (
<div className={styles.root}>
<Dialog
open={dialog !== ''}
onClose={() => setDialog('')}
message={dialog.startsWith('Error: ') ? dialog.substring(7) : dialog}
error={dialog.startsWith('Error: ')}
/>
<Modal open={dialog !== ''} onClose={() => setDialog('')}>
<ModalDialog color={dialog.startsWith('Error: ') ? 'danger' : undefined}>
<ModalClose variant='soft' />
<DialogTitle>{dialog.startsWith('Error: ') ? 'Error' : 'Info'}</DialogTitle>
<DialogContent>
{dialog.startsWith('Error: ') ? dialog.substring(7) : dialog}
</DialogContent>
</ModalDialog>
</Modal>
<div className={styles.container}>
<Typography>Step 1: Select the disk image (.iso, .img, etc) to flash.</Typography>
<div className={styles['select-container']}>
<Button variant='soft' onClick={() => globalThis.promptForFile()}>
Select File
</Button>
<Textarea
minRows={2}
maxRows={2}
required
placeholder='Path to disk image'
className={styles['full-width']}
value={file}
onChange={onFileInputChange}
{progress === null && (
<MainScreen
file={file}
setFile={setFile}
device={device}
setDevice={setDevice}
devices={devices}
setDialog={setDialog}
/>
</div>
<br />
<Typography>Step 2: Select the device to flash to.</Typography>
<div className={styles['select-container']}>
<Select
className={styles['full-width']}
placeholder='Select a device'
value={selectedDevice}
required
onChange={(_, value) => setSelectedDevice(value)}
>
{devices.map(device => (
<Option key={device} value={device}>
{device.substr(device.indexOf(' ') + 1)}
</Option>
))}
</Select>
<Button onClick={() => globalThis.refreshDevices()} variant='soft'>
Refresh
</Button>
</div>

<br />
<div className={styles['flash-progress-container']}>
{/* FIXME: Add Settings dialog to disable validation. */}
{inProgress && (
// FIXME: Move this to a dedicated screen.
<Typography className={styles['full-width']}>
Progress: {progressPercent.toString()}% | Speed: {speed}
</Typography>
)}
<div className={styles['full-width']} />
<Button onClick={onFlashButtonClick}>
{confirm ? 'Confirm?' : inProgress ? 'Cancel' : 'Flash'}
</Button>
{typeof progress === 'string' && <span>{progress}</span>}
</div>
)}
{progress !== null && (
<ProgressScreen
device={device ?? ''}
file={file}
progress={progress}
setProgress={setProgress}
/>
)}
</div>
</div>
)
Expand Down
7 changes: 0 additions & 7 deletions renderer/Dialog.module.scss

This file was deleted.

21 changes: 0 additions & 21 deletions renderer/Dialog.tsx

This file was deleted.

13 changes: 8 additions & 5 deletions renderer/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,20 @@ import App from './App'
declare global {
/* eslint-disable no-var */
// Exports from Go app process.
var flash: (filePath: string, devicePath: string) => void
var flash: (filePath: string, devicePath: string, deviceSize: number) => void
var cancelFlash: () => void
var promptForFile: () => void
var refreshDevices: () => void
// Export React state to the global scope.
var setFileReact: (file: string) => void
var setSpeedReact: (speed: string) => void
var setDialogReact: (dialog: string) => void
var setDevicesReact: (devices: string[]) => void
var setFileSizeReact: (fileSize: number) => void
var setProgressReact: (progress: number | string | null) => void
var setDialogReact: (dialog: string) => void
var setProgressReact: (progress: Progress | string | null) => void
interface Progress {
bytes: number
total: number
speed: string
}
} /* eslint-enable no-var */

const theme = extendTheme({
Expand Down
19 changes: 19 additions & 0 deletions renderer/screens/MainScreen.module.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
.select-container {
display: flex;
padding-top: 0.4em;
padding-bottom: 0.4em;

> :last-child {
margin-left: 0.4em;
}
}

.flash-progress-container {
display: flex;
align-items: center;
padding-top: 0.4em;
}

.full-width {
width: 100%;
}
Loading

0 comments on commit 6814918

Please sign in to comment.