Merge branch 'oneclient/main' of https://github.com/Polyfrost/OneLauncher into refactor/frontend
This commit is contained in:
commit
816be606ff
29 changed files with 570 additions and 70 deletions
|
|
@ -20,6 +20,12 @@ pub struct SingleInstancePayload {
|
|||
|
||||
#[tracing::instrument]
|
||||
async fn initialize_core() -> LauncherResult<()> {
|
||||
let level = if cfg!(debug_assertions) {
|
||||
tracing::Level::DEBUG
|
||||
} else {
|
||||
tracing::Level::INFO
|
||||
};
|
||||
|
||||
let opts = CoreOptions {
|
||||
curseforge_api_key: Some(constants::CURSEFORGE_API_KEY.to_string()),
|
||||
launcher_name: "OneClient".to_string(),
|
||||
|
|
@ -27,6 +33,9 @@ async fn initialize_core() -> LauncherResult<()> {
|
|||
launcher_website: "https://polyfrost.org/".to_string(),
|
||||
discord_client_id: None, //Some(constants::DISCORD_CLIENT_ID.to_string()), // disabled for now
|
||||
fetch_attempts: 3,
|
||||
logger_filter: Some(
|
||||
format!("{}={level},onelauncher_core={level}", env!("CARGO_PKG_NAME")),
|
||||
),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import type { CSSProperties } from 'react';
|
||||
import { twMerge } from 'tailwind-merge';
|
||||
import HypixelSkyblockHub from '../assets/backgrounds/HypixelSkyblockHub.png';
|
||||
|
||||
|
|
@ -5,20 +6,21 @@ const GameBackgrounds = {
|
|||
HypixelSkyblockHub,
|
||||
};
|
||||
|
||||
type GameBackgroundName = keyof typeof GameBackgrounds;
|
||||
export type GameBackgroundName = keyof typeof GameBackgrounds;
|
||||
|
||||
interface GameBackgroundProps {
|
||||
name: GameBackgroundName;
|
||||
className?: string;
|
||||
style?: CSSProperties;
|
||||
}
|
||||
|
||||
export function GameBackground({ name, className = '' }: GameBackgroundProps) {
|
||||
export function GameBackground({ name, className = '', style = undefined }: GameBackgroundProps) {
|
||||
const BackgroundImage = GameBackgrounds[name];
|
||||
|
||||
return (
|
||||
<div
|
||||
className={twMerge(`absolute -z-50 pointer-events-none inset-0 bg-cover bg-center`, className)}
|
||||
style={{ backgroundImage: `url(${BackgroundImage})` }}
|
||||
style={{ backgroundImage: `url(${BackgroundImage})`, ...style }}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
68
apps/oneclient/frontend/src/components/Loader.tsx
Normal file
68
apps/oneclient/frontend/src/components/Loader.tsx
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
import type { HTMLAttributes, ReactNode } from 'react';
|
||||
import type { SpinnerProps } from './Spinner';
|
||||
import { motion } from 'motion/react';
|
||||
import { Suspense } from 'react';
|
||||
import { twMerge } from 'tailwind-merge';
|
||||
import { Spinner } from './Spinner';
|
||||
|
||||
interface LoaderProps extends HTMLAttributes<HTMLDivElement> {
|
||||
children?: ReactNode;
|
||||
spinner?: SpinnerProps;
|
||||
}
|
||||
|
||||
export function LoaderContainer({
|
||||
loading = false,
|
||||
children,
|
||||
spinner,
|
||||
className,
|
||||
}: LoaderProps & {
|
||||
loading?: boolean;
|
||||
}) {
|
||||
return loading ? <Fallback className={className} spinner={spinner} /> : <LoadedChildren className={className}>{children}</LoadedChildren>;
|
||||
}
|
||||
|
||||
export function LoaderSuspense({
|
||||
children,
|
||||
spinner,
|
||||
className,
|
||||
}: LoaderProps) {
|
||||
return (
|
||||
<Suspense fallback={<Fallback className={className} spinner={spinner} />}>
|
||||
<LoadedChildren className={className}>
|
||||
{children}
|
||||
</LoadedChildren>
|
||||
</Suspense>
|
||||
);
|
||||
}
|
||||
|
||||
function Fallback({
|
||||
spinner,
|
||||
className,
|
||||
}: {
|
||||
spinner?: SpinnerProps;
|
||||
className?: string;
|
||||
}) {
|
||||
return (
|
||||
<div className={twMerge('w-full h-full flex justify-center items-center', className)}>
|
||||
<Spinner {...spinner} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function LoadedChildren({
|
||||
children,
|
||||
className,
|
||||
}: {
|
||||
children: ReactNode;
|
||||
className?: string;
|
||||
}) {
|
||||
return (
|
||||
<motion.div
|
||||
animate={{ opacity: 1 }}
|
||||
className={twMerge('w-full h-full', className)}
|
||||
initial={{ opacity: 0 }}
|
||||
>
|
||||
{children}
|
||||
</motion.div>
|
||||
);
|
||||
}
|
||||
|
|
@ -20,11 +20,11 @@ export function Navbar() {
|
|||
|
||||
<div className="flex flex-1 items-center justify-center pointer-events-none gap-6">
|
||||
<NavbarLink to="/app">Home</NavbarLink>
|
||||
<NavbarLink to="/app/test">Clusters</NavbarLink>
|
||||
<NavbarLink to="/app/clusters">Clusters</NavbarLink>
|
||||
<NavbarLink to="/">Accounts</NavbarLink>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-1 items-center justify-end gap-2">
|
||||
<div className="flex flex-1 items-center justify-end gap-2 pointer-events-none">
|
||||
<NavbarButton
|
||||
children={<MinusIcon />}
|
||||
onClick={onMinimize}
|
||||
|
|
|
|||
32
apps/oneclient/frontend/src/components/Spinner.tsx
Normal file
32
apps/oneclient/frontend/src/components/Spinner.tsx
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
import type { HTMLAttributes } from 'react';
|
||||
import type { VariantProps } from 'tailwind-variants';
|
||||
import { tv } from 'tailwind-variants';
|
||||
|
||||
const spinnerVariants = tv({
|
||||
base: 'inline-block relative opacity-0 animate-delay-1000 animate-fade animate-fill-forwards',
|
||||
variants: {
|
||||
size: {
|
||||
small: 'w-8 h-8',
|
||||
medium: 'w-12 h-12',
|
||||
large: 'w-16 h-16',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
type SpinnerVariantsProps = VariantProps<typeof spinnerVariants>;
|
||||
|
||||
export interface SpinnerProps extends HTMLAttributes<HTMLDivElement> {
|
||||
size?: SpinnerVariantsProps['size'];
|
||||
};
|
||||
|
||||
export function Spinner({
|
||||
size = 'medium',
|
||||
className,
|
||||
}: SpinnerProps) {
|
||||
return (
|
||||
<div className={spinnerVariants({ size, className })}>
|
||||
<div className="absolute w-full h-full rounded-full border-2 border-fg-primary animate-fade-scale"></div>
|
||||
<div className="absolute w-full h-full rounded-full border-2 border-fg-primary animate-fade-scale animate-delay-500"></div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,2 +1,4 @@
|
|||
export * from './GameBackground';
|
||||
export * from './Loader';
|
||||
export * from './Navbar';
|
||||
export * from './Spinner';
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
import type { QueryClient } from '@tanstack/react-query';
|
||||
import type { NavigateOptions, ToOptions } from '@tanstack/react-router';
|
||||
import { Navbar } from '@/components';
|
||||
import { AnimatedOutletProvider } from '@onelauncher/common/components';
|
||||
import { createRootRouteWithContext, Outlet, useRouter } from '@tanstack/react-router';
|
||||
import { TanStackRouterDevtools } from '@tanstack/react-router-devtools';
|
||||
|
|
@ -30,8 +29,6 @@ function RootRoute() {
|
|||
useHref={to => router.buildLocation({ to }).href}
|
||||
>
|
||||
<div className="h-screen flex flex-col overflow-hidden text-fg-primary px-12">
|
||||
<Navbar />
|
||||
|
||||
<Outlet />
|
||||
|
||||
<TanStackRouterDevtools />
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
import { createFileRoute } from '@tanstack/react-router';
|
||||
|
||||
export const Route = createFileRoute('/app/test')({
|
||||
export const Route = createFileRoute('/app/clusters/')({
|
||||
component: RouteComponent,
|
||||
});
|
||||
|
||||
function RouteComponent() {
|
||||
return <div>Hello "/app/test"!</div>;
|
||||
return <div>Hello "/app/clusters/"!</div>;
|
||||
}
|
||||
57
apps/oneclient/frontend/src/routes/app/clusters/route.tsx
Normal file
57
apps/oneclient/frontend/src/routes/app/clusters/route.tsx
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
import { AnimatedOutlet, AnimatedOutletProvider } from '@onelauncher/common/components';
|
||||
import { createFileRoute } from '@tanstack/react-router';
|
||||
|
||||
export const Route = createFileRoute('/app/clusters')({
|
||||
component: RouteComponent,
|
||||
});
|
||||
|
||||
function RouteComponent() {
|
||||
return (
|
||||
<div className="flex w-full h-full">
|
||||
<BackgroundGradients />
|
||||
|
||||
<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() {
|
||||
return (
|
||||
<div className="relative">
|
||||
<div style={{
|
||||
background: 'rgba(0, 0, 0, 0.80)',
|
||||
}}
|
||||
>
|
||||
</div>
|
||||
<div style={{
|
||||
background: 'linear-gradient(180deg, rgba(17, 23, 28, 0.00) 0%, #11171C 60.1%)',
|
||||
}}
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,21 +1,140 @@
|
|||
import type { ClusterModel } from '@/bindings.gen';
|
||||
import type { HTMLAttributes } from 'react';
|
||||
import { GameBackground } from '@/components';
|
||||
import { bindings } from '@/main';
|
||||
import { animations, transitions } from '@/utils/motion';
|
||||
import { getVersionInfo } from '@/utils/versionMap';
|
||||
import { useCommandSuspense } from '@onelauncher/common';
|
||||
import { Button } from '@onelauncher/common/components';
|
||||
import { createFileRoute } from '@tanstack/react-router';
|
||||
import { DotsGridIcon, Settings04Icon } from '@untitled-theme/icons-react';
|
||||
import { motion } from 'motion/react';
|
||||
import { twMerge } from 'tailwind-merge';
|
||||
|
||||
export const Route = createFileRoute('/app/')({
|
||||
component: RouteComponent,
|
||||
});
|
||||
|
||||
const clusters = [
|
||||
{
|
||||
mc_version: '1.8.9',
|
||||
},
|
||||
undefined,
|
||||
{
|
||||
mc_version: '1.21.4',
|
||||
},
|
||||
];
|
||||
|
||||
function RouteComponent() {
|
||||
// const { data: clusters } = useCommandSuspense<Array<ClusterModel | undefined>>(
|
||||
// 'getClusters',
|
||||
// bindings.core.getClusters,
|
||||
// {
|
||||
// select: ((data: Array<ClusterModel>) => {
|
||||
// const sorted = 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);
|
||||
// });
|
||||
|
||||
// return [
|
||||
// sorted[0],
|
||||
// sorted[1],
|
||||
// undefined,
|
||||
// ];
|
||||
// }) as (data: Array<ClusterModel | undefined>) => Array<ClusterModel | undefined>,
|
||||
// },
|
||||
// );
|
||||
|
||||
return (
|
||||
<div className="flex h-full w-full flex-col justify-center">
|
||||
<div className="flex flex-1 flex-col justify-center">
|
||||
<h1 className="text-5xl font-semibold text-fg-primary">1.8.9</h1>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-row h-56">
|
||||
<div className="flex-1 rounded-xl border border-solid border-component-border bg-page-overlay">
|
||||
<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>
|
||||
<p className="text-lg font-medium text-fg-secondary">The Bountiful Update</p>
|
||||
|
||||
<div className="flex flex-row justify-center items-center gap-2">
|
||||
<Button size="large">Launch</Button>
|
||||
<Button color="ghost" size="iconLarge"><Settings04Icon /></Button>
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
|
||||
<motion.div {...animations.slideInUp} className="flex flex-row transition-[height] max-h-84 h-[28vh] max-xl:h-52 gap-6">
|
||||
{clusters.map(cluster => (
|
||||
<RecentsCard key={cluster?.mc_version} version={cluster?.mc_version} />
|
||||
))}
|
||||
|
||||
<Card blur className="flex flex-col justify-center items-center max-w-24">
|
||||
<DotsGridIcon height={48} width={48} />
|
||||
</Card>
|
||||
</motion.div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
interface RecentsCardProps {
|
||||
version: string | undefined;
|
||||
}
|
||||
|
||||
const BLUR = '30px';
|
||||
function RecentsCard({ version }: RecentsCardProps) {
|
||||
const versionInfo = getVersionInfo(version);
|
||||
|
||||
return (
|
||||
<Card blur={versionInfo === undefined}>
|
||||
{versionInfo && (
|
||||
<div className="flex w-full h-full justify-start items-end px-6 py-3">
|
||||
<GameBackground className="-z-10" name={versionInfo.backgroundName} />
|
||||
|
||||
<div
|
||||
className="absolute top-0 left-0 -z-10 w-full h-full"
|
||||
style={{
|
||||
background: 'linear-gradient(180deg, rgba(25, 25, 25, 0.00) 24.52%, rgba(17, 17, 21, 0.75) 65%)',
|
||||
}}
|
||||
>
|
||||
</div>
|
||||
|
||||
<h4 className="text-3xl font-semibold">{version}</h4>
|
||||
</div>
|
||||
)}
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
function Card({
|
||||
blur = true,
|
||||
children,
|
||||
className,
|
||||
style,
|
||||
}: {
|
||||
blur?: boolean;
|
||||
children?: React.ReactNode;
|
||||
} & HTMLAttributes<HTMLDivElement>) {
|
||||
return (
|
||||
<div
|
||||
className={twMerge(
|
||||
'relative overflow-hidden flex-1 rounded-xl inset-ring-2 inset-ring-component-border',
|
||||
blur
|
||||
? 'bg-white/5 hover:bg-white/15 active:bg-white/20'
|
||||
: 'hover:bg-ghost-overlay-hover active:bg-ghost-overlay-pressed',
|
||||
className,
|
||||
)}
|
||||
style={blur
|
||||
? {
|
||||
// shitty hack because webkit breaks with css variables in its backdrop filter
|
||||
backdropFilter: `blur(${BLUR})`,
|
||||
WebkitBackdropFilter: `blur(${BLUR})`,
|
||||
...style,
|
||||
}
|
||||
: style}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import { LoaderSuspense, Navbar } from '@/components';
|
||||
import { GameBackground } from '@/components/GameBackground';
|
||||
import { AnimatedOutlet } from '@onelauncher/common/components';
|
||||
import { createFileRoute } from '@tanstack/react-router';
|
||||
|
|
@ -8,24 +9,28 @@ export const Route = createFileRoute('/app')({
|
|||
|
||||
function RouteComponent() {
|
||||
return (
|
||||
<div className="h-full w-full">
|
||||
<Background />
|
||||
<LoaderSuspense spinner={{ size: 'large' }}>
|
||||
<div className="flex flex-col h-full w-full">
|
||||
<Navbar />
|
||||
|
||||
<div className="h-full w-full">
|
||||
<AnimatedOutlet
|
||||
enter={{
|
||||
initial: { opacity: 0, transform: 'translateX(-20%)' },
|
||||
animate: { opacity: 1, transform: 'translateX(0)' },
|
||||
}}
|
||||
exit={{
|
||||
initial: { opacity: 1, transform: 'translateX(0)' },
|
||||
animate: { opacity: 0, transform: 'translateX(-20%)' },
|
||||
}}
|
||||
from={Route.id}
|
||||
transition={{ duration: 0.3, bounce: 0.1, power: 0.2, type: 'spring' }}
|
||||
/>
|
||||
<Background />
|
||||
|
||||
<div className="h-full w-full pb-12">
|
||||
<AnimatedOutlet
|
||||
enter={{
|
||||
initial: { opacity: 0 },
|
||||
animate: { opacity: 1 },
|
||||
}}
|
||||
exit={{
|
||||
initial: { opacity: 1 },
|
||||
animate: { opacity: 0 },
|
||||
}}
|
||||
from={Route.id}
|
||||
transition={{ duration: 0.3, bounce: 0.1, power: 0.2, type: 'spring' }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</LoaderSuspense>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,17 +2,27 @@ import type { ClusterModel } from '@/bindings.gen';
|
|||
import type { FileRoutesByFullPath } from '@/routeTree.gen';
|
||||
import { create } from 'zustand';
|
||||
|
||||
interface AppFrameStore {
|
||||
previousPage: FileRoutesByFullPath | null;
|
||||
interface AppStore {
|
||||
activeCluster: ClusterModel | null;
|
||||
previousPage: FileRoutesByFullPath | null;
|
||||
}
|
||||
|
||||
const useAppFrameStore = create<AppFrameStore>()(set => ({
|
||||
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 useAppFrameStore;
|
||||
export default useAppStore;
|
||||
18
apps/oneclient/frontend/src/styles/animations.css
Normal file
18
apps/oneclient/frontend/src/styles/animations.css
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
@layer utilities {
|
||||
@keyframes custom-anim-fade-scale {
|
||||
0% {
|
||||
transform: scale(0);
|
||||
opacity: 1;
|
||||
}
|
||||
100% {
|
||||
transform: scale(1);
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@utility animate-fade-scale {
|
||||
transform: scale(0);
|
||||
opacity: 1;
|
||||
animation: custom-anim-fade-scale var(--default-animation-duration, 1s) var(--default-animation-timing-function, cubic-bezier(0, 0, 0.2, 1)) var(--default-animation-delay, 0s) forwards;
|
||||
}
|
||||
|
|
@ -2,6 +2,7 @@
|
|||
@plugin "tailwindcss-react-aria-components";
|
||||
@plugin "tailwindcss-animated";
|
||||
@import "../../node_modules/@onelauncher/common/dist/theme.css";
|
||||
@import "./animations.css";
|
||||
|
||||
@utility partial-underline-* {
|
||||
position: relative;
|
||||
|
|
@ -18,3 +19,16 @@
|
|||
border-radius: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
@theme {
|
||||
/** border */
|
||||
--color-component-border: #ffffff1A;
|
||||
|
||||
/** rounding */
|
||||
--radius-sm: 0.25rem;
|
||||
--radius-md: 0.438rem;
|
||||
--radius-lg: 0.625rem;
|
||||
--radius-xl: 1rem;
|
||||
--radius-2xl: 1.5rem;
|
||||
--radius-3xl: 2rem;
|
||||
}
|
||||
|
|
|
|||
9
apps/oneclient/frontend/src/types/global.d.ts
vendored
Normal file
9
apps/oneclient/frontend/src/types/global.d.ts
vendored
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
import type { bindings } from '@/main';
|
||||
|
||||
export type BindingCommands = (keyof typeof bindings.core) | (keyof typeof bindings.oneclient);
|
||||
|
||||
declare module '@onelauncher/common' {
|
||||
interface Register {
|
||||
commands: Array<BindingCommands>;
|
||||
}
|
||||
}
|
||||
44
apps/oneclient/frontend/src/utils/motion.ts
Normal file
44
apps/oneclient/frontend/src/utils/motion.ts
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
import type { MotionProps } from 'motion/react';
|
||||
|
||||
export const transitions = {
|
||||
spring: {
|
||||
duration: 0.3,
|
||||
bounce: 0.1,
|
||||
power: 0.2,
|
||||
type: 'spring',
|
||||
},
|
||||
} as const satisfies Record<string, MotionProps['transition']>;
|
||||
|
||||
export const animations = {
|
||||
slideInRight: {
|
||||
initial: {
|
||||
right: '-100%',
|
||||
},
|
||||
animate: {
|
||||
right: '0',
|
||||
},
|
||||
transition: transitions.spring,
|
||||
},
|
||||
|
||||
slideInLeft: {
|
||||
initial: {
|
||||
position: 'relative',
|
||||
left: '-100%',
|
||||
},
|
||||
animate: {
|
||||
position: 'relative',
|
||||
left: '0',
|
||||
},
|
||||
},
|
||||
|
||||
slideInUp: {
|
||||
initial: {
|
||||
position: 'relative',
|
||||
bottom: '-100%',
|
||||
},
|
||||
animate: {
|
||||
position: 'relative',
|
||||
bottom: '0',
|
||||
},
|
||||
},
|
||||
} as const satisfies Record<string, MotionProps>;
|
||||
40
apps/oneclient/frontend/src/utils/versionMap.ts
Normal file
40
apps/oneclient/frontend/src/utils/versionMap.ts
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
import type { GameBackgroundName } from '@/components';
|
||||
|
||||
export interface VersionInfo {
|
||||
backgroundName: GameBackgroundName;
|
||||
description: string;
|
||||
}
|
||||
|
||||
const VERSION_MAP: Record<number, VersionInfo> = {
|
||||
[-1]: {
|
||||
backgroundName: 'HypixelSkyblockHub',
|
||||
description: 'Unknown Version',
|
||||
},
|
||||
8: {
|
||||
backgroundName: 'HypixelSkyblockHub',
|
||||
description: 'The Bountiful Update',
|
||||
},
|
||||
} as const;
|
||||
|
||||
export function getVersionInfo(version: number | string | null | undefined): VersionInfo | undefined {
|
||||
if (version === undefined || version === null)
|
||||
return undefined;
|
||||
|
||||
let majorVersion;
|
||||
|
||||
if (typeof version === 'string') {
|
||||
const parts = version.split('.');
|
||||
if (parts.length >= 2)
|
||||
majorVersion = Number.parseInt(parts[1], 10);
|
||||
}
|
||||
|
||||
if (majorVersion)
|
||||
return VERSION_MAP[majorVersion];
|
||||
else
|
||||
return undefined;
|
||||
}
|
||||
|
||||
export function getVersionInfoOrDefault(version: number | string): VersionInfo {
|
||||
const info = getVersionInfo(version);
|
||||
return info ?? VERSION_MAP[-1];
|
||||
}
|
||||
|
|
@ -20,6 +20,12 @@ pub struct SingleInstancePayload {
|
|||
|
||||
#[tracing::instrument]
|
||||
async fn initialize_core() -> LauncherResult<()> {
|
||||
let level = if cfg!(debug_assertions) {
|
||||
tracing::Level::DEBUG
|
||||
} else {
|
||||
tracing::Level::INFO
|
||||
};
|
||||
|
||||
let opts = CoreOptions {
|
||||
curseforge_api_key: Some(constants::CURSEFORGE_API_KEY.to_string()),
|
||||
launcher_name: "OneLauncher".to_string(),
|
||||
|
|
@ -27,6 +33,9 @@ async fn initialize_core() -> LauncherResult<()> {
|
|||
launcher_website: "https://polyfrost.org/".to_string(),
|
||||
discord_client_id: Some(constants::DISCORD_CLIENT_ID.to_string()),
|
||||
fetch_attempts: 3,
|
||||
logger_filter: Some(
|
||||
format!("{}={level},onelauncher_core={level}", env!("CARGO_PKG_NAME")),
|
||||
),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -2,3 +2,14 @@
|
|||
@plugin "tailwindcss-react-aria-components";
|
||||
@plugin "tailwindcss-animated";
|
||||
@import "../../node_modules/@onelauncher/common/dist/theme.css";
|
||||
|
||||
@theme {
|
||||
--radius-xs: 0.125rem;
|
||||
--radius-sm: 0.25rem;
|
||||
--radius-md: 0.375rem;
|
||||
--radius-lg: 0.5rem;
|
||||
--radius-xl: 0.75rem;
|
||||
--radius-2xl: 1rem;
|
||||
--radius-3xl: 1.5rem;
|
||||
--radius-4xl: 2rem;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,6 +12,9 @@ pub struct ProxyTauri {
|
|||
impl ProxyTauri {
|
||||
#[must_use]
|
||||
pub fn new(handle: AppHandle) -> Self {
|
||||
tracing::debug!("using tauri bridge");
|
||||
tracing::debug!("webview version: {}", tauri::webview_version().unwrap_or("unknown".into()));
|
||||
|
||||
Self {
|
||||
emitter: LauncherEventEmitter::new(handle.clone()),
|
||||
handle,
|
||||
|
|
|
|||
|
|
@ -143,8 +143,8 @@ impl TauriLauncherApi for TauriLauncherApiImpl {
|
|||
.await
|
||||
.map_err(|e| LauncherError::from(e))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
// Ok(())
|
||||
// }
|
||||
|
||||
|
||||
// Setting Profiles
|
||||
|
|
|
|||
|
|
@ -6,7 +6,15 @@ use crate::store::Core;
|
|||
#[cfg(debug_assertions)]
|
||||
pub async fn start_logger() {
|
||||
let filter = tracing_subscriber::EnvFilter::try_from_default_env()
|
||||
.unwrap_or_else(|_| tracing_subscriber::EnvFilter::new(format!("{}=info", env!("CARGO_PKG_NAME"))));
|
||||
.unwrap_or_else(|_| {
|
||||
tracing_subscriber::EnvFilter::new(
|
||||
Core::get().logger_filter
|
||||
.as_ref()
|
||||
.unwrap_or(
|
||||
&format!("{}=info", env!("CARGO_PKG_NAME"))
|
||||
)
|
||||
)
|
||||
});
|
||||
|
||||
let mut fmt_layer = tracing_subscriber::fmt::layer()
|
||||
.with_ansi(true)
|
||||
|
|
@ -16,7 +24,7 @@ pub async fn start_logger() {
|
|||
.with_thread_names(true)
|
||||
.pretty();
|
||||
|
||||
if let Some(span) = Core::get().span_formatting.clone() {
|
||||
if let Some(span) = Core::get().logger_span_formatting.clone() {
|
||||
fmt_layer = fmt_layer.with_span_events(span);
|
||||
}
|
||||
|
||||
|
|
@ -62,7 +70,15 @@ pub async fn start_logger() {
|
|||
};
|
||||
|
||||
let filter = tracing_subscriber::EnvFilter::try_from_default_env()
|
||||
.unwrap_or_else(|_| tracing_subscriber::EnvFilter::new(format!("{}=info", env!("CARGO_PKG_NAME"))));
|
||||
.unwrap_or_else(|_| {
|
||||
tracing_subscriber::EnvFilter::new(
|
||||
Core::get().logger_filter
|
||||
.as_ref()
|
||||
.unwrap_or(
|
||||
&format!("{}=info", env!("CARGO_PKG_NAME"))
|
||||
)
|
||||
)
|
||||
});
|
||||
|
||||
let mut fmt_layer = tracing_subscriber::fmt::layer()
|
||||
.compact()
|
||||
|
|
|
|||
|
|
@ -16,11 +16,12 @@ pub struct CoreOptions {
|
|||
pub launcher_version: String,
|
||||
pub launcher_website: String,
|
||||
pub fetch_attempts: usize,
|
||||
pub span_formatting: Option<FmtSpan>,
|
||||
/// Default client id is the same as the one used by the official launcher
|
||||
pub msa_client_id: String,
|
||||
pub msa_redirect_uri: String,
|
||||
pub curseforge_api_key: Option<String>,
|
||||
pub logger_span_formatting: Option<FmtSpan>,
|
||||
pub logger_filter: Option<String>,
|
||||
}
|
||||
|
||||
impl Default for CoreOptions {
|
||||
|
|
@ -31,10 +32,11 @@ impl Default for CoreOptions {
|
|||
launcher_version: String::from(env!("CARGO_PKG_VERSION")),
|
||||
launcher_website: String::from("https://polyfrost.org/"),
|
||||
fetch_attempts: 3,
|
||||
span_formatting: None,
|
||||
msa_client_id: String::from("00000000402b5328"),
|
||||
msa_redirect_uri: String::from("https://login.live.com/oauth20_desktop.srf"),
|
||||
curseforge_api_key: None,
|
||||
logger_span_formatting: None,
|
||||
logger_filter: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@
|
|||
"peerDependencies": {
|
||||
"@tanstack/react-query": "^5.66.5",
|
||||
"@tanstack/react-router": "^1.114.3",
|
||||
"@untitled-theme/icons-react": "^0.14.0",
|
||||
"motion": "^12.10.5",
|
||||
"react": "^19.0.0",
|
||||
"react-aria-components": "^1.8.0",
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
import type { AnyRoute } from '@tanstack/react-router';
|
||||
import type { MotionProps } from 'motion/react';
|
||||
import { Outlet, useMatch, useRouter } from '@tanstack/react-router';
|
||||
import { motion } from 'motion/react';
|
||||
import { AnimatePresence, motion } from 'motion/react';
|
||||
import { createContext, useContext, useEffect, useRef, useState } from 'react';
|
||||
|
||||
interface TransitionProps {
|
||||
|
|
|
|||
|
|
@ -29,8 +29,9 @@ export const buttonVariants = tv({
|
|||
},
|
||||
size: {
|
||||
normal: 'py-1.5 px-3',
|
||||
large: 'py-2 px-6 text-lg',
|
||||
icon: 'p-1.5 aspect-square box-border w-8 h-8',
|
||||
iconLarge: 'w-9 h-9 [&>*]:p-0.5',
|
||||
iconLarge: 'w-10 h-10 [&>*]:p-0.5',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import type { UndefinedInitialDataOptions, UseQueryResult } from '@tanstack/react-query';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
/* eslint-disable query/exhaustive-deps -- False */
|
||||
import type { UndefinedInitialDataOptions, UseMutationOptions, UseMutationResult, UseQueryResult, UseSuspenseQueryOptions, UseSuspenseQueryResult } from '@tanstack/react-query';
|
||||
import { useMutation, useQuery, useSuspenseQuery } from '@tanstack/react-query';
|
||||
|
||||
// this gets overwritten by the consumer project
|
||||
export interface Register {
|
||||
|
|
@ -10,24 +11,52 @@ type RegisterConfig = Register extends {
|
|||
commands: infer Keys;
|
||||
} ? Keys : never;
|
||||
|
||||
export function useCommand<T>(cacheKey: RegisterConfig[number] | (string & {}) | false, command: () => Promise<T>, options?: Omit<UndefinedInitialDataOptions<T>, 'queryKey' | 'queryFn'>): UseQueryResult<T, Error> {
|
||||
async function fetchCommand<T>(command: () => Promise<T>): Promise<T> {
|
||||
try {
|
||||
const result = await command();
|
||||
return result;
|
||||
}
|
||||
// TODO: (lynith) handle errors better (requires modifications to TauRPC)
|
||||
catch (e) {
|
||||
console.error(e);
|
||||
if (e instanceof Error)
|
||||
return Promise.reject(e);
|
||||
else
|
||||
return Promise.reject(new Error('Unknown error'));
|
||||
}
|
||||
}
|
||||
|
||||
export function useCommand<T>(
|
||||
cacheKey: RegisterConfig[number] | (string & {}) | false,
|
||||
command: () => Promise<T>,
|
||||
options?: Omit<UndefinedInitialDataOptions<T>, 'queryKey' | 'queryFn'>,
|
||||
): UseQueryResult<T, Error> {
|
||||
return useQuery({
|
||||
queryKey: cacheKey === false ? [] : [cacheKey],
|
||||
queryFn: async () => {
|
||||
try {
|
||||
const result = await command();
|
||||
return result;
|
||||
}
|
||||
// TODO: (lynith) handle errors better (requires modifications to TauRPC)
|
||||
catch (e) {
|
||||
console.error(e);
|
||||
if (e instanceof Error)
|
||||
return Promise.reject(e);
|
||||
else
|
||||
return Promise.reject(new Error('Unknown error'));
|
||||
}
|
||||
},
|
||||
queryFn: () => fetchCommand(command),
|
||||
enabled: true,
|
||||
...options,
|
||||
});
|
||||
}
|
||||
|
||||
export function useCommandSuspense<T>(
|
||||
cacheKey: RegisterConfig[number] | (string & {}) | false,
|
||||
command: () => Promise<T>,
|
||||
options?: Omit<UseSuspenseQueryOptions<T>, 'queryKey' | 'queryFn'>,
|
||||
): UseSuspenseQueryResult<T, Error> {
|
||||
return useSuspenseQuery({
|
||||
queryKey: cacheKey === false ? [] : [cacheKey],
|
||||
queryFn: () => fetchCommand(command),
|
||||
...options,
|
||||
});
|
||||
}
|
||||
|
||||
export function useCommandMut<TData, TError = Error, TVariables = void, TContext = unknown>(
|
||||
command: () => Promise<TData>,
|
||||
options?: Omit<UseMutationOptions<TData, TError, TVariables, TContext>, 'mutationFn'>,
|
||||
): UseMutationResult<TData, TError, TVariables, TContext> {
|
||||
return useMutation<TData, TError, TVariables, TContext>({
|
||||
mutationFn: () => fetchCommand(command),
|
||||
...options,
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
@plugin "tailwindcss-animated";
|
||||
|
||||
@theme {
|
||||
/* foreground colors */
|
||||
/** foreground colors */
|
||||
--color-fg-primary: #d5dbff;
|
||||
--color-fg-primary-hover: #dae0ff;
|
||||
--color-fg-primary-pressed: #e1e5ff;
|
||||
|
|
@ -19,7 +19,7 @@
|
|||
--color-link-pressed: #72afff;
|
||||
--color-link-disabled: #7597b3;
|
||||
|
||||
/* background colors */
|
||||
/** background colors */
|
||||
--color-brand: #2b4bff;
|
||||
--color-brand-hover: #2843dd;
|
||||
--color-brand-pressed: #3957ff;
|
||||
|
|
@ -35,7 +35,7 @@
|
|||
--color-page-overlay: #ffffff1a;
|
||||
--color-window-frame: #151b20;
|
||||
|
||||
/* general colors */
|
||||
/** general colors */
|
||||
--color-danger: #ff4444;
|
||||
--color-danger-hover: #d63434;
|
||||
--color-danger-pressed: #ff5656;
|
||||
|
|
@ -52,12 +52,12 @@
|
|||
--color-code-debug: #2b4bff;
|
||||
--color-code-trace: #fdfdfd;
|
||||
|
||||
/* border */
|
||||
/** border */
|
||||
--color-component-border: rgba(255, 255, 255, 0.05);
|
||||
--color-component-border-hover: rgba(255, 255, 255, 0.10);
|
||||
--color-component-border-pressed: rgba(255, 255, 255, 0.15);
|
||||
|
||||
/* overlay */
|
||||
/** overlay */
|
||||
--color-ghost-overlay: rgba(255, 255, 255, 0.05);
|
||||
--color-ghost-overlay-hover: rgba(255, 255, 255, 0.10);
|
||||
--color-ghost-overlay-pressed: rgba(255, 255, 255, 0.15);
|
||||
|
|
@ -66,11 +66,11 @@
|
|||
--color-scrollbar-bg-hover: rgba(255, 255, 255, 0.10);
|
||||
--color-scrollbar-bg-pressed: rgba(255, 255, 255, 0.15);
|
||||
|
||||
/* other */
|
||||
/** other */
|
||||
--font-sans: "Poppins", sans-serif;
|
||||
--text-xs: 0.688rem;
|
||||
--text-sm: 0.75rem;
|
||||
--text-base: 0.875rem;
|
||||
--text-base: 0.875rem; /** body text is 14px */
|
||||
--text-lg: 1rem;
|
||||
--text-2lg: 1.125rem;
|
||||
--text-xl: 1.25rem;
|
||||
|
|
@ -83,6 +83,7 @@
|
|||
* {
|
||||
user-select: none !important;
|
||||
-webkit-user-select: none !important;
|
||||
-webkit-user-drag: none !important;
|
||||
cursor: default !important;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -44,6 +44,7 @@ export default defineConfig({
|
|||
'tailwindcss',
|
||||
'tailwind-merge',
|
||||
'tailwind-variants',
|
||||
'@untitled-theme/icons-react',
|
||||
// 'tailwindcss-animated',
|
||||
// 'tailwindcss-react-aria-components',
|
||||
],
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue