diff --git a/apps/oneclient/frontend/src/bindings.gen.ts b/apps/oneclient/frontend/src/bindings.gen.ts index db2c583c..d3e6253c 100644 --- a/apps/oneclient/frontend/src/bindings.gen.ts +++ b/apps/oneclient/frontend/src/bindings.gen.ts @@ -167,7 +167,7 @@ export type SettingProfileModel = { name: string; java_id: number | null; res: R export type Settings = { global_game_settings: SettingProfileModel; allow_parallel_running_clusters: boolean; enable_gamemode: boolean; discord_enabled: boolean; max_concurrent_requests: number; settings_version: number; native_window_frame: boolean } -export type SettingsOsExtra = Record +export type SettingsOsExtra = { enable_gamemode: boolean | null } export type Sort = "Relevance" | "Downloads" | "Newest" | "Updated" @@ -230,14 +230,11 @@ export type VersionType = */ "old_beta" -const ARGS_MAP = { 'core':'{"getPackageBody":["provider","body"],"getLogByName":["id","name"],"getClusters":[],"updateClusterProfile":["name","profile"],"getPackage":["provider","slug"],"getClusterById":["id"],"getGameVersions":[],"getUsers":[],"setDefaultUser":["uuid"],"openMsaLogin":[],"removeCluster":["id"],"getScreenshots":["id"],"removeUser":["uuid"],"searchPackages":["provider","query"],"getUsersFromAuthor":["provider","author"],"launchCluster":["id","uuid"],"getLogs":["id"],"updateClusterById":["id","request"],"getLoadersForVersion":["mc_version"],"getUser":["uuid"],"getProfileOrDefault":["name"],"fetchMinecraftProfile":["uuid"],"getWorlds":["id"],"writeSettings":["setting"],"getDefaultUser":["fallback"],"readSettings":[],"getGlobalProfile":[],"getMultiplePackages":["provider","slugs"],"downloadPackage":["provider","package_id","version_id","cluster_id","skip_compatibility"],"getPackageVersions":["provider","slug","mc_version","loader","offset","limit"],"createCluster":["options"]}', 'folders':'{"fromCluster":["folder_name"],"openCluster":["folder_name"]}', 'events':'{"message":["event"],"ingress":["event"],"process":["event"]}', 'oneclient':'{"getClustersGroupedByMajor":[],"openDevTools":[]}' } -export type Router = { 'folders': { fromCluster: (folderName: string) => Promise, -openCluster: (folderName: string) => Promise }, -'oneclient': { openDevTools: () => Promise, +const ARGS_MAP = { 'folders':'{"fromCluster":["folder_name"],"openCluster":["folder_name"]}', 'events':'{"message":["event"],"ingress":["event"],"process":["event"]}', 'oneclient':'{"openDevTools":[],"getClustersGroupedByMajor":[]}', 'core':'{"getPackageVersions":["provider","slug","mc_version","loader","offset","limit"],"getProfileOrDefault":["name"],"updateClusterById":["id","request"],"createCluster":["options"],"getLogByName":["id","name"],"getGameVersions":[],"getScreenshots":["id"],"writeSettings":["setting"],"getMultiplePackages":["provider","slugs"],"getLogs":["id"],"removeUser":["uuid"],"downloadPackage":["provider","package_id","version_id","cluster_id","skip_compatibility"],"getDefaultUser":["fallback"],"getLoadersForVersion":["mc_version"],"getClusters":[],"setDefaultUser":["uuid"],"getGlobalProfile":[],"getClusterById":["id"],"updateClusterProfile":["name","profile"],"getUsers":[],"getUsersFromAuthor":["provider","author"],"openMsaLogin":[],"launchCluster":["id","uuid"],"searchPackages":["provider","query"],"getWorlds":["id"],"getUser":["uuid"],"getPackageBody":["provider","body"],"readSettings":[],"removeCluster":["id"],"getPackage":["provider","slug"],"fetchMinecraftProfile":["uuid"]}' } +export type Router = { 'oneclient': { openDevTools: () => Promise, getClustersGroupedByMajor: () => Promise> }, -'events': { ingress: (event: IngressPayload) => Promise, -message: (event: MessagePayload) => Promise, -process: (event: ProcessPayload) => Promise }, +'folders': { fromCluster: (folderName: string) => Promise, +openCluster: (folderName: string) => Promise }, 'core': { getClusters: () => Promise, getClusterById: (id: number) => Promise, removeCluster: (id: number) => Promise, @@ -268,7 +265,10 @@ getMultiplePackages: (provider: Provider, slugs: string[]) => Promise Promise>, downloadPackage: (provider: Provider, packageId: string, versionId: string, clusterId: number, skipCompatibility: boolean | null) => Promise, getUsersFromAuthor: (provider: Provider, author: PackageAuthor) => Promise, -fetchMinecraftProfile: (uuid: string) => Promise } }; +fetchMinecraftProfile: (uuid: string) => Promise }, +'events': { ingress: (event: IngressPayload) => Promise, +message: (event: MessagePayload) => Promise, +process: (event: ProcessPayload) => Promise } }; export type { InferCommandOutput } diff --git a/apps/oneclient/frontend/src/hooks/useClusters.ts b/apps/oneclient/frontend/src/hooks/useClusters.ts new file mode 100644 index 00000000..620304ff --- /dev/null +++ b/apps/oneclient/frontend/src/hooks/useClusters.ts @@ -0,0 +1,33 @@ +import { bindings } from '@/main'; +import useAppShellStore from '@/stores/appShellStore'; +import { useCommandSuspense } from '@onelauncher/common'; + +export function useLastPlayedClusters() { + return useCommandSuspense( + ['getClusters', 'sortedByLastPlayed'], + bindings.core.getClusters, + { + select: data => data + .sort((a, b) => { + if (a.last_played && b.last_played) + return new Date(b.last_played).getTime() - new Date(a.last_played).getTime(); + else if (a.last_played) + return -1; // a has last_played, b does not + else if (b.last_played) + return 1; // b has last_played, a does not + + const aVersion = a.mc_version.replaceAll('.', ''); + const bVersion = b.mc_version.replaceAll('.', ''); + return Number.parseInt(bVersion) - Number.parseInt(aVersion); + }), + }, + ); +} + +export function useActiveCluster() { + const clusterId = useAppShellStore(state => state.activeClusterId); + + const { data: clusters } = useLastPlayedClusters(); + + return clusters.find(c => c.id === clusterId) ?? clusters.at(0); +} diff --git a/apps/oneclient/frontend/src/routes/app/index.tsx b/apps/oneclient/frontend/src/routes/app/index.tsx index 41a17146..06eb5dee 100644 --- a/apps/oneclient/frontend/src/routes/app/index.tsx +++ b/apps/oneclient/frontend/src/routes/app/index.tsx @@ -1,16 +1,17 @@ -import type { GameLoader } from '@/bindings.gen'; -import type { HTMLAttributes } from 'react'; +import type { ClusterModel, GameLoader } from '@/bindings.gen'; +import type { ButtonProps } from 'react-aria-components'; import { GameBackground } from '@/components'; +import { useActiveCluster, useLastPlayedClusters } from '@/hooks/useClusters'; import { bindings } from '@/main'; +import useAppShellStore from '@/stores/appShellStore'; import { prettifyLoader } from '@/utils/loaders'; import { animations, transitions } from '@/utils/motion'; -import { getVersionInfo } from '@/utils/versionMap'; -import { useCommandSuspense } from '@onelauncher/common'; +import { getVersionInfo, getVersionInfoOrDefault } from '@/utils/versionMap'; import { Button } from '@onelauncher/common/components'; -import { createFileRoute } from '@tanstack/react-router'; -import { DotsGridIcon, PlusIcon, Settings04Icon } from '@untitled-theme/icons-react'; +import { createFileRoute, useNavigate } from '@tanstack/react-router'; +import { DotsGridIcon, Settings04Icon } from '@untitled-theme/icons-react'; import { motion } from 'motion/react'; -import { MouseParallax } from 'react-just-parallax'; +import { Button as AriaButton } from 'react-aria-components'; import { twMerge } from 'tailwind-merge'; export const Route = createFileRoute('/app/')({ @@ -18,64 +19,35 @@ export const Route = createFileRoute('/app/')({ }); function RouteComponent() { - const { data: clusters } = useCommandSuspense( - ['getClusters'], - bindings.core.getClusters, - { - select(data) { - const sorted = data - .filter(cluster => cluster.last_played !== null) - .sort((a, b) => { - if (a.last_played && b.last_played) - return new Date(b.last_played).getTime() - new Date(a.last_played).getTime(); - else if (a.last_played) - return -1; // a has last_played, b does not - else if (b.last_played) - return 1; // b has last_played, a does not + const { data: lastPlayedClusters } = useLastPlayedClusters(); - const aVersion = a.mc_version.replaceAll('.', ''); - const bVersion = b.mc_version.replaceAll('.', ''); - return Number.parseInt(bVersion) - Number.parseInt(aVersion); - }); + const setActiveClusterId = useAppShellStore(state => state.setActiveClusterId); + const activeCluster = useActiveCluster(); - return [ - sorted[0], - sorted[1], - sorted[3], - ]; - }, - }, - ); + const navigate = useNavigate({ + from: Route.id, + }); return (
- -

1.8.9

-

The Bountiful Update

- -
- - -
-
+ - {clusters.map((cluster) => { - // eslint-disable-next-line ts/no-unnecessary-condition -- rule broken - return cluster === undefined - ? ( - - - - ) - : ( - - ); - })} + {lastPlayedClusters.slice(0, 3).map(cluster => ( + setActiveClusterId(cluster.id)} + version={cluster.mc_version} + /> + ))} - + navigate({ to: '/app/clusters' })} + > @@ -83,24 +55,81 @@ function RouteComponent() { ); } +function ActiveClusterInfo({ + cluster, +}: { + cluster: ClusterModel | undefined; +}) { + const versionInfo = getVersionInfoOrDefault(cluster?.mc_version); + const navigate = useNavigate({ + from: Route.id, + }); + + const launch = () => { + if (!cluster) + return; + + bindings.core.launchCluster(cluster.id, null); + }; + + const viewCluster = () => { + if (!cluster) + return; + + navigate({ + to: '/app/cluster/overview', + search: { + clusterId: cluster.id, + }, + }); + }; + + return ( + +

{cluster?.mc_version} {prettifyLoader(cluster?.mc_loader ?? 'vanilla')}

+

{versionInfo.shortDescription}

+ +
+ + +
+
+ ); +} + interface RecentsCardProps { version: string; loader: GameLoader; + onPress: () => void; + active: boolean; } -const BLUR = '30px'; -function RecentsCard({ version, loader }: RecentsCardProps) { +function RecentsCard({ + version, + loader, + onPress, + active, +}: RecentsCardProps) { const versionInfo = getVersionInfo(version); if (!versionInfo) - return Unknown Version; + return ( + +

Unknown Version

+
+ ); return ( - -
- - - + +
+
-

{version} {prettifyLoader(loader)}

+

{version} {prettifyLoader(loader)}

); } +const BLUR = '30px'; function Card({ blur = true, children, className, style, + onPress, }: { blur?: boolean; - children?: React.ReactNode; -} & HTMLAttributes) { + className?: string | undefined; +} & ButtonProps & React.RefAttributes) { return ( -
{children} -
+ ); } diff --git a/apps/oneclient/frontend/src/routes/app/route.tsx b/apps/oneclient/frontend/src/routes/app/route.tsx index 1c7d87c4..d75289d7 100644 --- a/apps/oneclient/frontend/src/routes/app/route.tsx +++ b/apps/oneclient/frontend/src/routes/app/route.tsx @@ -1,8 +1,10 @@ import type { PropsWithChildren } from 'react'; import { LoaderSuspense, Navbar } from '@/components'; import { GameBackground } from '@/components/GameBackground'; -import useAppShellStore from '@/stores/appShellStore'; +import { useActiveCluster } from '@/hooks/useClusters'; +import { getVersionInfoOrDefault } from '@/utils/versionMap'; import { createFileRoute, Outlet } from '@tanstack/react-router'; +import { motion } from 'motion/react'; import { MouseParallax } from 'react-just-parallax'; export const Route = createFileRoute('/app')({ @@ -50,10 +52,8 @@ function AppShell({ } function BackgroundGradient() { - const background = useAppShellStore(state => state.background); - - if (background === 'none') - return undefined; + const cluster = useActiveCluster(); + const versionInfo = getVersionInfoOrDefault(cluster?.mc_version); return (
@@ -83,10 +83,23 @@ function BackgroundGradient() {
- + + +
); diff --git a/apps/oneclient/frontend/src/stores/appShellStore.ts b/apps/oneclient/frontend/src/stores/appShellStore.ts index e9380efe..4d19126a 100644 --- a/apps/oneclient/frontend/src/stores/appShellStore.ts +++ b/apps/oneclient/frontend/src/stores/appShellStore.ts @@ -1,17 +1,17 @@ import { create } from 'zustand'; export interface AppShellStore { - background: 'gradientOverlay' | 'none'; + activeClusterId: number; } export interface AppShellStoreActions { - setBackground: (background: AppShellStore['background']) => void; + setActiveClusterId: (cluster: AppShellStore['activeClusterId']) => void; } export const useAppShellStore = create()(set => ({ - background: 'gradientOverlay', + activeClusterId: 0, - setBackground: background => set({ background }), + setActiveClusterId: cluster => set({ activeClusterId: cluster }), })); export default useAppShellStore; diff --git a/apps/oneclient/frontend/src/utils/motion.ts b/apps/oneclient/frontend/src/utils/motion.ts index 44ceedac..f290de54 100644 --- a/apps/oneclient/frontend/src/utils/motion.ts +++ b/apps/oneclient/frontend/src/utils/motion.ts @@ -41,4 +41,13 @@ export const animations = { bottom: '0', }, }, + + fadeIn: { + initial: { + opacity: 0, + }, + animate: { + opacity: 1, + }, + }, } as const satisfies Record;