From 65c3249e23a3622731dc3abc75c4da8e64076e56 Mon Sep 17 00:00:00 2001 From: LittleSheep Date: Sat, 11 Jan 2025 20:59:14 +0800 Subject: [PATCH] :bug: Bug fixes --- electron.vite.config.ts | 1 + src/main/downloader.ts | 31 +++++--- src/main/installer.ts | 2 +- src/main/library.ts | 1 + src/main/tasks.ts | 7 +- src/renderer/src/App.tsx | 2 + src/renderer/src/components/MaAppBar.tsx | 42 +++++++++-- .../src/components/MaInstallDialog.tsx | 20 +++++- src/renderer/src/pages/Tasks.tsx | 70 +++++++++++++++++++ src/renderer/src/pages/products/Details.tsx | 7 +- tsconfig.web.json | 7 ++ 11 files changed, 166 insertions(+), 24 deletions(-) create mode 100644 src/renderer/src/pages/Tasks.tsx diff --git a/electron.vite.config.ts b/electron.vite.config.ts index c257d1d..6359077 100644 --- a/electron.vite.config.ts +++ b/electron.vite.config.ts @@ -13,6 +13,7 @@ export default defineConfig({ resolve: { alias: { '@renderer': resolve('src/renderer/src'), + '@main': resolve('src/main'), }, }, plugins: [react()], diff --git a/src/main/downloader.ts b/src/main/downloader.ts index 3b47401..79e4978 100644 --- a/src/main/downloader.ts +++ b/src/main/downloader.ts @@ -18,14 +18,22 @@ function downloadFile(task: InstallTask, url: string, output: string): Promise { 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 { +export async function downloadAssets(task: InstallTask): Promise { const platform = process.platform const asset = task.release.assets[platform] if (!asset) return @@ -64,15 +72,16 @@ export async function downloadAssets(task: InstallTask): Promise { 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 } diff --git a/src/main/installer.ts b/src/main/installer.ts index ada0379..f5a015e 100644 --- a/src/main/installer.ts +++ b/src/main/installer.ts @@ -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 { diff --git a/src/main/library.ts b/src/main/library.ts index a3666c7..54d7b78 100644 --- a/src/main/library.ts +++ b/src/main/library.ts @@ -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) diff --git a/src/main/tasks.ts b/src/main/tasks.ts index 99a5f36..397c22e 100644 --- a/src/main/tasks.ts +++ b/src/main/tasks.ts @@ -30,11 +30,14 @@ export interface InstallProgress { export const installTasks: Record = {} 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)) } diff --git a/src/renderer/src/App.tsx b/src/renderer/src/App.tsx index a210ca3..f3a633b 100644 --- a/src/renderer/src/App.tsx +++ b/src/renderer/src/App.tsx @@ -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 { } /> + } /> } /> diff --git a/src/renderer/src/components/MaAppBar.tsx b/src/renderer/src/components/MaAppBar.tsx index 0c8379b..256c70f 100644 --- a/src/renderer/src/components/MaAppBar.tsx +++ b/src/renderer/src/components/MaAppBar.tsx @@ -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: , + href: '/', + }, + { + title: 'Library', + icon: , + href: '/library', + }, + { + title: 'Tasks', + icon: , + href: '/tasks', + }, + ] + return ( <> setOpen(false)} sx={{ width: '320px' }}> - {['Inbox', 'Starred', 'Send email', 'Drafts'].map((text) => ( - - - - - - + {functionLinks.map((l) => ( + + navigate(l.href)}> + {l.icon} + ))} diff --git a/src/renderer/src/components/MaInstallDialog.tsx b/src/renderer/src/components/MaInstallDialog.tsx index 9d413ce..5f5f34a 100644 --- a/src/renderer/src/components/MaInstallDialog.tsx +++ b/src/renderer/src/components/MaInstallDialog.tsx @@ -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 ( Install {release?.version} @@ -105,7 +121,7 @@ export function MaInstallDialog({ )} - diff --git a/src/renderer/src/pages/Tasks.tsx b/src/renderer/src/pages/Tasks.tsx new file mode 100644 index 0000000..1f10588 --- /dev/null +++ b/src/renderer/src/pages/Tasks.tsx @@ -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([]) + + 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 ( + + + Tasks + + + + {tasks.map((t) => ( + + + + {t.product.name} + + + {t.release.meta.title} {t.release.version} + + + + {periodLabels[t.progress?.period ?? 0]} ยท Period {(t.progress?.period ?? 0) + 1}/6 + + {t.progress?.details && ( + + {t.progress.details} + + )} + + + {t.progress?.value ? ( + + ) : ( + + )} + + + + ))} + + + ) +} diff --git a/src/renderer/src/pages/products/Details.tsx b/src/renderer/src/pages/products/Details.tsx index 10ab2b6..44a3ed7 100644 --- a/src/renderer/src/pages/products/Details.tsx +++ b/src/renderer/src/pages/products/Details.tsx @@ -167,7 +167,12 @@ export default function ProductDetails(): JSX.Element { - setShowInstaller(false)} /> + setShowInstaller(false)} + /> ) } diff --git a/tsconfig.web.json b/tsconfig.web.json index 9c16b66..434019a 100644 --- a/tsconfig.web.json +++ b/tsconfig.web.json @@ -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/*" ]