import type { GameLoader } from '@/bindings.gen'; import { GameBackground, LaunchButton, SheetPage } from '@/components'; import { useCachedImage } from '@/hooks/useCachedImage'; import { bindings } from '@/main'; import useClusterStore from '@/stores/clusterStore'; import { prettifyLoader } from '@/utils/loaders'; import { getVersionInfo, getVersionInfoOrDefault, parseMcVersion } from '@/utils/versionMap'; import { useCommandSuspense } from '@onelauncher/common'; import { Button, Dropdown } from '@onelauncher/common/components'; import { createFileRoute } from '@tanstack/react-router'; import { ArrowRightIcon } from '@untitled-theme/icons-react'; import { useCallback, useMemo, useState } from 'react'; import { twMerge } from 'tailwind-merge'; export const Route = createFileRoute('/app/clusters')({ component: RouteComponent, }); function RouteComponent() { const prevMinorVersions = useClusterStore(state => state.minorVersions); const setMinorVersion = useClusterStore(state => state.setMinorVersion); const modLoaders = useClusterStore(state => state.modLoaders); const setModLoader = useClusterStore(state => state.setModLoader); const navigate = Route.useNavigate(); const { data: clusters } = useCommandSuspense(['getClustersGroupedByMajor'], bindings.oneclient.getClustersGroupedByMajor); const { data: versions } = useCommandSuspense(['getVersions'], () => bindings.oneclient.getVersions()); const loadMinorVersion = useCallback((major: number) => { const existing = prevMinorVersions.find(v => v.major === major); if (existing) return existing.minor; return undefined; }, [prevMinorVersions]); const [majorVersion, setMajorVersion] = useState(() => { const keys = Object.keys(clusters); if (keys.length === 0) return 0; return Number.parseInt(keys[0]); }); const [activeMinorVersion, setActiveMinorVersion] = useState(); const minorVersions = useMemo(() => { const list = clusters[majorVersion]; if (!list || list.length === 0) return []; const versions = list.map(c => parseMcVersion(c.mc_version)?.minor).filter((v): v is number => v !== undefined); return Array.from(new Set(versions)).sort((a, b) => a - b); }, [clusters, majorVersion]); // Derive effective minor version without side effects in useMemo const effectiveMinorVersion = useMemo(() => { if (minorVersions.length === 0) return undefined; // If user has explicitly selected a minor version valid for this major, use it if (activeMinorVersion !== undefined && minorVersions.includes(activeMinorVersion)) return activeMinorVersion; // Otherwise fall back to stored version or first available const storedMinor = loadMinorVersion(majorVersion); if (storedMinor !== undefined && minorVersions.includes(storedMinor)) return storedMinor; return minorVersions[0]; }, [minorVersions, activeMinorVersion, loadMinorVersion, majorVersion]); const modLoader = useMemo(() => { if (!effectiveMinorVersion) return undefined; return modLoaders[`${majorVersion}.${effectiveMinorVersion}`]; }, [modLoaders, majorVersion, effectiveMinorVersion]); const loaders = useMemo(() => { const list = clusters[majorVersion]; if (!list || list.length === 0) return []; const loadersSet: Set = new Set(); for (const cluster of list) if (cluster.mc_loader !== 'vanilla') loadersSet.add(cluster.mc_loader); return Array.from(loadersSet).sort(); }, [clusters, majorVersion]); const cluster = useMemo(() => { const list = clusters[majorVersion]; if (effectiveMinorVersion === undefined) return list?.[0]; return list?.find((c) => { const parsed = parseMcVersion(c.mc_version); const versionMatch = parsed?.minor === effectiveMinorVersion; const loaderCheck = modLoader ? c.mc_loader === modLoader : true; return versionMatch && loaderCheck; }); }, [clusters, majorVersion, effectiveMinorVersion, modLoader]); const selectedMinorArtPath = useMemo(() => { const onlineCluster = versions.clusters.find(c => c.major_version === majorVersion); if (!onlineCluster) return undefined; const selectedMinor = parseMcVersion(cluster?.mc_version)?.minor; if (selectedMinor === undefined) return onlineCluster.art; const loaderMatchedEntry = onlineCluster.entries.find(entry => entry.minor_version === selectedMinor && entry.loader === cluster?.mc_loader); const minorMatchedEntry = onlineCluster.entries.find(entry => entry.minor_version === selectedMinor); return loaderMatchedEntry?.art ?? minorMatchedEntry?.art ?? onlineCluster.art; }, [versions.clusters, majorVersion, cluster?.mc_version, cluster?.mc_loader]); const selectedMinorArtSrc = useCachedImage(selectedMinorArtPath); const versionInfo = useMemo(() => getVersionInfoOrDefault(cluster?.mc_version), [cluster]); const view = useCallback(() => { if (!cluster) return; navigate({ to: `/app/cluster/mods`, search: { clusterId: cluster.id, }, }); }, [cluster, navigate]); if (!cluster) return ( } headerSmall={} >

Something wen't wrong while getting a cluster

Please reload and if this does not work please contact support

); return ( } headerSmall={} >
{Object.keys(clusters).map((majorStr) => { const major = Number.parseInt(majorStr, 10); const isSelected = major === majorVersion; const onlineCluster = versions.clusters.find(c => c.major_version === major); return ( setMajorVersion(major)} tags={[...new Set(clusters[major]?.flatMap(c => prettifyLoader(c.mc_loader)))]} /> ); })}
{selectedMinorArtSrc ? ( {`Minecraft ) : ( )}

Version {cluster.mc_version}

{versionInfo.longDescription}

{minorVersions.length > 1 && (

Minor Version

{ setActiveMinorVersion(Number(selected)); setMinorVersion(majorVersion, Number(selected)); }} selectedKey={effectiveMinorVersion} > {minorVersions.map((minorVersion) => { const fullVer = `${versionInfo.prettyName}.${minorVersion}`; return ( {fullVer} ); })}
)} {loaders.length > 1 && (

Mod Loader

{ if (!effectiveMinorVersion) return; setModLoader(`${majorVersion}.${effectiveMinorVersion}`, selected as GameLoader); }} selectedKey={effectiveMinorVersion ? (modLoaders[`${majorVersion}.${effectiveMinorVersion}`] ?? loaders[0]) : loaders[0]} > {loaders.map(loader => ( {prettifyLoader(loader)} ))}
)}
); } function HeaderLarge() { return (

Clusters

Something something in corporate style fashion about picking your preferred gamemodes and versions and optionally loader so that oneclient can pick something for them

); } function HeaderSmall() { return (

Clusters

); } function ClusterEntry({ artPath, isSelected, major_version, onClick, tags, }: { artPath: string | null | undefined; isSelected: boolean; major_version: number; onClick: () => unknown; tags: Array; }) { const artSrc = useCachedImage(artPath); const info = getVersionInfo(major_version); if (!info) return undefined; return (
{artSrc ? ( {`Minecraft ) : ( )}
{info.tags.concat(tags).map(tag => (
{tag}
))}

Version {info.prettyName}

); }