diff --git a/apps/oneclient/frontend/src/assets/backgrounds/MinecraftBuilding.png b/apps/oneclient/frontend/src/assets/backgrounds/MinecraftBuilding.png new file mode 100644 index 00000000..94208370 Binary files /dev/null and b/apps/oneclient/frontend/src/assets/backgrounds/MinecraftBuilding.png differ diff --git a/apps/oneclient/frontend/src/assets/logos/oneclient2.svg b/apps/oneclient/frontend/src/assets/logos/oneclient2.svg new file mode 100644 index 00000000..7330f6ce --- /dev/null +++ b/apps/oneclient/frontend/src/assets/logos/oneclient2.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/apps/oneclient/frontend/src/bindings.gen.ts b/apps/oneclient/frontend/src/bindings.gen.ts index 9a5dfc93..29274b59 100644 --- a/apps/oneclient/frontend/src/bindings.gen.ts +++ b/apps/oneclient/frontend/src/bindings.gen.ts @@ -228,8 +228,13 @@ export type VersionType = */ "old_beta" -const ARGS_MAP = { 'events':'{"ingress":["event"],"message":["event"],"process":["event"]}', 'oneclient':'{"openDevTools":[],"getClustersGroupedByMajor":[]}', 'core':'{"getClusters":[],"updateClusterById":["id","request"],"getGameVersions":[],"searchPackages":["provider","query"],"getUser":["uuid"],"getWorlds":["id"],"readSettings":[],"getPackage":["provider","slug"],"getClusterById":["id"],"writeSettings":["setting"],"getMultiplePackages":["provider","slugs"],"getPackageVersions":["provider","slug","mc_version","loader","offset","limit"],"getLoadersForVersion":["mc_version"],"getPackageBody":["provider","body"],"launchCluster":["id","uuid"],"getUsersFromAuthor":["provider","author"],"getGlobalProfile":[],"getUsers":[],"removeUser":["uuid"],"removeCluster":["id"],"downloadPackage":["provider","package_id","version_id","cluster_id","skip_compatibility"],"createCluster":["options"],"getProfileOrDefault":["name"],"updateClusterProfile":["name","profile"],"openMsaLogin":[],"setDefaultUser":["uuid"],"getDefaultUser":["fallback"],"getScreenshots":["id"]}' } -export type Router = { 'core': { getClusters: () => Promise, +const ARGS_MAP = { 'events':'{"ingress":["event"],"message":["event"],"process":["event"]}', 'oneclient':'{"openDevTools":[],"getClustersGroupedByMajor":[]}', 'core':'{"launchCluster":["id","uuid"],"writeSettings":["setting"],"getUser":["uuid"],"getDefaultUser":["fallback"],"updateClusterById":["id","request"],"setDefaultUser":["uuid"],"createCluster":["options"],"getClusters":[],"openMsaLogin":[],"readSettings":[],"getGameVersions":[],"updateClusterProfile":["name","profile"],"searchPackages":["provider","query"],"getClusterById":["id"],"getProfileOrDefault":["name"],"getScreenshots":["id"],"getGlobalProfile":[],"getUsers":[],"getMultiplePackages":["provider","slugs"],"downloadPackage":["provider","package_id","version_id","cluster_id","skip_compatibility"],"getUsersFromAuthor":["provider","author"],"getLoadersForVersion":["mc_version"],"removeUser":["uuid"],"removeCluster":["id"],"getWorlds":["id"],"getPackage":["provider","slug"],"getPackageBody":["provider","body"],"getPackageVersions":["provider","slug","mc_version","loader","offset","limit"]}' } +export type Router = { 'events': { ingress: (event: IngressPayload) => Promise, +message: (event: MessagePayload) => Promise, +process: (event: ProcessPayload) => Promise }, +'oneclient': { openDevTools: () => Promise, +getClustersGroupedByMajor: () => Promise> }, +'core': { getClusters: () => Promise, getClusterById: (id: bigint) => Promise, removeCluster: (id: bigint) => Promise, createCluster: (options: CreateCluster) => Promise, @@ -256,12 +261,7 @@ getPackageBody: (provider: Provider, body: ManagedPackageBody) => Promise 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 }, -'oneclient': { openDevTools: () => Promise, -getClustersGroupedByMajor: () => Promise> }, -'events': { ingress: (event: IngressPayload) => Promise, -message: (event: MessagePayload) => Promise, -process: (event: ProcessPayload) => Promise } }; +getUsersFromAuthor: (provider: Provider, author: PackageAuthor) => Promise } }; export type { InferCommandOutput } diff --git a/apps/oneclient/frontend/src/components/GameBackground.tsx b/apps/oneclient/frontend/src/components/GameBackground.tsx index 6dca5458..281d70fc 100644 --- a/apps/oneclient/frontend/src/components/GameBackground.tsx +++ b/apps/oneclient/frontend/src/components/GameBackground.tsx @@ -4,10 +4,12 @@ import { twMerge } from 'tailwind-merge'; import CavesAndCliffs from '@/assets/backgrounds/CavesAndCliffs.jpg'; import HypixelSkyblockHub from '@/assets/backgrounds/HypixelSkyblockHub.png'; +import MinecraftBuilding from '@/assets/backgrounds/MinecraftBuilding.png'; const GameBackgrounds = { HypixelSkyblockHub, CavesAndCliffs, + MinecraftBuilding, }; export type GameBackgroundName = keyof typeof GameBackgrounds; diff --git a/apps/oneclient/frontend/src/components/Navbar.tsx b/apps/oneclient/frontend/src/components/Navbar.tsx index bdb6e0a0..02e136e6 100644 --- a/apps/oneclient/frontend/src/components/Navbar.tsx +++ b/apps/oneclient/frontend/src/components/Navbar.tsx @@ -77,7 +77,7 @@ function NavbarLink({ ); } -function NavbarButton({ +export function NavbarButton({ children, color = 'ghost', size = 'iconLarge', diff --git a/apps/oneclient/frontend/src/components/Stepper.tsx b/apps/oneclient/frontend/src/components/Stepper.tsx new file mode 100644 index 00000000..2a2d6850 --- /dev/null +++ b/apps/oneclient/frontend/src/components/Stepper.tsx @@ -0,0 +1,73 @@ +import type { OnboardingStep } from '@/routes/onboarding/route'; +import { useMatchRoute } from '@tanstack/react-router'; + +interface VerticalStepperProps { + steps: Array; + linearSteps: Array>; +}; + +export function Stepper({ steps, linearSteps }: VerticalStepperProps) { + const matchRoute = useMatchRoute(); + + const currentLinearIndex = linearSteps.findIndex(step => + matchRoute({ to: step.path })); + + const progressHeight = currentLinearIndex >= 0 ? `${(currentLinearIndex / (linearSteps.length - 1)) * 100}%` : '0%'; + + // const nextStep = () => { + // if (currentStep < steps.length - 1) + // setCurrentStep(currentStep + 1); + // }; + + // const prevStep = () => { + // if (currentStep > 0) + // setCurrentStep(currentStep - 1); + // }; + + // const goToStep = (stepIndex: number) => { + // setCurrentStep(stepIndex); + // }; + + return ( +
+
+
+
+ +
+
+ +
    + {steps.map((step, index) => ( +
  • goToStep(index)} + > +
    + + {step.title} + +
    +
  • + ))} +
+
+
+
+ ); +} diff --git a/apps/oneclient/frontend/src/routes/onboarding/account.tsx b/apps/oneclient/frontend/src/routes/onboarding/account.tsx new file mode 100644 index 00000000..60a283e7 --- /dev/null +++ b/apps/oneclient/frontend/src/routes/onboarding/account.tsx @@ -0,0 +1,9 @@ +import { createFileRoute } from '@tanstack/react-router' + +export const Route = createFileRoute('/onboarding/account')({ + component: RouteComponent, +}) + +function RouteComponent() { + return
Hello "/onboarding/account"!
+} diff --git a/apps/oneclient/frontend/src/routes/onboarding/finished.tsx b/apps/oneclient/frontend/src/routes/onboarding/finished.tsx new file mode 100644 index 00000000..63221c15 --- /dev/null +++ b/apps/oneclient/frontend/src/routes/onboarding/finished.tsx @@ -0,0 +1,9 @@ +import { createFileRoute } from '@tanstack/react-router' + +export const Route = createFileRoute('/onboarding/finished')({ + component: RouteComponent, +}) + +function RouteComponent() { + return
Hello "/onboarding/finished"!
+} diff --git a/apps/oneclient/frontend/src/routes/onboarding/index.tsx b/apps/oneclient/frontend/src/routes/onboarding/index.tsx new file mode 100644 index 00000000..75d55f23 --- /dev/null +++ b/apps/oneclient/frontend/src/routes/onboarding/index.tsx @@ -0,0 +1,15 @@ +import { createFileRoute } from '@tanstack/react-router'; + +export const Route = createFileRoute('/onboarding/')({ + component: RouteComponent, +}); + +function RouteComponent() { + return ( + <> +
+

OneClient

+
+ + ); +} diff --git a/apps/oneclient/frontend/src/routes/onboarding/language.tsx b/apps/oneclient/frontend/src/routes/onboarding/language.tsx new file mode 100644 index 00000000..eaa4214b --- /dev/null +++ b/apps/oneclient/frontend/src/routes/onboarding/language.tsx @@ -0,0 +1,13 @@ +import { createFileRoute } from '@tanstack/react-router'; + +export const Route = createFileRoute('/onboarding/language')({ + component: RouteComponent, +}); + +function RouteComponent() { + return ( +
+ Hello "/onboarding/language"! +
+ ); +} diff --git a/apps/oneclient/frontend/src/routes/onboarding/preferences/index.tsx b/apps/oneclient/frontend/src/routes/onboarding/preferences/index.tsx new file mode 100644 index 00000000..39adfe28 --- /dev/null +++ b/apps/oneclient/frontend/src/routes/onboarding/preferences/index.tsx @@ -0,0 +1,9 @@ +import { createFileRoute } from '@tanstack/react-router' + +export const Route = createFileRoute('/onboarding/preferences/')({ + component: RouteComponent, +}) + +function RouteComponent() { + return
Hello "/onboarding/preferences/"!
+} diff --git a/apps/oneclient/frontend/src/routes/onboarding/preferences/mods.tsx b/apps/oneclient/frontend/src/routes/onboarding/preferences/mods.tsx new file mode 100644 index 00000000..48e17f3f --- /dev/null +++ b/apps/oneclient/frontend/src/routes/onboarding/preferences/mods.tsx @@ -0,0 +1,9 @@ +import { createFileRoute } from '@tanstack/react-router' + +export const Route = createFileRoute('/onboarding/preferences/mods')({ + component: RouteComponent, +}) + +function RouteComponent() { + return
Hello "/onboarding/preferences/mods"!
+} diff --git a/apps/oneclient/frontend/src/routes/onboarding/preferences/versions.tsx b/apps/oneclient/frontend/src/routes/onboarding/preferences/versions.tsx new file mode 100644 index 00000000..b02241c2 --- /dev/null +++ b/apps/oneclient/frontend/src/routes/onboarding/preferences/versions.tsx @@ -0,0 +1,88 @@ +import CavesAndCliffs from '@/assets/backgrounds/CavesAndCliffs.jpg'; +import { Button } from '@onelauncher/common/components'; +import { createFileRoute } from '@tanstack/react-router'; +import { DotsVerticalIcon, PlusIcon } from '@untitled-theme/icons-react'; + +export const Route = createFileRoute('/onboarding/preferences/versions')({ + component: RouteComponent, +}); + +// placeholder +const versions = [ + { + version: '1.8.9', + tags: ['PvP', 'Skyblock', 'Bedwars', 'UHC'], + }, + { + version: '1.21.4', + tags: ['Nostalgia', 'Survival', 'UHC'], + }, + { + version: '1.21.4', + tags: ['PvP', 'Minigames', 'Survival', 'UHC'], + }, +]; + +function RouteComponent() { + return ( +
+
+

Starting Versions

+

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

+ +
+ {versions.map(version => ( + + ))} +
+
+ +
+
+
+
+
+ ); +} + +interface VersionCardProps { + version: string; + tags: Array; +} + +export function VersionCard({ version, tags }: VersionCardProps) { + return ( +
+
+ {`Minecraft + +
+ {tags.map(tag => ( +
+ {tag} +
+ ))} +
+ + + +
+ {version} +
+
+
+ ); +} diff --git a/apps/oneclient/frontend/src/routes/onboarding/route.tsx b/apps/oneclient/frontend/src/routes/onboarding/route.tsx new file mode 100644 index 00000000..b534dc2a --- /dev/null +++ b/apps/oneclient/frontend/src/routes/onboarding/route.tsx @@ -0,0 +1,230 @@ +import type { PropsWithChildren } from 'react'; +import Logo from '@/assets/logos/oneclient2.svg'; +import { LoaderSuspense, NavbarButton } from '@/components'; +import { GameBackground } from '@/components/GameBackground'; +import { Stepper } from '@/components/Stepper'; +import useAppShellStore from '@/stores/appShellStore'; +import { Button } from '@onelauncher/common/components'; +import { createFileRoute, Link, Outlet } from '@tanstack/react-router'; +import { Window } from '@tauri-apps/api/window'; +import { MinusIcon, SquareIcon, XCloseIcon } from '@untitled-theme/icons-react'; +import { MouseParallax } from 'react-just-parallax'; + +export const Route = createFileRoute('/onboarding')({ + component: RouteComponent, + loader: ({ location }) => { + const currentStepIndex = LINEAR_ONBOARDING_STEPS.findIndex( + step => step.path === location.pathname, + ); + + const isFirstStep = currentStepIndex === 0; + const isLastStep = currentStepIndex === LINEAR_ONBOARDING_STEPS.length - 1; + + return { + isFirstStep, + isLastStep, + previousPath: + currentStepIndex > 0 + ? LINEAR_ONBOARDING_STEPS[currentStepIndex - 1]?.path + : undefined, + nextPath: + currentStepIndex < LINEAR_ONBOARDING_STEPS.length - 1 + ? LINEAR_ONBOARDING_STEPS[currentStepIndex + 1]?.path + : undefined, + currentStepIndex, + }; + }, +}); + +export interface OnboardingStep { + path: string; + title: string; + subSteps?: Array; +}; + +const ONBOARDING_STEPS: Array = [ + { + path: '/onboarding', + title: 'Welcome', + }, + { + path: '/onboarding/language', + title: 'Set Language', + }, + { + path: '/onboarding/account', + title: 'Account', + }, + { + path: '/onboarding/preferences/', + title: 'Preferences', + subSteps: [ + { + path: '/onboarding/preferences/versions', + title: 'Versions', + }, + { + path: '/onboarding/preferences/mods', + title: 'Mods', + }, + ], + }, + { + path: '/onboarding/finished', + title: 'Finished', + }, +]; + +function getLinearSteps(steps: Array): Array> { + return steps.flatMap(step => (step.subSteps ? step.subSteps : step)); +} + +const LINEAR_ONBOARDING_STEPS = getLinearSteps(ONBOARDING_STEPS); + +function RouteComponent() { + return ( + + +
+ + + + +
+
+
+ ); +} + +function AppShell({ + children, +}: PropsWithChildren) { + const { isFirstStep } = Route.useLoaderData(); + + return ( +
+ + +
+
+
+
+ logo +
+
+ + + +
+

version info

+
+
+ +
+ {children} +
+
+
+ ); +} + +function BackgroundGradient() { + const background = useAppShellStore(state => state.background); + + if (background === 'none') + return undefined; + + return ( +
+ {/* Linear black gradient: left -> right */} + {/*
+
*/} + + {/* Radial black gradient */} +
+
+ + {/* Linear black gradient: bottom -> 200 px up */} +
+
+ + + + +
+ ); +} + +export function OnboardingNavigation() { + const { isFirstStep, isLastStep, previousPath, nextPath } = Route.useLoaderData(); + + return ( +
+
+ {!isFirstStep && previousPath && ( + + + + )} +
+
+ {nextPath && ( + + + + )} +
+
+ ); +} + +export function Navbar() { + const onMinimize = () => Window.getCurrent().minimize(); + const onMaximize = () => Window.getCurrent().toggleMaximize(); + const onClose = () => Window.getCurrent().close(); + + return ( + + ); +}