From ac7bef4fd033c9373d2271e6275832e4589e25b3 Mon Sep 17 00:00:00 2001 From: Szedann <65607498+Szedann@users.noreply.github.com> Date: Sat, 19 Jul 2025 22:07:27 +0200 Subject: [PATCH] feat: package page --- .../src/components/content/PackageItem.tsx | 3 + .../app/browser/package.$provider.$slug.tsx | 282 ++++++++++++++++++ 2 files changed, 285 insertions(+) create mode 100644 apps/onelauncher/frontend/src/routes/app/browser/package.$provider.$slug.tsx diff --git a/apps/onelauncher/frontend/src/components/content/PackageItem.tsx b/apps/onelauncher/frontend/src/components/content/PackageItem.tsx index f974362b..63e9830f 100644 --- a/apps/onelauncher/frontend/src/components/content/PackageItem.tsx +++ b/apps/onelauncher/frontend/src/components/content/PackageItem.tsx @@ -1,6 +1,7 @@ import type { Provider, SearchResult } from '@/bindings.gen'; import { abbreviateNumber } from '@/utils'; import { Show } from '@onelauncher/common/components'; +import { useNavigate } from '@tanstack/react-router'; import { Download01Icon } from '@untitled-theme/icons-react'; export function PackageGrid({ items, provider }: { items: Array; provider: Provider }) { @@ -14,7 +15,9 @@ export function PackageGrid({ items, provider }: { items: Array; p } export function PackageItem({ provider, ...item }: SearchResult & { provider: Provider }) { + const navigate = useNavigate() function redirect() { + navigate({to: "/app/browser/package/$provider/$slug", params: {provider, slug: item.slug}}) } return ( diff --git a/apps/onelauncher/frontend/src/routes/app/browser/package.$provider.$slug.tsx b/apps/onelauncher/frontend/src/routes/app/browser/package.$provider.$slug.tsx new file mode 100644 index 00000000..ffb75066 --- /dev/null +++ b/apps/onelauncher/frontend/src/routes/app/browser/package.$provider.$slug.tsx @@ -0,0 +1,282 @@ +import type { ClusterModel, ManagedPackage, ManagedUser, ManagedVersion, Paginated, Provider } from '@/bindings.gen' +import { useBrowserContext, usePackageData, usePackageVersions } from '@/hooks/useBrowser' +import { useClusters } from '@/hooks/useCluster' +import { bindings } from '@/main' +import { abbreviateNumber, formatAsRelative, PROVIDERS } from '@/utils' +import { useCommand } from '@onelauncher/common' +import { Show, Tooltip, Button, Dropdown } from '@onelauncher/common/components' +import { createFileRoute, Link } from '@tanstack/react-router' +import { openUrl } from '@tauri-apps/plugin-opener' +import { Download01Icon, LinkExternal01Icon, File02Icon, CalendarIcon, ClockRewindIcon, ChevronDownIcon, ChevronUpIcon } from '@untitled-theme/icons-react' +import { createContext, useContext, useEffect, useMemo, useRef, useState } from 'react' +import { Collection, ListBox, ListBoxItem, Popover, Select } from 'react-aria-components' + + +export const Route = createFileRoute('/app/browser/package/$provider/$slug')({ + component: RouteComponent, +}) + +function includes(list:{includes: (arg0:C)=>boolean}, element:T): element is C{ + return list.includes(element as unknown as C) +} + +type PackageContextType = { + pkg: ManagedPackage|undefined + versions: Paginated|undefined +} + +const PackageContext = createContext({ + pkg: undefined, + versions: undefined +}) + +function RouteComponent() { + const {provider, slug} = Route.useParams() + if(!includes(PROVIDERS, provider)) throw new Error("Invalid provider"); + const packageData = usePackageData(provider, slug) + const browserContext = useBrowserContext() + const {data: versions} = usePackageVersions(provider, slug, { + mc_versions: browserContext.cluster ? [browserContext.cluster.mc_version] : null, + loaders: browserContext.cluster ? [browserContext.cluster.mc_loader] : null, + limit: 50}) + + return ( + + +
+ + +
+
+ About + Versions + + +
+ +
+ +
+
+
+
+
+ ) +} + + +function BrowserSidebar({package: pkg}: { package: ManagedPackage }) { + const {provider} = Route.useParams() + + const createdAt = useMemo(() => pkg.created ? new Date(pkg.created) : null, []); + const updatedAt = useMemo(() => pkg.updated ? new Date(pkg.updated) : null, []); + + const authors = useCommand("getUsersFromAuthor", ()=>bindings.core.getUsersFromAuthor(provider as Provider, pkg.author)) + + return ( +
+
+
+ {`Icon + {`Icon +
+
+
+

{pkg.name}

+

+ {pkg.package_type} + {' '} + on + {' '} + {pkg.provider} +

+
+ +

{pkg.short_desc}

+ +
+ +
+ + {abbreviateNumber(pkg.downloads)} +
+
+
+
+
+ + + + {/*
+

Links

+ + {contentPackage.provider} + {' '} + Page + +
*/} + +
+

Authors

+ { + authors.isSuccess + ? authors.data.map(author=>) + :

Loading...

+ } + +
+ +
+

Details

+ +
+ + License + + {pkg.license?.name || pkg.license?.id || 'Unknown'} + +
+
+ + + +
+ + Created + + {formatAsRelative(createdAt!.getTime(), 'en', 'long')} + +
+
+
+ + + +
+ + Last Updated + + {formatAsRelative(updatedAt!.getTime(), 'en', 'long')} + +
+
+
+
+ +
+ ); +} + + +function colorForType(type: string) { + switch (type.toLowerCase()) { + case 'release': + return 'bg-code-trace'; + case 'snapshot': + return 'bg-code-debug'; + case 'beta': + return 'bg-code-warn'; + case 'alpha': + return 'bg-code-error'; + default: + return 'bg-border/05'; + } +} + +function InstallButton({...pkg}:ManagedPackage){ + const triggerRef = useRef(null) + const elementRef = useRef(null) + const {provider, slug} = Route.useParams() + const [open, setOpen] = useState(false) + const clusters = useClusters() + const browserContext = useBrowserContext() + const {versions} = useContext(PackageContext) + const version = useMemo(()=>{ + if(!versions || !browserContext.cluster) return undefined; + return versions.items.findLast(version=> + version.mc_versions.includes(browserContext.cluster!.mc_version) + && version.loaders.includes(browserContext.cluster!.mc_loader) + ) + },[browserContext.cluster, versions]) + + function download(){ + if(!version || !browserContext.cluster || !includes(PROVIDERS, provider)) return false; + downloadPackage(browserContext.cluster, provider, version) + } + + + return ( + + + ) +} + + +function Author({author}:{author:ManagedUser}){ + + //TODO: onclick and fallback avatar url + return ( + {if(author.url) openUrl(author.url)}} + > + {`${author.username}'s +
+ {author.username} + + + Organization + + + + {author.role} + +
+ +
+ + ) +} + +function downloadPackage(cluster: ClusterModel, provider:Provider, version: ManagedVersion, skipCompatibility = false){ + return bindings.core.downloadPackage(provider, version.project_id, version.version_id, cluster.id, skipCompatibility) +}