Merge branch 'oneclient/main' of https://github.com/Polyfrost/OneLauncher into refactor/frontend
This commit is contained in:
commit
f025673d9d
12 changed files with 312 additions and 126 deletions
|
|
@ -13,7 +13,7 @@ export function Navbar() {
|
||||||
const onClose = () => Window.getCurrent().close();
|
const onClose = () => Window.getCurrent().close();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<nav className="flex flex-row items-center justify-between h-20" data-tauri-drag-region="true">
|
<nav className="flex flex-row items-center justify-between h-20 px-12 z-50" data-tauri-drag-region="true">
|
||||||
<div className="flex flex-1 pointer-events-none">
|
<div className="flex flex-1 pointer-events-none">
|
||||||
<LauncherLogo height={47} width={230} />
|
<LauncherLogo height={47} width={230} />
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
131
apps/oneclient/frontend/src/components/SheetPage.tsx
Normal file
131
apps/oneclient/frontend/src/components/SheetPage.tsx
Normal file
|
|
@ -0,0 +1,131 @@
|
||||||
|
import type { MotionValue } from 'motion/react';
|
||||||
|
import useAppShellStore from '@/stores/appShellStore';
|
||||||
|
import { getLocationName } from '@/utils/locationMapping';
|
||||||
|
import { Button } from '@onelauncher/common/components';
|
||||||
|
import { Outlet, useNavigate } from '@tanstack/react-router';
|
||||||
|
import { ArrowLeftIcon } from '@untitled-theme/icons-react';
|
||||||
|
import { motion, useScroll, useTransform } from 'motion/react';
|
||||||
|
import { OverlayScrollbarsComponent } from 'overlayscrollbars-react';
|
||||||
|
import React, { useRef } from 'react';
|
||||||
|
|
||||||
|
export function SheetPage({
|
||||||
|
headerSmall,
|
||||||
|
headerLarge,
|
||||||
|
}: {
|
||||||
|
headerSmall: React.ReactNode;
|
||||||
|
headerLarge: React.ReactNode;
|
||||||
|
}) {
|
||||||
|
const scrollContainer = useRef<HTMLElement>(null);
|
||||||
|
const headerLargeRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
|
const { scrollYProgress } = useScroll({
|
||||||
|
axis: 'y',
|
||||||
|
container: scrollContainer,
|
||||||
|
target: headerLargeRef,
|
||||||
|
layoutEffect: false,
|
||||||
|
offset: ['end start', 'start start'],
|
||||||
|
});
|
||||||
|
|
||||||
|
const smallTop = useTransform(scrollYProgress, [0, 1], ['0', '100%']);
|
||||||
|
const smallOpacity = useTransform(scrollYProgress, [0, 1], ['1', '0']);
|
||||||
|
const largeOpacity = useTransform(scrollYProgress, [0, 1], ['0', '1']);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="h-full">
|
||||||
|
<HeaderBackgrounds scrollYProgress={scrollYProgress} />
|
||||||
|
|
||||||
|
<div className="flex flex-col w-full min-h-full h-0">
|
||||||
|
<div className="sticky top-0 left-0 mx-12 flex flex-row gap-4 h-8 overflow-hidden">
|
||||||
|
<GoBackButton />
|
||||||
|
|
||||||
|
<motion.div
|
||||||
|
className="relative w-full h-full left-0" style={{
|
||||||
|
top: smallTop,
|
||||||
|
opacity: smallOpacity,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{headerSmall}
|
||||||
|
</motion.div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<OverlayScrollbarsComponent className="flex flex-col flex-1 mask-t-from-97% pt-4 relative" ref={ref => scrollContainer.current = ref?.osInstance()?.elements().content ?? null}>
|
||||||
|
<div className="flex flex-col mx-12 gap-4">
|
||||||
|
<motion.div className="w-full" ref={headerLargeRef} style={{ opacity: largeOpacity }}>
|
||||||
|
{/* <headerLarge ref={headerLargeRef as React.RefObject<HTMLDivElement>} /> */}
|
||||||
|
{headerLarge}
|
||||||
|
</motion.div>
|
||||||
|
|
||||||
|
<motion.div
|
||||||
|
animate={{ bottom: 0 }}
|
||||||
|
className="relative flex flex-col px-10 py-6 mb-8 bg-page-elevated rounded-2xl"
|
||||||
|
initial={{ bottom: '-80px' }}
|
||||||
|
>
|
||||||
|
<Outlet />
|
||||||
|
</motion.div>
|
||||||
|
</div>
|
||||||
|
</OverlayScrollbarsComponent>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function GoBackButton() {
|
||||||
|
const prevLocation = useAppShellStore(state => state.prevLocation);
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
const onClick = () => {
|
||||||
|
if (!prevLocation)
|
||||||
|
return;
|
||||||
|
|
||||||
|
navigate({
|
||||||
|
to: prevLocation.pathname,
|
||||||
|
search: prevLocation.search,
|
||||||
|
hash: prevLocation.hash,
|
||||||
|
state: prevLocation.state,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!prevLocation)
|
||||||
|
return undefined;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Button color="ghost" onClick={onClick}>
|
||||||
|
<ArrowLeftIcon height={20} />
|
||||||
|
<span className="h-5">{getLocationName(prevLocation.pathname)}</span>
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function HeaderBackgrounds({
|
||||||
|
scrollYProgress,
|
||||||
|
}: {
|
||||||
|
scrollYProgress: MotionValue<number>;
|
||||||
|
}) {
|
||||||
|
const top = useTransform(scrollYProgress, [1, 0], ['0px', '-60px']);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<motion.div
|
||||||
|
className="absolute left-0 h-full w-full"
|
||||||
|
style={{
|
||||||
|
top,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="absolute -z-10 -top-20 h-50 left-0 w-screen animate-fade animate-duration-200" style={{
|
||||||
|
background: 'rgba(0, 0, 0, 0.20)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
className="relative h-50 top-0 left-0 w-screen animate-fade animate-duration-200"
|
||||||
|
style={{
|
||||||
|
background: 'linear-gradient(180deg, rgba(17, 23, 28, 0.00) 0%, #11171C 60.1%)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="relative top-0 left-0 w-full h-full bg-page animate-fade animate-duration-200"></div>
|
||||||
|
</motion.div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -5,7 +5,7 @@ import { createRootRouteWithContext, Outlet, useRouter } from '@tanstack/react-r
|
||||||
import { TanStackRouterDevtools } from '@tanstack/react-router-devtools';
|
import { TanStackRouterDevtools } from '@tanstack/react-router-devtools';
|
||||||
import { RouterProvider } from 'react-aria-components';
|
import { RouterProvider } from 'react-aria-components';
|
||||||
|
|
||||||
interface MyRouterContext {
|
interface AppRouterContext {
|
||||||
queryClient: QueryClient;
|
queryClient: QueryClient;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -16,7 +16,7 @@ declare module 'react-aria-components' {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Route = createRootRouteWithContext<MyRouterContext>()({
|
export const Route = createRootRouteWithContext<AppRouterContext>()({
|
||||||
component: RootRoute,
|
component: RootRoute,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -28,7 +28,7 @@ function RootRoute() {
|
||||||
navigate={(to, options) => router.navigate({ to, ...options })}
|
navigate={(to, options) => router.navigate({ to, ...options })}
|
||||||
useHref={to => router.buildLocation({ to }).href}
|
useHref={to => router.buildLocation({ to }).href}
|
||||||
>
|
>
|
||||||
<div className="h-screen flex flex-col overflow-hidden text-fg-primary px-12">
|
<div className="h-screen flex flex-col overflow-hidden text-fg-primary">
|
||||||
<Outlet />
|
<Outlet />
|
||||||
|
|
||||||
<TanStackRouterDevtools />
|
<TanStackRouterDevtools />
|
||||||
|
|
|
||||||
|
|
@ -5,5 +5,13 @@ export const Route = createFileRoute('/app/clusters/')({
|
||||||
});
|
});
|
||||||
|
|
||||||
function RouteComponent() {
|
function RouteComponent() {
|
||||||
return <div>Hello "/app/clusters/"!</div>;
|
return (
|
||||||
|
<div>
|
||||||
|
<div>Hello "/app/clusters/"!</div>
|
||||||
|
|
||||||
|
<div className="w-px h-screen bg-red-100/10"></div>
|
||||||
|
|
||||||
|
<div>Hello "/app/clusters/"!</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import { AnimatedOutlet, AnimatedOutletProvider } from '@onelauncher/common/components';
|
import { SheetPage } from '@/components/SheetPage';
|
||||||
|
import { Button } from '@onelauncher/common/components';
|
||||||
import { createFileRoute } from '@tanstack/react-router';
|
import { createFileRoute } from '@tanstack/react-router';
|
||||||
|
|
||||||
export const Route = createFileRoute('/app/clusters')({
|
export const Route = createFileRoute('/app/clusters')({
|
||||||
|
|
@ -7,51 +8,38 @@ export const Route = createFileRoute('/app/clusters')({
|
||||||
|
|
||||||
function RouteComponent() {
|
function RouteComponent() {
|
||||||
return (
|
return (
|
||||||
<div className="flex w-full h-full">
|
<SheetPage
|
||||||
<BackgroundGradients />
|
headerLarge={<HeaderLarge />}
|
||||||
|
headerSmall={<HeaderSmall />}
|
||||||
<AnimatedOutletProvider>
|
/>
|
||||||
<AnimatedOutlet
|
|
||||||
enter={{
|
|
||||||
initial: {
|
|
||||||
bottom: '-100%',
|
|
||||||
opacity: 0,
|
|
||||||
},
|
|
||||||
animate: {
|
|
||||||
bottom: 0,
|
|
||||||
opacity: 1,
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
exit={{
|
|
||||||
initial: {
|
|
||||||
bottom: 0,
|
|
||||||
opacity: 1,
|
|
||||||
},
|
|
||||||
animate: {
|
|
||||||
bottom: '-100%',
|
|
||||||
opacity: 0,
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
from={Route.id}
|
|
||||||
/>
|
|
||||||
</AnimatedOutletProvider>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function BackgroundGradients() {
|
function HeaderLarge({ ref }: { ref?: React.Ref<HTMLDivElement> }) {
|
||||||
return (
|
return (
|
||||||
<div className="relative">
|
<div className="flex flex-row justify-between items-end" ref={ref}>
|
||||||
<div style={{
|
<div className="flex flex-col">
|
||||||
background: 'rgba(0, 0, 0, 0.80)',
|
<h1 className="text-3xl font-semibold">Header</h1>
|
||||||
}}
|
<p className="text-md font-medium text-fg-secondary">Tailored for Hypixel Skyblock.</p>
|
||||||
>
|
|
||||||
</div>
|
|
||||||
<div style={{
|
|
||||||
background: 'linear-gradient(180deg, rgba(17, 23, 28, 0.00) 0%, #11171C 60.1%)',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<Button color="primary" size="large">
|
||||||
|
Launch
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function HeaderSmall() {
|
||||||
|
return (
|
||||||
|
<div className="flex flex-row w-full items-center justify-between">
|
||||||
|
<div className="flex flex-row items-center gap-4">
|
||||||
|
<h1 className="text-2lg h-full font-medium">Header</h1>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Button color="primary">
|
||||||
|
Launch
|
||||||
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,46 +15,36 @@ export const Route = createFileRoute('/app/')({
|
||||||
component: RouteComponent,
|
component: RouteComponent,
|
||||||
});
|
});
|
||||||
|
|
||||||
const clusters = [
|
|
||||||
{
|
|
||||||
mc_version: '1.8.9',
|
|
||||||
},
|
|
||||||
undefined,
|
|
||||||
{
|
|
||||||
mc_version: '1.21.4',
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
function RouteComponent() {
|
function RouteComponent() {
|
||||||
// const { data: clusters } = useCommandSuspense<Array<ClusterModel | undefined>>(
|
const { data: clusters } = useCommandSuspense<Array<ClusterModel | undefined>>(
|
||||||
// 'getClusters',
|
'getClusters',
|
||||||
// bindings.core.getClusters,
|
bindings.core.getClusters,
|
||||||
// {
|
{
|
||||||
// select: ((data: Array<ClusterModel>) => {
|
select: ((data: Array<ClusterModel>) => {
|
||||||
// const sorted = data.sort((a, b) => {
|
const sorted = data.sort((a, b) => {
|
||||||
// if (a.last_played && b.last_played)
|
if (a.last_played && b.last_played)
|
||||||
// return new Date(b.last_played).getTime() - new Date(a.last_played).getTime();
|
return new Date(b.last_played).getTime() - new Date(a.last_played).getTime();
|
||||||
// else if (a.last_played)
|
else if (a.last_played)
|
||||||
// return -1; // a has last_played, b does not
|
return -1; // a has last_played, b does not
|
||||||
// else if (b.last_played)
|
else if (b.last_played)
|
||||||
// return 1; // b has last_played, a does not
|
return 1; // b has last_played, a does not
|
||||||
|
|
||||||
// const aVersion = a.mc_version.replaceAll('.', '');
|
const aVersion = a.mc_version.replaceAll('.', '');
|
||||||
// const bVersion = b.mc_version.replaceAll('.', '');
|
const bVersion = b.mc_version.replaceAll('.', '');
|
||||||
// return Number.parseInt(bVersion) - Number.parseInt(aVersion);
|
return Number.parseInt(bVersion) - Number.parseInt(aVersion);
|
||||||
// });
|
});
|
||||||
|
|
||||||
// return [
|
return [
|
||||||
// sorted[0],
|
sorted[0],
|
||||||
// sorted[1],
|
sorted[1],
|
||||||
// undefined,
|
undefined,
|
||||||
// ];
|
];
|
||||||
// }) as (data: Array<ClusterModel | undefined>) => Array<ClusterModel | undefined>,
|
}) as (data: Array<ClusterModel | undefined>) => Array<ClusterModel | undefined>,
|
||||||
// },
|
},
|
||||||
// );
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex h-full w-full flex-col justify-center">
|
<div className="flex h-full w-full flex-col justify-center p-12">
|
||||||
<motion.div {...animations.slideInLeft} className="flex flex-1 flex-col justify-center items-start gap-2" transition={{ ...transitions.spring, delay: 0.2 }}>
|
<motion.div {...animations.slideInLeft} className="flex flex-1 flex-col justify-center items-start gap-2" transition={{ ...transitions.spring, delay: 0.2 }}>
|
||||||
<h1 className="text-6xl font-bold text-fg-primary">1.8.9</h1>
|
<h1 className="text-6xl font-bold text-fg-primary">1.8.9</h1>
|
||||||
<p className="text-lg font-medium text-fg-secondary">The Bountiful Update</p>
|
<p className="text-lg font-medium text-fg-secondary">The Bountiful Update</p>
|
||||||
|
|
@ -65,9 +55,9 @@ function RouteComponent() {
|
||||||
</div>
|
</div>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
|
|
||||||
<motion.div {...animations.slideInUp} className="flex flex-row transition-[height] max-h-84 h-[28vh] max-xl:h-52 gap-6">
|
<motion.div {...animations.slideInUp} className="flex flex-row transition-[height] h-52 gap-6">
|
||||||
{clusters.map(cluster => (
|
{clusters.map((cluster, index) => (
|
||||||
<RecentsCard key={cluster?.mc_version} version={cluster?.mc_version} />
|
<RecentsCard key={cluster?.folder_name ?? index} version={cluster?.mc_version} />
|
||||||
))}
|
))}
|
||||||
|
|
||||||
<Card blur className="flex flex-col justify-center items-center max-w-24">
|
<Card blur className="flex flex-col justify-center items-center max-w-24">
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,10 @@
|
||||||
|
import { useEffect, type PropsWithChildren } from 'react';
|
||||||
import { LoaderSuspense, Navbar } from '@/components';
|
import { LoaderSuspense, Navbar } from '@/components';
|
||||||
import { GameBackground } from '@/components/GameBackground';
|
import { GameBackground } from '@/components/GameBackground';
|
||||||
|
import useAppShellStore from '@/stores/appShellStore';
|
||||||
import { AnimatedOutlet } from '@onelauncher/common/components';
|
import { AnimatedOutlet } from '@onelauncher/common/components';
|
||||||
import { createFileRoute } from '@tanstack/react-router';
|
import { createFileRoute, useRouter } from '@tanstack/react-router';
|
||||||
|
import { AnimatePresence } from 'motion/react';
|
||||||
|
|
||||||
export const Route = createFileRoute('/app')({
|
export const Route = createFileRoute('/app')({
|
||||||
component: RouteComponent,
|
component: RouteComponent,
|
||||||
|
|
@ -10,12 +13,8 @@ export const Route = createFileRoute('/app')({
|
||||||
function RouteComponent() {
|
function RouteComponent() {
|
||||||
return (
|
return (
|
||||||
<LoaderSuspense spinner={{ size: 'large' }}>
|
<LoaderSuspense spinner={{ size: 'large' }}>
|
||||||
<div className="flex flex-col h-full w-full">
|
<AppShell>
|
||||||
<Navbar />
|
<div className="h-full w-full">
|
||||||
|
|
||||||
<Background />
|
|
||||||
|
|
||||||
<div className="h-full w-full pb-12">
|
|
||||||
<AnimatedOutlet
|
<AnimatedOutlet
|
||||||
enter={{
|
enter={{
|
||||||
initial: { opacity: 0 },
|
initial: { opacity: 0 },
|
||||||
|
|
@ -29,15 +28,49 @@ function RouteComponent() {
|
||||||
transition={{ duration: 0.3, bounce: 0.1, power: 0.2, type: 'spring' }}
|
transition={{ duration: 0.3, bounce: 0.1, power: 0.2, type: 'spring' }}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</AppShell>
|
||||||
</LoaderSuspense>
|
</LoaderSuspense>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function Background() {
|
function AppShell({
|
||||||
|
children,
|
||||||
|
}: PropsWithChildren) {
|
||||||
|
const router = useRouter();
|
||||||
|
const setPrevLocation = useAppShellStore(state => state.setPrevLocation);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const unsub = router.subscribe("onBeforeNavigate", (e) => {
|
||||||
|
setPrevLocation(e.fromLocation ?? null);
|
||||||
|
});
|
||||||
|
|
||||||
|
return () => unsub();
|
||||||
|
}, [router]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col h-full w-full">
|
||||||
|
<AnimatePresence>
|
||||||
|
<BackgroundGradient />
|
||||||
|
</AnimatePresence>
|
||||||
|
|
||||||
|
<Navbar />
|
||||||
|
|
||||||
|
<div className="flex flex-col w-full h-full">
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function BackgroundGradient() {
|
||||||
|
const background = useAppShellStore(state => state.background);
|
||||||
|
|
||||||
|
if (background === 'none')
|
||||||
|
return undefined;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
{/* Linear gradient: left -> right */}
|
{/* Linear black gradient: left -> right */}
|
||||||
<div
|
<div
|
||||||
className="absolute top-0 left-0 w-screen h-screen -z-10"
|
className="absolute top-0 left-0 w-screen h-screen -z-10"
|
||||||
style={{
|
style={{
|
||||||
|
|
@ -46,7 +79,7 @@ function Background() {
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Radial Gradient */}
|
{/* Radial black gradient */}
|
||||||
<div
|
<div
|
||||||
className="absolute top-0 left-0 w-screen h-screen -z-10" style={{
|
className="absolute top-0 left-0 w-screen h-screen -z-10" style={{
|
||||||
background: 'radial-gradient(48.29% 48.29% at 77.29% 50%, rgba(0, 0, 0, 0.00) 0%, rgba(0, 0, 0, 0.64) 100%)',
|
background: 'radial-gradient(48.29% 48.29% at 77.29% 50%, rgba(0, 0, 0, 0.00) 0%, rgba(0, 0, 0, 0.64) 100%)',
|
||||||
|
|
@ -54,7 +87,7 @@ function Background() {
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Linear gradient: bottom -> 200 px up */}
|
{/* Linear black gradient: bottom -> 200 px up */}
|
||||||
<div
|
<div
|
||||||
className="absolute bottom-0 left-0 w-screen h-50 -z-10" style={{
|
className="absolute bottom-0 left-0 w-screen h-50 -z-10" style={{
|
||||||
background: 'linear-gradient(180deg, rgba(17, 23, 28, 0.00) 0%, rgba(0, 0, 0, 0.68) 60%)',
|
background: 'linear-gradient(180deg, rgba(17, 23, 28, 0.00) 0%, rgba(0, 0, 0, 0.68) 60%)',
|
||||||
|
|
|
||||||
22
apps/oneclient/frontend/src/stores/appShellStore.ts
Normal file
22
apps/oneclient/frontend/src/stores/appShellStore.ts
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
import type { ParsedLocation } from '@tanstack/react-router';
|
||||||
|
import { create } from 'zustand';
|
||||||
|
|
||||||
|
export interface AppShellStore {
|
||||||
|
background: 'gradientOverlay' | 'none';
|
||||||
|
prevLocation: ParsedLocation | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AppShellStoreActions {
|
||||||
|
setBackground: (background: AppShellStore['background']) => void;
|
||||||
|
setPrevLocation: (location: ParsedLocation | null) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useAppShellStore = create<AppShellStore & AppShellStoreActions>()(set => ({
|
||||||
|
background: 'gradientOverlay',
|
||||||
|
prevLocation: null,
|
||||||
|
|
||||||
|
setBackground: background => set({ background }),
|
||||||
|
setPrevLocation: location => set({ prevLocation: location }),
|
||||||
|
}));
|
||||||
|
|
||||||
|
export default useAppShellStore;
|
||||||
|
|
@ -1,28 +0,0 @@
|
||||||
import type { ClusterModel } from '@/bindings.gen';
|
|
||||||
import type { FileRoutesByFullPath } from '@/routeTree.gen';
|
|
||||||
import { create } from 'zustand';
|
|
||||||
|
|
||||||
interface AppStore {
|
|
||||||
activeCluster: ClusterModel | null;
|
|
||||||
previousPage: FileRoutesByFullPath | null;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface AppStoreActions {
|
|
||||||
setInitialData: (data: AppStore) => void;
|
|
||||||
setActiveCluster: (cluster: ClusterModel | null) => void;
|
|
||||||
setPreviousPage: (page: FileRoutesByFullPath) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
const useAppStore = create<AppStore & AppStoreActions>()(set => ({
|
|
||||||
activeCluster: null,
|
|
||||||
previousPage: null,
|
|
||||||
|
|
||||||
setInitialData: (data: AppStore) => set({
|
|
||||||
activeCluster: data.activeCluster,
|
|
||||||
previousPage: data.previousPage,
|
|
||||||
}),
|
|
||||||
setPreviousPage: (page: FileRoutesByFullPath) => set({ previousPage: page }),
|
|
||||||
setActiveCluster: (cluster: ClusterModel | null) => set({ activeCluster: cluster }),
|
|
||||||
}));
|
|
||||||
|
|
||||||
export default useAppStore;
|
|
||||||
39
apps/oneclient/frontend/src/utils/locationMapping.ts
Normal file
39
apps/oneclient/frontend/src/utils/locationMapping.ts
Normal file
|
|
@ -0,0 +1,39 @@
|
||||||
|
import type { FileRouteTypes } from '@/routeTree.gen';
|
||||||
|
|
||||||
|
const mapping: Partial<Record<FileRouteTypes['to'], string>> = {
|
||||||
|
'/app': 'Home',
|
||||||
|
'/app/clusters': 'Clusters',
|
||||||
|
};
|
||||||
|
|
||||||
|
export function getLocationName(
|
||||||
|
to: FileRouteTypes['to'] | (string & {}) | undefined | null,
|
||||||
|
): string | undefined {
|
||||||
|
if (!to)
|
||||||
|
return undefined;
|
||||||
|
|
||||||
|
if (to in mapping)
|
||||||
|
return mapping[to as keyof typeof mapping];
|
||||||
|
|
||||||
|
const mostSimilarPath = getMostSimilarPathMapping(to);
|
||||||
|
return mostSimilarPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getMostSimilarPathMapping(
|
||||||
|
to: string,
|
||||||
|
): FileRouteTypes['to'] | undefined {
|
||||||
|
let path = to;
|
||||||
|
|
||||||
|
while (path !== '') {
|
||||||
|
if (Object.prototype.hasOwnProperty.call(mapping, path))
|
||||||
|
return path as FileRouteTypes['to'];
|
||||||
|
|
||||||
|
const lastSlash = path.lastIndexOf('/');
|
||||||
|
|
||||||
|
if (lastSlash === 0)
|
||||||
|
return Object.prototype.hasOwnProperty.call(mapping, '/') ? '/' : undefined;
|
||||||
|
|
||||||
|
path = path.substring(0, lastSlash);
|
||||||
|
}
|
||||||
|
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
@ -31,6 +31,7 @@ export default defineConfig(
|
||||||
'ts/no-use-before-define': 'off',
|
'ts/no-use-before-define': 'off',
|
||||||
'style/function-paren-newline': ['error', 'consistent'],
|
'style/function-paren-newline': ['error', 'consistent'],
|
||||||
'react/no-context-provider': 'off',
|
'react/no-context-provider': 'off',
|
||||||
|
"prefer-const": "off" // disabled due to maximum call stack size
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@
|
||||||
import type { AnyRoute } from '@tanstack/react-router';
|
import type { AnyRoute } from '@tanstack/react-router';
|
||||||
import type { MotionProps } from 'motion/react';
|
import type { MotionProps } from 'motion/react';
|
||||||
import { Outlet, useMatch, useRouter } from '@tanstack/react-router';
|
import { Outlet, useMatch, useRouter } from '@tanstack/react-router';
|
||||||
import { AnimatePresence, motion } from 'motion/react';
|
import { motion } from 'motion/react';
|
||||||
import { createContext, useContext, useEffect, useRef, useState } from 'react';
|
import { createContext, useContext, useEffect, useRef, useState } from 'react';
|
||||||
|
|
||||||
interface TransitionProps {
|
interface TransitionProps {
|
||||||
|
|
@ -21,6 +21,7 @@ interface AnimatedOutletProps {
|
||||||
transition?: MotionProps['transition'];
|
transition?: MotionProps['transition'];
|
||||||
from: AnyRoute['id'];
|
from: AnyRoute['id'];
|
||||||
clone?: boolean;
|
clone?: boolean;
|
||||||
|
children?: (children: React.ReactNode) => React.ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface AnimatedOutletWrapperProps {
|
interface AnimatedOutletWrapperProps {
|
||||||
|
|
@ -82,6 +83,7 @@ export function AnimatedOutlet({
|
||||||
transition = { duration: 0.3 },
|
transition = { duration: 0.3 },
|
||||||
from,
|
from,
|
||||||
clone = true,
|
clone = true,
|
||||||
|
children,
|
||||||
}: AnimatedOutletProps) {
|
}: AnimatedOutletProps) {
|
||||||
const [snapshots, setSnapshots] = useState<Array<{ node: HTMLElement; id: number }>>([]);
|
const [snapshots, setSnapshots] = useState<Array<{ node: HTMLElement; id: number }>>([]);
|
||||||
const [pathname, setPathname] = useState<string | null>(null);
|
const [pathname, setPathname] = useState<string | null>(null);
|
||||||
|
|
@ -148,7 +150,7 @@ export function AnimatedOutlet({
|
||||||
transition={transition}
|
transition={transition}
|
||||||
>
|
>
|
||||||
<div className="w-full h-full">
|
<div className="w-full h-full">
|
||||||
<Outlet />
|
{children?.(<Outlet />) ?? <Outlet />}
|
||||||
</div>
|
</div>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue