From 087ba5e51b63e8e1ef2ef097d791035fca40659d Mon Sep 17 00:00:00 2001 From: LittleSheep Date: Sat, 11 Jan 2025 22:02:21 +0800 Subject: [PATCH] :sparkles: Launch app --- bun.lockb | Bin 310603 -> 310603 bytes package.json | 2 +- src/main/library.ts | 27 ++++- src/renderer/src/App.tsx | 4 + .../src/components/MaInstallDialog.tsx | 24 +--- src/renderer/src/pages/Landing.tsx | 2 +- src/renderer/src/pages/Library.tsx | 84 ++++++++++++++ src/renderer/src/pages/library/Details.tsx | 109 ++++++++++++++++++ src/renderer/src/pages/products/Details.tsx | 24 +--- src/renderer/src/services/parser.ts | 23 ++++ 10 files changed, 250 insertions(+), 49 deletions(-) create mode 100644 src/renderer/src/pages/Library.tsx create mode 100644 src/renderer/src/pages/library/Details.tsx create mode 100644 src/renderer/src/services/parser.ts diff --git a/bun.lockb b/bun.lockb index 698a5d6a285b7d521e031a02e8fe50d4d60d26f6..f84d2340bf7a8b953e5ac5979a3b1c5fb55ab0d6 100755 GIT binary patch delta 151 zcmV;I0BHZq`V!0f5|Az+jkEXLVhK;7EUrOHdsK_btzIl$dQF%4aRXcrv@m-4u}<1H zljuV*vsgDh@IYR=u{E^~KoHQM(=9Q_c0{yqm8{2L@J9h`u}<1H zlPEJVvsgDh@IVgAPsS`&Ln_vo?`mDmkSo*tNf>A1--GNpAT3{KHie+1X!h!wVo;3m zK getAppLibrary()) + ipcMain.handle('get-app-library', () => JSON.stringify(getAppLibrary())) + ipcMain.handle('get-app-library-one', (_, id: string) => + JSON.stringify(getAppLibrary().filter((ele) => ele.id === id)[0]), + ) + + ipcMain.handle('launch-app', (_, id: string) => launchApp(id)) } export function getAppLibrary(): LocalAppRecord[] { @@ -61,3 +67,22 @@ export function setAppRecord(record: LocalAppRecord) { library.apps = current saveLocalLibrary() } + +export function launchApp(id: string): void { + const app = getAppLibrary().filter((ele) => ele.id === id)[0] + if (!app) return + + const platform = process.platform + const runner = app.release.runners[platform] + + const segments = runner.script.split(' ') + + try { + spawn(segments[0], segments.slice(1), { + detached: true, + cwd: runner.workdir ? join(app.basePath, runner.workdir) : app.basePath, + }) + } catch (err: any) { + console.error(err) + } +} diff --git a/src/renderer/src/App.tsx b/src/renderer/src/App.tsx index f3a633b..aaeab8c 100644 --- a/src/renderer/src/App.tsx +++ b/src/renderer/src/App.tsx @@ -8,6 +8,8 @@ import { useEffect } from 'react' import ProductDetails from './pages/products/Details' import Tasks from './pages/Tasks' +import Library from './pages/Library' +import LibraryDetails from './pages/library/Details' function App(): JSX.Element { const userStore = useUserStore() @@ -42,7 +44,9 @@ function App(): JSX.Element { } /> } /> + } /> } /> + } /> diff --git a/src/renderer/src/components/MaInstallDialog.tsx b/src/renderer/src/components/MaInstallDialog.tsx index 5f5f34a..4f0a5a4 100644 --- a/src/renderer/src/components/MaInstallDialog.tsx +++ b/src/renderer/src/components/MaInstallDialog.tsx @@ -11,14 +11,9 @@ import { } from '@mui/material' import { useEffect, useState } from 'react' 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' +import { parseContent } from '@renderer/services/parser' export function MaInstallDialog({ release, @@ -35,23 +30,6 @@ export function MaInstallDialog({ const [installPath, setInstallPath] = useState() - async function parseContent(content: string): Promise { - content = content.replace( - /!\[.*?\]\(solink:\/\/attachments\/([\w-]+)\)/g, - '![alt](https://api.sn.solsynth.dev/cgi/uc/attachments/$1)', - ) - - const out = await unified() - .use(remarkParse) - .use(remarkRehype) - .use(remarkGfm) - .use(rehypeSanitize) - .use(rehypeStringify) - .process(content) - - return String(out) - } - useEffect(() => { if (release == null) return parseContent(release.meta.content).then((out) => setContent(out)) diff --git a/src/renderer/src/pages/Landing.tsx b/src/renderer/src/pages/Landing.tsx index ff9cb14..02d11a6 100644 --- a/src/renderer/src/pages/Landing.tsx +++ b/src/renderer/src/pages/Landing.tsx @@ -32,7 +32,7 @@ export default function Landing(): JSX.Element { }, []) return ( - + Matrix Marketplace diff --git a/src/renderer/src/pages/Library.tsx b/src/renderer/src/pages/Library.tsx new file mode 100644 index 0000000..505cb5e --- /dev/null +++ b/src/renderer/src/pages/Library.tsx @@ -0,0 +1,84 @@ +import { LocalAppRecord } from '@main/library' +import { + Avatar, + Card, + CardActionArea, + CardContent, + CardMedia, + Container, + Grid2 as Grid, + Typography, +} from '@mui/material' +import { useEffect, useState } from 'react' +import { useNavigate } from 'react-router' +import { getAttachmentUrl } from 'solar-js-sdk' + +export default function Library(): JSX.Element { + const navigate = useNavigate() + + const [apps, setApps] = useState([]) + + function fetchApps() { + window.electron.ipcRenderer.invoke('get-app-library').then((res) => setApps(JSON.parse(res))) + } + + useEffect(() => { + fetchApps() + }, []) + + return ( + + + Library + + + + {apps.map((p) => ( + + + navigate('/library/' + p.id)}> + {p.product.previews && ( + + )} + + {p.product.icon && ( + + )} + + {p.product.name} + + + {p.product.description} + + + + Installed + + + {p.release.meta.title} {p.release.version} + + + + + + ))} + + + ) +} diff --git a/src/renderer/src/pages/library/Details.tsx b/src/renderer/src/pages/library/Details.tsx new file mode 100644 index 0000000..a548e1d --- /dev/null +++ b/src/renderer/src/pages/library/Details.tsx @@ -0,0 +1,109 @@ +import { LocalAppRecord } from '@main/library' +import { Avatar, Box, Button, Container, Grid2 as Grid, IconButton, Stack, Typography } from '@mui/material' +import { useEffect, useMemo, useState } from 'react' +import { useNavigate, useParams } from 'react-router' +import { getAttachmentUrl } from 'solar-js-sdk' +import { parseContent } from '@renderer/services/parser' + +import PlayIcon from '@mui/icons-material/PlayArrow' +import ArrowBackwardIcon from '@mui/icons-material/ArrowBack' + +export default function LibraryDetails(): JSX.Element { + const { id } = useParams() + const navigate = useNavigate() + + const [app, setApp] = useState() + const [appProductContent, setAppProductContent] = useState() + const [appReleaseContent, setAppReleaseContent] = useState() + + const product = useMemo(() => app?.product, [app]) + + function fetchApp() { + window.electron.ipcRenderer.invoke('get-app-library-one', id).then((res) => { + const app = JSON.parse(res) + if (app == null) { + navigate('/library') + return + } + return setApp(app) + }) + } + + useEffect(() => { + fetchApp() + }, []) + + useEffect(() => { + if (app) { + parseContent(app.product.meta.introduction).then((out) => setAppProductContent(out)) + parseContent(app.release.meta.content).then((out) => setAppReleaseContent(out)) + } + }, [app]) + + function launchApp() { + window.electron.ipcRenderer.invoke('launch-app', app?.id) + } + + return ( + <> + {product?.previews && ( + + )} + + + + + + + + + + {product?.icon && ( + + )} + + {product?.name} + + + {product?.description} + + + + {appProductContent && ( + + )} + + + + + + + + + + + + + + + ) +} diff --git a/src/renderer/src/pages/products/Details.tsx b/src/renderer/src/pages/products/Details.tsx index 44a3ed7..98272bd 100644 --- a/src/renderer/src/pages/products/Details.tsx +++ b/src/renderer/src/pages/products/Details.tsx @@ -16,15 +16,10 @@ import { useEffect, useState } from 'react' import { useNavigate, useParams } from 'react-router' import { MaInstallDialog } from '@renderer/components/MaInstallDialog' import { getAttachmentUrl, MaProduct, MaRelease, sni } 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 DownloadIcon from '@mui/icons-material/Download' import ArrowBackwardIcon from '@mui/icons-material/ArrowBack' +import { parseContent } from '@renderer/services/parser' export default function ProductDetails(): JSX.Element { const { id } = useParams() @@ -38,23 +33,6 @@ export default function ProductDetails(): JSX.Element { setProduct(data) } - async function parseContent(content: string): Promise { - content = content.replace( - /!\[.*?\]\(solink:\/\/attachments\/([\w-]+)\)/g, - '![alt](https://api.sn.solsynth.dev/cgi/uc/attachments/$1)', - ) - - const out = await unified() - .use(remarkParse) - .use(remarkRehype) - .use(remarkGfm) - .use(rehypeSanitize) - .use(rehypeStringify) - .process(content) - - return String(out) - } - const [releases, setReleases] = useState() const [selectedRelease, setSelectedRelease] = useState() diff --git a/src/renderer/src/services/parser.ts b/src/renderer/src/services/parser.ts new file mode 100644 index 0000000..d33fb8d --- /dev/null +++ b/src/renderer/src/services/parser.ts @@ -0,0 +1,23 @@ +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' + +export async function parseContent(content: string): Promise { + content = content.replace( + /!\[.*?\]\(solink:\/\/attachments\/([\w-]+)\)/g, + '![alt](https://api.sn.solsynth.dev/cgi/uc/attachments/$1)', + ) + + const out = await unified() + .use(remarkParse) + .use(remarkRehype) + .use(remarkGfm) + .use(rehypeSanitize) + .use(rehypeStringify) + .process(content) + + return String(out) +}