From 4fda6e44b21e60fd5fb45516b2b3db3ef60b8c8b Mon Sep 17 00:00:00 2001 From: Szedann <65607498+Szedann@users.noreply.github.com> Date: Sat, 9 Aug 2025 23:12:54 +0200 Subject: [PATCH] refactor: adapt frontend to backend changes --- apps/onelauncher/frontend/src/bindings.gen.ts | 49 +++++++++++++------ .../src/components/content/PackageItem.tsx | 4 +- .../frontend/src/hooks/useBrowser.tsx | 4 +- ...er.$slug.tsx => package.$provider.$id.tsx} | 26 +++++----- .../src/api/packages/provider/modrinth/mod.rs | 1 + packages/core/src/api/tauri/commands.rs | 9 +++- 6 files changed, 57 insertions(+), 36 deletions(-) rename apps/onelauncher/frontend/src/routes/app/browser/{package.$provider.$slug.tsx => package.$provider.$id.tsx} (95%) diff --git a/apps/onelauncher/frontend/src/bindings.gen.ts b/apps/onelauncher/frontend/src/bindings.gen.ts index 63b21f34..4302fe57 100644 --- a/apps/onelauncher/frontend/src/bindings.gen.ts +++ b/apps/onelauncher/frontend/src/bindings.gen.ts @@ -5,9 +5,9 @@ import { createTauRPCProxy as createProxy, type InferCommandOutput } from 'taurp type TAURI_CHANNEL = (response: T) => void -export type ClusterError = { type: "InvalidVersion"; data: string } | { type: "MissingJavaVersion"; data: string } | { type: "ClusterDownloading"; data: string } | { type: "ClusterAlreadyRunning"; data: string } +export type ClusterError = { type: "InvalidVersion"; data: string } | { type: "MismatchedVersion"; data: string } | { type: "MismatchedLoader"; data: string } | { type: "MissingJavaVersion"; data: string } | { type: "ClusterDownloading"; data: string } | { type: "ClusterAlreadyRunning"; data: string } -export type ClusterModel = { id: bigint; folder_name: string; stage: ClusterStage; created_at: string; group_id: bigint | null; name: string; mc_version: string; mc_loader: GameLoader; mc_loader_version: string | null; last_played: string | null; overall_played: bigint | null; icon_url: Icon | null; setting_profile_name: string | null; linked_pack_id: string | null; linked_pack_version: string | null } +export type ClusterModel = { id: bigint; folder_name: string; stage: ClusterStage; created_at: string; group_id: bigint | null; name: string; mc_version: string; mc_loader: GameLoader; mc_loader_version: string | null; last_played: string | null; overall_played: bigint | null; icon_url: Icon | null; setting_profile_name: string | null; linked_modpack_hash: string | null } export type ClusterStage = "notready" | "downloading" | "repairing" | "ready" @@ -25,11 +25,11 @@ export type DirectoryError = { type: "BaseDir"; data: string } export type DiscordError = { type: "MissingClientId"; data: string } | { type: "ConnectError"; data: string } -export type Filters = { game_versions: string[] | null; loaders: GameLoader[] | null; categories: string[] | null; package_types: PackageType[] | null } +export type Filters = { game_versions: string[] | null; loaders: GameLoader[] | null; categories: PackageCategories | null; package_type: PackageType | null } export type GameLoader = "vanilla" | "forge" | "neoforge" | "quilt" | "fabric" | "legacyfabric" -export type IOError = { type: "InvalidAbsolutePath"; data: string } | { type: "IOErrorWrapper"; data: string } | { type: "IOError"; data: string } | { type: "DeserializeError"; data: string } | { type: "AsyncZipError"; data: string } +export type IOError = { type: "InvalidAbsolutePath"; data: string } | { type: "FileNotFoundInZip"; data: string } | { type: "IOErrorWrapper"; data: string } | { type: "IOError"; data: string } | { type: "DeserializeError"; data: string } | { type: "AsyncZipError"; data: string } export type Icon = string @@ -45,7 +45,14 @@ export type JavaError = { type: "ParseVersion"; data: string } | { type: "Execut export type LauncherError = { type: "DirError"; data: DirectoryError } | { type: "IOError"; data: IOError } | { type: "IngressError"; data: IngressError } | { type: "JavaError"; data: JavaError } | { type: "CryptoError"; data: CryptoError } | { type: "DiscordError"; data: DiscordError } | { type: "MetadataError"; data: MetadataError } | { type: "ClusterError"; data: ClusterError } | { type: "MinecraftAuthError"; data: MinecraftAuthError } | { type: "ProcessError"; data: ProcessError } | { type: "PackageError"; data: PackageError } | { type: "DaoError"; data: DaoError } | { type: "SerdeError"; data: string } | { type: "AnyhowError"; data: string } | { type: "DbError"; data: string } | { type: "ReqwestError"; data: string } | { type: "InterpulseError"; data: string } | { type: "RegexError"; data: string } | { type: "SemaphoreError"; data: string } | { type: "UrlError"; data: string } | { type: "TauriError"; data: string } -export type ManagedPackage = { id: string; slug: string; provider: Provider; package_type: PackageType; name: string; short_desc: string; body: string; version_ids: string[]; mc_versions: string[]; loaders: GameLoader[]; icon_url: string | null; created: string; updated: string; client: PackageSide; server: PackageSide; categories: string[]; license: PackageLicense | null; author: PackageAuthor; links: PackageLinks; status: PackageStatus; downloads: bigint; gallery: PackageGallery[] } +export type ManagedPackage = { id: string; slug: string; provider: Provider; package_type: PackageType; name: string; short_desc: string; body: ManagedPackageBody; +/** + * Won't have all versions for some providers, like CurseForge. + * Try making a request to get the versions if needed + */ +version_ids: string[]; mc_versions: string[]; loaders: GameLoader[]; icon_url: string | null; created: string; updated: string; client: PackageSide; server: PackageSide; categories: PackageCategories; license: PackageLicense | null; author: PackageAuthor; links: PackageLinks; status: PackageStatus; downloads: bigint; gallery: PackageGallery[] } + +export type ManagedPackageBody = { Url: string } | { Raw: string } export type ManagedUser = { id: string; username: string; url?: string | null; avatar_url?: string | null; bio?: string | null; is_organization_user?: boolean; role?: string | null } @@ -90,6 +97,8 @@ expires: string } export type PackageAuthor = { Team: { team_id: string; org_id: string | null } } | { Users: ManagedUser[] } +export type PackageCategories = { Mod: PackageModCategory[] } | { ResourcePack: PackageResourcePackCategory[] } | { Shader: PackageShaderCategory[] } | { DataPack: PackageModCategory[] } | { ModPack: PackageModPackCategory[] } + export type PackageDependencyType = "required" | "optional" | "embedded" | "incompatible" /** @@ -99,7 +108,7 @@ export type PackageDonationPlatform = "patreon" | "buymeacoffee" | "paypal" | "g export type PackageDonationUrl = { id: PackageDonationPlatform; url: string } -export type PackageError = { type: "NoPrimaryFile"; data: string } | { type: "IsModPack"; data: string } | { type: "Incompatible"; data: IncompatiblePackageType } +export type PackageError = { type: "NoPrimaryFile"; data: string } | { type: "IsNotModPack"; data: string } | { type: "Incompatible"; data: IncompatiblePackageType } | { type: "MissingApiKey"; data: string } | { type: "UnsupportedBodyType"; data: string } | { type: "UnsupportedAuthorType"; data: string } | { type: "UnsupportedModpackFormat"; data: string } export type PackageGallery = { url: string; thumbnail_url: string; title: string | null; description: string | null; featured: boolean | null } @@ -113,10 +122,18 @@ export type PackageLicense = { id: string; name: string; url: string | null } */ export type PackageLinks = { website: string | null; issues: string | null; source: string | null; wiki: string | null; donation: PackageDonationUrl[] | null; discord: string | null } +export type PackageModCategory = "Adventure" | "Library" | "Equipment" | "Patches" | "Cosmetic" | "Food" | "Magic" | "Information" | "Misc" | "Performance" | "Redstone" | "ServerUtil" | "Storage" | "Technology" | "Farming" | "Automation" | "Transport" | "Utility" | "QoL" | "WorldGen" | "Mobs" | "Economy" | "Social" + +export type PackageModPackCategory = "Technology" | "Quests" | "Optimization" | "Multiplayer" | "Magic" | "LightWeight" | "Combat" | "Challenging" | "Adventure" + export type PackageModel = { hash: string; file_name: string; version_id: string; published_at: string; display_name: string; display_version: string; package_type: PackageType; provider: Provider; package_id: string; mc_versions: DbVec; mc_loader: DbVec; icon: Icon | null } export type PackageReleaseType = "release" | "beta" | "alpha" +export type PackageResourcePackCategory = "X8" | "X16" | "X32" | "X48" | "X64" | "X128" | "X256" | "X512" | "VanillaLike" | "Utility" | "Tweaks" | "Themed" | "Simplistic" | "Realistic" | "Modded" | "Decoration" | "Cursed" | "Combat" | "Audio" | "Blocks" | "CoreShaders" | "Gui" | "Fonts" | "Equipment" | "Environment" | "Entities" | "Items" | "Locale" | "Models" + +export type PackageShaderCategory = "VanillaLike" | "SemiRealistic" | "Realistic" | "Fantasy" | "Cursed" | "Cartoon" | "Bloom" | "Atmosphere" | "Reflections" | "Shadows" | "PBR" | "PathTracing" | "Foliage" | "ColoredLightning" | "Potato" | "Low" | "Medium" | "High" | "Ultra" + export type PackageSide = "unknown" | "required" | "optional" | "unsupported" export type PackageStatus = "active" | "abandoned" @@ -139,7 +156,7 @@ export type Resolution = { width: number; height: number } export type SearchQuery = { query: string | null; offset: bigint | null; limit: bigint | null; sort: Sort | null; filters: Filters | null } -export type SearchResult = { project_id: string; project_type: string; slug: string; author: string; title: string; description: string; categories: string[]; display_categories: string[]; versions: string[]; downloads: bigint; icon_url: string; date_created: string; date_modified: string; latest_version: string; license: string; client_side: PackageSide; server_side: PackageSide; +export type SearchResult = { project_id: string; package_type: PackageType; slug: string; author: string; title: string; description: string; categories: PackageCategories; mc_versions: string[]; downloads: bigint; icon_url: string; date_created: string; date_modified: string; latest_version: string; license: string | null; client_side: PackageSide; server_side: PackageSide; /** * List of URLs to images */ @@ -149,7 +166,7 @@ export type SettingProfileModel = { name: string; java_id: bigint | null; res: R export type Settings = { global_game_settings: SettingProfileModel; allow_parallel_running_clusters: boolean; enable_gamemode: boolean; discord_enabled: boolean; max_concurrent_requests: bigint; settings_version: number; native_window_frame: boolean } -export type SettingsOsExtra = { enable_gamemode: boolean | null } +export type SettingsOsExtra = Record export type Sort = "Relevance" | "Downloads" | "Newest" | "Updated" @@ -212,11 +229,8 @@ export type VersionType = */ "old_beta" -const ARGS_MAP = { 'core':'{"getUser":["uuid"],"getLoadersForVersion":["mc_version"],"getPackage":["provider","slug"],"getMultiplePackages":["provider","slugs"],"updateClusterProfile":["name","profile"],"getGlobalProfile":[],"openMsaLogin":[],"writeSettings":["setting"],"getProfileOrDefault":["name"],"removeUser":["uuid"],"getClusterById":["id"],"getGameVersions":[],"getUsers":[],"getPackageVersions":["provider","slug","mc_versions","loaders","offset","limit"],"getPackageUser":["provider","slug"],"getClusters":[],"updateClusterById":["id","request"],"getUsersFromAuthor":["provider","author"],"launchCluster":["id","uuid"],"downloadPackage":["provider","package_id","version_id","cluster_id","skip_compatibility"],"createCluster":["options"],"setDefaultUser":["uuid"],"getWorlds":["id"],"removeCluster":["id"],"getScreenshots":["id"],"getDefaultUser":["fallback"],"readSettings":[],"searchPackages":["provider","query"]}', 'onelauncher':'{"set_window_style":["decorations"],"open_dev_tools":[],"return_error":[]}', 'events':'{"process":["event"],"message":["event"],"ingress":["event"]}' } -export type Router = { 'events': { ingress: (event: IngressPayload) => Promise, -message: (event: MessagePayload) => Promise, -process: (event: ProcessPayload) => Promise }, -'onelauncher': { return_error: () => Promise, +const ARGS_MAP = { 'onelauncher':'{"open_dev_tools":[],"set_window_style":["decorations"],"return_error":[]}', 'core':'{"getClusters":[],"searchPackages":["provider","query"],"getLoadersForVersion":["mc_version"],"getUsers":[],"getScreenshots":["id"],"getMultiplePackages":["provider","slugs"],"downloadPackage":["provider","package_id","version_id","cluster_id","skip_compatibility"],"getUsersFromAuthor":["provider","author"],"updateClusterProfile":["name","profile"],"updateClusterById":["id","request"],"getPackageBody":["provider","body"],"getDefaultUser":["fallback"],"removeCluster":["id"],"removeUser":["uuid"],"createCluster":["options"],"openMsaLogin":[],"readSettings":[],"getGameVersions":[],"launchCluster":["id","uuid"],"writeSettings":["setting"],"getProfileOrDefault":["name"],"getPackage":["provider","slug"],"getPackageVersions":["provider","slug","mc_version","loader","offset","limit"],"getClusterById":["id"],"getWorlds":["id"],"getGlobalProfile":[],"getUser":["uuid"],"setDefaultUser":["uuid"]}', 'events':'{"ingress":["event"],"message":["event"],"process":["event"]}' } +export type Router = { 'onelauncher': { return_error: () => Promise, open_dev_tools: () => Promise, set_window_style: (decorations: boolean) => Promise }, 'core': { getClusters: () => Promise, @@ -242,11 +256,14 @@ readSettings: () => Promise, writeSettings: (setting: Settings) => Promise, searchPackages: (provider: Provider, query: SearchQuery) => Promise>, getPackage: (provider: Provider, slug: string) => Promise, +getPackageBody: (provider: Provider, body: ManagedPackageBody) => Promise, getMultiplePackages: (provider: Provider, slugs: string[]) => Promise, -getPackageVersions: (provider: Provider, slug: string, mcVersions: string[] | null, loaders: GameLoader[] | null, offset: bigint, limit: bigint) => Promise>, -getPackageUser: (provider: Provider, slug: string) => Promise, +getPackageVersions: (provider: Provider, slug: string, mcVersion: string | null, loader: GameLoader | null, offset: bigint, limit: bigint) => Promise>, downloadPackage: (provider: Provider, packageId: string, versionId: string, clusterId: bigint, skipCompatibility: boolean | null) => Promise, -getUsersFromAuthor: (provider: Provider, author: PackageAuthor) => Promise } }; +getUsersFromAuthor: (provider: Provider, author: PackageAuthor) => Promise }, +'events': { ingress: (event: IngressPayload) => Promise, +message: (event: MessagePayload) => Promise, +process: (event: ProcessPayload) => Promise } }; export type { InferCommandOutput } diff --git a/apps/onelauncher/frontend/src/components/content/PackageItem.tsx b/apps/onelauncher/frontend/src/components/content/PackageItem.tsx index 1afb8891..8cd40969 100644 --- a/apps/onelauncher/frontend/src/components/content/PackageItem.tsx +++ b/apps/onelauncher/frontend/src/components/content/PackageItem.tsx @@ -32,9 +32,9 @@ export function PackageItem({ provider, ...item }: SearchResult & { provider: Pr return (
>, 'queryKey' | 'queryFn'> | undefined) { const validFilters = useMemo(() => Object.values(query.filters ?? {}).filter(a => a).length > 0, [query.filters]); - return useCommand('searchPackages', () => bindings.core.searchPackages(provider, validFilters ? query : { ...query, filters: null }), options); + return useCommand(`search.${provider}`, () => bindings.core.searchPackages(provider, validFilters ? query : { ...query, filters: null }), options); } export function usePackageData(provider: Provider, slug: string, options?: Omit, 'queryKey' | 'queryFn'> | undefined, key: false | BindingCommands | (string & {}) = `getPackage.${provider}.${slug}`) { return useCommand(key, () => bindings.core.getPackage(provider, slug), options); } -export function usePackageVersions(provider: Provider, slug: string, { mc_version, loader, offset, limit, ...options }: { mc_version?: string | null; loader?: GameLoader | null; offset?: number; limit: number } & Omit>, 'queryKey' | 'queryFn'>, key: false | BindingCommands | (string & {}) = `getPackageVersions.${provider}.${slug}.${mc_version}.${loader}`) { +export function usePackageVersions(provider: Provider, slug: string, { mc_version, loader, offset, limit, ...options }: { mc_version?: string | null; loader?: GameLoader | null; offset?: number; limit: number } & Omit>, 'queryKey' | 'queryFn'>, key: false | BindingCommands | (string & {}) = `getPackageVersions.${provider}.${slug}.${mc_version}.${loader}.${offset}`) { return useCommand(key, () => bindings.core.getPackageVersions(provider, slug, mc_version ?? null, loader ?? null, (offset ?? 0) as unknown as bigint, limit as unknown as bigint), options); } diff --git a/apps/onelauncher/frontend/src/routes/app/browser/package.$provider.$slug.tsx b/apps/onelauncher/frontend/src/routes/app/browser/package.$provider.$id.tsx similarity index 95% rename from apps/onelauncher/frontend/src/routes/app/browser/package.$provider.$slug.tsx rename to apps/onelauncher/frontend/src/routes/app/browser/package.$provider.$id.tsx index 76263a95..d73a807e 100644 --- a/apps/onelauncher/frontend/src/routes/app/browser/package.$provider.$slug.tsx +++ b/apps/onelauncher/frontend/src/routes/app/browser/package.$provider.$id.tsx @@ -5,7 +5,7 @@ import { useBrowserContext, useDownloadPackage, usePackageData, usePackageVersio import { ChooseClusterModal, useClusters } from '@/hooks/useCluster'; import usePagination from '@/hooks/usePagination'; import { bindings } from '@/main'; -import { abbreviateNumber, formatAsRelative, PROVIDERS, upperFirst } from '@/utils'; +import { abbreviateNumber, formatAsRelative, includes, PROVIDERS, upperFirst } from '@/utils'; import { useCommand } from '@onelauncher/common'; import { Button, Show, Tooltip } from '@onelauncher/common/components'; import { createFileRoute, Link } from '@tanstack/react-router'; @@ -18,14 +18,10 @@ import rehypeRaw from 'rehype-raw'; import remarkGfm from 'remark-gfm'; import { twMerge } from 'tailwind-merge'; -export const Route = createFileRoute('/app/browser/package/$provider/$slug')({ +export const Route = createFileRoute('/app/browser/package/$provider/$id')({ component: RouteComponent, }); -function includes(list: { includes: (arg0: TArray) => boolean }, element: T): element is TArray { - return list.includes(element as unknown as TArray); -} - function CustomA({ href, children, includeIcon, className, ...rest }: { href: string; children: any; includeIcon?: boolean } & HTMLProps) { return ( ({ }); function RouteComponent() { - const { provider, slug } = Route.useParams(); + const { provider, id } = Route.useParams(); if (!includes(PROVIDERS, provider)) throw new Error('Invalid provider'); - const packageData = usePackageData(provider, slug, {}); + const packageData = usePackageData(provider, id, {}); + const { data: packageBody } = useCommand('getPackageBody', () => bindings.core.getPackageBody(provider, packageData.data?.body ?? { Raw: 'No Package Data' })); const packageContextValue = useMemo(() => ({ pkg: packageData.data, }), [packageData.data]); @@ -81,9 +78,7 @@ function RouteComponent() { rehypePlugins={[rehypeRaw]} remarkPlugins={[remarkGfm]} > - {'Raw' in (packageData.data?.body ?? { Raw: '' }) - ? (packageData.data?.body ?? { Raw: '' }).Raw - : packageData.data?.body.Url} + {packageBody}
@@ -249,13 +244,13 @@ function BrowserSidebar({ package: pkg }: { package: ManagedPackage }) { function InstallButton() { const triggerRef = useRef(null); - const { provider, slug } = Route.useParams(); + const { provider, id } = Route.useParams(); if (!includes(PROVIDERS, provider)) throw new Error('invalid provider'); const [open, setOpen] = useState(false); const clusters = useClusters(); const browserContext = useBrowserContext(); - const { data: versions, isLoading: versionsLoading } = usePackageVersions(provider, slug, { + const { data: versions, isLoading: versionsLoading } = usePackageVersions(provider, id, { mc_version: browserContext.cluster ? browserContext.cluster.mc_version : null, loader: browserContext.cluster ? browserContext.cluster.mc_loader : null, limit: 1, @@ -408,11 +403,12 @@ function colorForType(type: string) { } function Versions() { - const { provider, slug } = Route.useParams(); + const { provider, id } = Route.useParams(); if (!includes(PROVIDERS, provider)) throw new Error('invalid provider'); const [offset, setOffset] = useState(0); - const { data: versions } = usePackageVersions(provider, slug, { + + const { data: versions } = usePackageVersions(provider, id, { limit: 20, offset, }); diff --git a/packages/core/src/api/packages/provider/modrinth/mod.rs b/packages/core/src/api/packages/provider/modrinth/mod.rs index 00fa0951..3796e2f3 100644 --- a/packages/core/src/api/packages/provider/modrinth/mod.rs +++ b/packages/core/src/api/packages/provider/modrinth/mod.rs @@ -342,6 +342,7 @@ struct ModrinthSearchResult { pub title: String, pub description: String, pub categories: Vec, + #[serde(alias = "versions")] pub mc_versions: Vec, pub downloads: usize, pub icon_url: String, diff --git a/packages/core/src/api/tauri/commands.rs b/packages/core/src/api/tauri/commands.rs index 62c17ee6..143a2e8e 100644 --- a/packages/core/src/api/tauri/commands.rs +++ b/packages/core/src/api/tauri/commands.rs @@ -1,5 +1,5 @@ use crate::api::packages::data::{ - ManagedPackage, ManagedUser, ManagedVersion, PackageAuthor, SearchQuery, SearchResult, + ManagedPackage, ManagedPackageBody, ManagedUser, ManagedVersion, PackageAuthor, SearchQuery, SearchResult }; use crate::api::packages::provider::ProviderExt; use crate::store::{Settings, State}; @@ -108,6 +108,9 @@ pub trait TauriLauncherApi { #[taurpc(alias = "getPackage")] async fn get_package(provider: Provider, slug: String) -> LauncherResult; + #[taurpc(alias = "getPackageBody")] + async fn get_package_body(provider: Provider, body: ManagedPackageBody) -> LauncherResult; + #[taurpc(alias = "getMultiplePackages")] async fn get_multiple_packages( provider: Provider, @@ -473,6 +476,10 @@ impl TauriLauncherApi for TauriLauncherApiImpl { provider.get(&slug).await } + async fn get_package_body(self, provider: Provider, body: ManagedPackageBody) -> LauncherResult { + provider.get_body(&body).await + } + async fn get_multiple_packages( self, provider: Provider,