🐛 Bug fixes
This commit is contained in:
parent
3c191d3052
commit
65c3249e23
@ -13,6 +13,7 @@ export default defineConfig({
|
||||
resolve: {
|
||||
alias: {
|
||||
'@renderer': resolve('src/renderer/src'),
|
||||
'@main': resolve('src/main'),
|
||||
},
|
||||
},
|
||||
plugins: [react()],
|
||||
|
@ -18,14 +18,22 @@ function downloadFile(task: InstallTask, url: string, output: string): Promise<v
|
||||
const len = parseInt(res.headers['content-length'], 10)
|
||||
let downloaded = 0
|
||||
|
||||
let lastUpdate = Date.now()
|
||||
|
||||
res.data.on('data', (chunk: any) => {
|
||||
downloaded += chunk.length
|
||||
const percent = downloaded / len
|
||||
if (!task.progress) return
|
||||
task.progress.value = percent
|
||||
task.progress.period = InstallProgressPeriod.Downloading
|
||||
task.progress.details = `${downloaded}/${len}`
|
||||
updateInstallTask(task)
|
||||
|
||||
// Only update if 100ms has passed since the last update
|
||||
if (Date.now() - lastUpdate >= 100) {
|
||||
lastUpdate = Date.now()
|
||||
if (task.progress) {
|
||||
task.progress.value = percent
|
||||
task.progress.period = InstallProgressPeriod.Downloading
|
||||
task.progress.details = `${downloaded}/${len}`
|
||||
updateInstallTask(task)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
res.data.pipe(writer)
|
||||
@ -45,7 +53,7 @@ function convertAssetUri(uri: string): string {
|
||||
throw new Error('Unable handle assets uri')
|
||||
}
|
||||
|
||||
export async function downloadAssets(task: InstallTask): Promise<void> {
|
||||
export async function downloadAssets(task: InstallTask): Promise<string | undefined> {
|
||||
const platform = process.platform
|
||||
const asset = task.release.assets[platform]
|
||||
if (!asset) return
|
||||
@ -64,15 +72,16 @@ export async function downloadAssets(task: InstallTask): Promise<void> {
|
||||
updateInstallTask(task)
|
||||
}
|
||||
|
||||
const stream = fs.createReadStream(output)
|
||||
|
||||
switch (asset.contentType) {
|
||||
case 'application/zip':
|
||||
stream.on('close', () => fs.unlinkSync(output))
|
||||
stream.pipe(unzipper.Extract({ path: path.dirname(output) }))
|
||||
const directory = await unzipper.Open.file(output)
|
||||
await directory.extract({ path: task.basePath })
|
||||
fs.unlinkSync(output)
|
||||
break
|
||||
default:
|
||||
console.error(new Error('Failed to decompress, unsupported type'))
|
||||
throw new Error('Failed to decompress, unsupported type')
|
||||
}
|
||||
}
|
||||
|
||||
return output
|
||||
}
|
||||
|
@ -10,7 +10,7 @@ import { downloadAssets } from './downloader'
|
||||
import { setAppRecord } from './library'
|
||||
|
||||
export function initInstaller(): void {
|
||||
ipcMain.handle('install-product-release', (evt, task: InstallTask) => submitInstallTask(task, evt.sender))
|
||||
ipcMain.handle('install-product-release', (evt, task: string) => submitInstallTask(JSON.parse(task), evt.sender))
|
||||
}
|
||||
|
||||
function submitInstallTask(task: InstallTask, contents: WebContents): void {
|
||||
|
@ -12,6 +12,7 @@ export let library: AppLibrary
|
||||
function readLocalLibrary(): AppLibrary {
|
||||
const basePath = app.getPath('userData')
|
||||
const libraryPath = join(basePath, 'apps_library.json')
|
||||
console.log(`[Library] Loading library from ${libraryPath}`)
|
||||
if (fs.existsSync(libraryPath)) {
|
||||
const data = fs.readFileSync(libraryPath, 'utf-8')
|
||||
library = JSON.parse(data)
|
||||
|
@ -30,11 +30,14 @@ export interface InstallProgress {
|
||||
export const installTasks: Record<string, InstallTask> = {}
|
||||
|
||||
export function initTasks() {
|
||||
ipcMain.handle('get-install-tasks', () => Object.values(installTasks))
|
||||
ipcMain.handle('get-install-tasks', () => JSON.stringify(Object.values(installTasks)))
|
||||
}
|
||||
|
||||
export function updateInstallTask(task: InstallTask): void {
|
||||
if (!task.emitter) return
|
||||
if (task.id) installTasks[task.id] = task
|
||||
task.emitter.send('update:install-task', task)
|
||||
console.log(
|
||||
`[InstallTask] Task #${task.id} updated, progress: ${task.progress?.value}, period: ${task.progress?.period}, error: ${task.progress?.error}`,
|
||||
)
|
||||
task.emitter.send('update:install-task', JSON.stringify(task))
|
||||
}
|
||||
|
@ -7,6 +7,7 @@ import { useUserStore } from 'solar-js-sdk'
|
||||
import { useEffect } from 'react'
|
||||
|
||||
import ProductDetails from './pages/products/Details'
|
||||
import Tasks from './pages/Tasks'
|
||||
|
||||
function App(): JSX.Element {
|
||||
const userStore = useUserStore()
|
||||
@ -40,6 +41,7 @@ function App(): JSX.Element {
|
||||
|
||||
<Routes>
|
||||
<Route path="/" element={<Landing />} />
|
||||
<Route path="/tasks" element={<Tasks />} />
|
||||
<Route path="/products/:id" element={<ProductDetails />} />
|
||||
</Routes>
|
||||
</BrowserRouter>
|
||||
|
@ -13,21 +13,49 @@ import {
|
||||
import { useState } from 'react'
|
||||
|
||||
import GamepadIcon from '@mui/icons-material/Gamepad'
|
||||
import ExploreIcon from '@mui/icons-material/Explore'
|
||||
import AppsIcon from '@mui/icons-material/Apps'
|
||||
import TaskIcon from '@mui/icons-material/Task'
|
||||
import { useNavigate } from 'react-router'
|
||||
|
||||
export interface NavLink {
|
||||
title: string
|
||||
icon?: JSX.Element
|
||||
href: string
|
||||
}
|
||||
|
||||
export function MaAppBar(): JSX.Element {
|
||||
const navigate = useNavigate()
|
||||
|
||||
const [open, setOpen] = useState(false)
|
||||
|
||||
const functionLinks: NavLink[] = [
|
||||
{
|
||||
title: 'Explore',
|
||||
icon: <ExploreIcon />,
|
||||
href: '/',
|
||||
},
|
||||
{
|
||||
title: 'Library',
|
||||
icon: <AppsIcon />,
|
||||
href: '/library',
|
||||
},
|
||||
{
|
||||
title: 'Tasks',
|
||||
icon: <TaskIcon />,
|
||||
href: '/tasks',
|
||||
},
|
||||
]
|
||||
|
||||
return (
|
||||
<>
|
||||
<Drawer open={open} onClose={() => setOpen(false)} sx={{ width: '320px' }}>
|
||||
<List sx={{ width: '320px' }}>
|
||||
{['Inbox', 'Starred', 'Send email', 'Drafts'].map((text) => (
|
||||
<ListItem key={text} disablePadding>
|
||||
<ListItemButton>
|
||||
<ListItemIcon>
|
||||
<GamepadIcon />
|
||||
</ListItemIcon>
|
||||
<ListItemText primary={text} />
|
||||
{functionLinks.map((l) => (
|
||||
<ListItem disablePadding>
|
||||
<ListItemButton selected={window.location.pathname == l.href} onClick={() => navigate(l.href)}>
|
||||
<ListItemIcon>{l.icon}</ListItemIcon>
|
||||
<ListItemText primary={l.title} />
|
||||
</ListItemButton>
|
||||
</ListItem>
|
||||
))}
|
||||
|
@ -10,20 +10,24 @@ import {
|
||||
TextField,
|
||||
} from '@mui/material'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { MaRelease } from 'solar-js-sdk'
|
||||
import { MaProduct, MaRelease } from 'solar-js-sdk'
|
||||
import { unified } from 'unified'
|
||||
import rehypeSanitize from 'rehype-sanitize'
|
||||
import rehypeStringify from 'rehype-stringify'
|
||||
import remarkGfm from 'remark-gfm'
|
||||
import remarkParse from 'remark-parse'
|
||||
import remarkRehype from 'remark-rehype'
|
||||
import { InstallTask } from '@main/tasks'
|
||||
import { useNavigate } from 'react-router'
|
||||
|
||||
export function MaInstallDialog({
|
||||
release,
|
||||
product,
|
||||
open,
|
||||
onClose,
|
||||
}: {
|
||||
release?: MaRelease
|
||||
product?: MaProduct
|
||||
open: boolean
|
||||
onClose: () => void
|
||||
}): JSX.Element {
|
||||
@ -61,6 +65,18 @@ export function MaInstallDialog({
|
||||
window.electron.ipcRenderer.invoke('show-path-select').then((out: string) => setInstallPath(out))
|
||||
}
|
||||
|
||||
const navigate = useNavigate()
|
||||
|
||||
function installApp() {
|
||||
const task: InstallTask = {
|
||||
release: release!,
|
||||
product: product!,
|
||||
basePath: installPath!,
|
||||
}
|
||||
window.electron.ipcRenderer.invoke('install-product-release', JSON.stringify(task))
|
||||
navigate('/tasks')
|
||||
}
|
||||
|
||||
return (
|
||||
<Dialog open={open} onClose={onClose} maxWidth="sm" fullWidth>
|
||||
<DialogTitle>Install {release?.version}</DialogTitle>
|
||||
@ -105,7 +121,7 @@ export function MaInstallDialog({
|
||||
</Typography>
|
||||
)}
|
||||
<Button onClick={onClose}>Cancel</Button>
|
||||
<Button autoFocus disabled={!checkPlatformAvailable(window.platform) || !installPath}>
|
||||
<Button autoFocus disabled={!checkPlatformAvailable(window.platform) || !installPath} onClick={installApp}>
|
||||
Install
|
||||
</Button>
|
||||
</DialogActions>
|
||||
|
70
src/renderer/src/pages/Tasks.tsx
Normal file
70
src/renderer/src/pages/Tasks.tsx
Normal file
@ -0,0 +1,70 @@
|
||||
import { Box, Card, CardContent, Container, LinearProgress, Typography } from '@mui/material'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { InstallTask } from '@main/tasks'
|
||||
|
||||
export default function Tasks(): JSX.Element {
|
||||
const [tasks, setTasks] = useState<InstallTask[]>([])
|
||||
|
||||
function initListeners() {
|
||||
window.electron.ipcRenderer.on('update:install-task', (_, raw: string) => {
|
||||
const task = JSON.parse(raw)
|
||||
setTasks((tasks) => tasks.map((t) => (t.id === task.id ? task : t)))
|
||||
})
|
||||
}
|
||||
|
||||
async function fetchTasks() {
|
||||
window.electron.ipcRenderer.invoke('get-install-tasks').then((res) => setTasks(JSON.parse(res)))
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
initListeners()
|
||||
fetchTasks()
|
||||
}, [])
|
||||
|
||||
const periodLabels = ['Initializing', 'Downloading', 'Extracting', 'Installing', 'Indexing', 'Completed']
|
||||
|
||||
return (
|
||||
<Container sx={{ py: 4 }}>
|
||||
<Typography variant="h5" component="h1">
|
||||
Tasks
|
||||
</Typography>
|
||||
|
||||
<Box sx={{ mt: 3, display: 'flex', flexDirection: 'column', gap: 2 }}>
|
||||
{tasks.map((t) => (
|
||||
<Card variant="outlined" key={t.id}>
|
||||
<CardContent>
|
||||
<Typography variant="h6" component="h2" lineHeight={1.2}>
|
||||
{t.product.name}
|
||||
</Typography>
|
||||
<Typography variant="subtitle1" component="p" gutterBottom>
|
||||
{t.release.meta.title} {t.release.version}
|
||||
</Typography>
|
||||
|
||||
<Typography variant="body1" component="p">
|
||||
{periodLabels[t.progress?.period ?? 0]} · Period {(t.progress?.period ?? 0) + 1}/6
|
||||
</Typography>
|
||||
{t.progress?.details && (
|
||||
<Typography variant="body2" component="p" fontFamily="monospace" fontSize={13}>
|
||||
{t.progress.details}
|
||||
</Typography>
|
||||
)}
|
||||
|
||||
<Box sx={{ mt: 2 }}>
|
||||
{t.progress?.value ? (
|
||||
<LinearProgress
|
||||
variant="determinate"
|
||||
color={t.progress?.done ? 'success' : 'primary'}
|
||||
value={t.progress.value * 100}
|
||||
sx={{ borderRadius: 4 }}
|
||||
/>
|
||||
) : (
|
||||
<LinearProgress variant="indeterminate" sx={{ borderRadius: 4 }} />
|
||||
)}
|
||||
</Box>
|
||||
</CardContent>
|
||||
</Card>
|
||||
))}
|
||||
</Box>
|
||||
</Container>
|
||||
)
|
||||
}
|
@ -167,7 +167,12 @@ export default function ProductDetails(): JSX.Element {
|
||||
</Grid>
|
||||
</Container>
|
||||
|
||||
<MaInstallDialog release={selectedRelease} open={showInstaller} onClose={() => setShowInstaller(false)} />
|
||||
<MaInstallDialog
|
||||
product={product}
|
||||
release={selectedRelease}
|
||||
open={showInstaller}
|
||||
onClose={() => setShowInstaller(false)}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
@ -1,16 +1,23 @@
|
||||
{
|
||||
"extends": "@electron-toolkit/tsconfig/tsconfig.web.json",
|
||||
"include": [
|
||||
"src/main/**/*.ts",
|
||||
"src/renderer/src/env.d.ts",
|
||||
"src/renderer/src/**/*",
|
||||
"src/renderer/src/**/*.tsx",
|
||||
"src/preload/*.d.ts"
|
||||
],
|
||||
"exclude": [
|
||||
"src/main/index.ts"
|
||||
],
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"jsx": "react-jsx",
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@main/*": [
|
||||
"src/main/*"
|
||||
],
|
||||
"@renderer/*": [
|
||||
"src/renderer/src/*"
|
||||
]
|
||||
|
Loading…
x
Reference in New Issue
Block a user