feat: add cluster view and improve scroll behavior

mid commit again
This commit is contained in:
emirsassan 2025-05-26 19:14:07 +03:00
parent eebd4e90c2
commit be48b6042b
14 changed files with 162 additions and 57 deletions

View file

@ -19,6 +19,7 @@ macro_rules! collect_commands {
cluster::create_cluster,
cluster::get_clusters,
cluster::get_cluster_by_id,
settings::get_global_profile,

View file

@ -216,7 +216,9 @@
// Ok(())
// }
use onelauncher_core::{api::cluster, entity::{clusters::Model, icon::Icon, loader::GameLoader}};
use std::ops::Deref;
use onelauncher_core::{api::cluster, entity::{clusters::Model, icon::Icon, loader::GameLoader}, error::LauncherResult};
use serde::{Deserialize, Serialize};
use specta::Type;
use crate::api::error::SerializableResult;
@ -235,6 +237,8 @@ pub struct CreateCluster {
pub async fn create_cluster(options: CreateCluster) -> SerializableResult<Model> {
let thing = cluster::create_cluster(&options.name, &options.mc_version, options.mc_loader, options.mc_loader_version.as_deref(), options.icon_url).await?;
cluster::prepare_cluster(&mut thing.clone(), Some(false));
Ok(thing)
}
@ -242,4 +246,10 @@ pub async fn create_cluster(options: CreateCluster) -> SerializableResult<Model>
#[tauri::command]
pub async fn get_clusters() -> SerializableResult<Vec<Model>> {
Ok(cluster::dao::get_all_clusters().await?)
}
#[specta::specta]
#[tauri::command]
pub async fn get_cluster_by_id(id: i64) -> SerializableResult<Option<Model>> {
Ok(cluster::dao::get_cluster_by_id(id).await?)
}

View file

@ -11,7 +11,7 @@
/>
<link rel="apple-touch-icon" href="/logo192.png" />
<link rel="manifest" href="/manifest.json" />
<title>OneClient</title>
<title>OneLauncher</title>
</head>
<body>
<div id="app"></div>

View file

@ -16,4 +16,4 @@ function ScrollableContainer(props: ScrollableContainerProps) {
);
}
export default ScrollableContainer;
export default ScrollableContainer;

View file

@ -8,6 +8,7 @@ import { routeTree } from './routeTree.gen';
import './fonts';
import './utils/nativeExperience';
import './utils/devExperience';
import 'overlayscrollbars/overlayscrollbars.css';
import './styles/global.css';
export * as bindings from './bindings.gen';

View file

@ -29,13 +29,13 @@ function RootRoute() {
navigate={(to, options) => router.navigate({ to, ...options })}
useHref={to => router.buildLocation({ to }).href}
>
<main className='max-h-screen min-h-screen flex flex-col'>
<div className="h-screen flex flex-col overflow-hidden px-0.5">
<WindowFrame />
<Outlet />
<TanStackRouterDevtools />
</main>
</div>
</RouterProvider>
</AnimatedOutletProvider>
);

View file

@ -0,0 +1,23 @@
import useCommand from '@/hooks/useCommand'
import { bindings } from '@/main'
import { createFileRoute } from '@tanstack/react-router'
import Sidebar from '../settings/route'
export const Route = createFileRoute('/app/cluster/')({
component: RouteComponent
})
function RouteComponent() {
const { id } = Route.useSearch()
// dumbass fix ik
const cluster = useCommand("getClusterById", () => bindings.commands.getClusterById(Number(id.toString()) as unknown as bigint))
return (
<Sidebar.Page>
<div className="h-full">
<pre>{JSON.stringify(cluster, null, 2)}</pre>
</div>
</Sidebar.Page>
)
}

View file

@ -0,0 +1,43 @@
import { createFileRoute, Outlet } from '@tanstack/react-router'
import Sidebar from '../settings/route'
import { EyeIcon, File06Icon, Globe04Icon, Image03Icon, PackagePlusIcon, Settings04Icon } from '@untitled-theme/icons-react'
type ClusterSearch = {
id: bigint
}
export const Route = createFileRoute('/app/cluster')({
component: RouteComponent,
validateSearch: (search: Record<string, unknown>): ClusterSearch => {
return {
id: BigInt(search.id as string),
}
},
})
function RouteComponent() {
return (
<div className="h-full flex flex-row overflow-hidden">
<div className="flex-shrink-0 w-72 flex flex-col pt-8 pr-7 pb-8">
<Sidebar
base="/app/cluster"
links={{
'Cluster Settings': [
[<EyeIcon />, 'Overview', '/'],
[<PackagePlusIcon />, 'Mods', '/mods'],
[<Image03Icon />, 'Screenshots', '/screenshots'],
[<Globe04Icon />, 'Worlds', '/worlds'],
[<File06Icon />, 'Logs', '/logs'],
[<Settings04Icon />, 'Game Settings', '/settings'],
],
}}
/>
{/* <Info /> */}
</div>
<div className="flex-1 min-w-0 h-full">
<Outlet />
</div>
</div>
)
}

View file

@ -4,7 +4,7 @@ import Button from '@/components/base/Button';
import useCommand from '@/hooks/useCommand';
import { bindings } from '@/main';
import { useIsFetching, useQueryClient } from '@tanstack/react-query';
import { createFileRoute, useNavigate } from '@tanstack/react-router';
import { createFileRoute, Link, useNavigate } from '@tanstack/react-router';
import { OverlayScrollbarsComponent } from 'overlayscrollbars-react';
import DefaultInstancePhoto from "@/assets/images/default_instance_cover.jpg"
@ -176,33 +176,37 @@ function ClusterGroup(props: ClusterGroupProps) {
function ClusterCard(props: Model) {
return (
<div
className="group relative h-[152px] flex flex-col rounded-xl border border-component-border/5 bg-component-bg active:bg-component-bg-pressed hover:bg-component-bg-hover"
>
<div className="relative flex-1 overflow-hidden rounded-t-xl">
<div
className="absolute h-full w-full transition-transform group-hover:!scale-110"
>
<img
className="h-full w-full object-cover"
src={DefaultInstancePhoto}
/>
<Link to='/app/cluster' search={{
id: props.id
}}>
<div
className="group relative h-[152px] flex flex-col rounded-xl border border-component-border/5 bg-component-bg active:bg-component-bg-pressed hover:bg-component-bg-hover"
>
<div className="relative flex-1 overflow-hidden rounded-t-xl">
<div
className="absolute h-full w-full transition-transform group-hover:!scale-110"
>
<img
className="h-full w-full object-cover"
src={DefaultInstancePhoto}
/>
</div>
</div>
</div>
<div className="z-10 flex flex-row items-center justify-between gap-x-3 p-3">
<div className="h-full flex flex-col gap-1.5 overflow-hidden">
<p className="h-4 text-ellipsis whitespace-nowrap font-medium">{props.name}</p>
<p className="h-4 text-xs">
{props.mc_loader}
{' '}
{props.mc_version}
{/* {' '}
<div className="z-10 flex flex-row items-center justify-between gap-x-3 p-3">
<div className="h-full flex flex-col gap-1.5 overflow-hidden">
<p className="h-4 text-ellipsis whitespace-nowrap font-medium">{props.name}</p>
<p className="h-4 text-xs">
{props.mc_loader}
{' '}
{props.mc_version}
{/* {' '}
{props.packages.mods && `• ${props.mods} mods`} */}
</p>
</div>
</p>
</div>
{/* <LaunchButton cluster={props} iconOnly /> */}
{/* <LaunchButton cluster={props} iconOnly /> */}
</div>
</div>
</div>
</Link>
);
}

View file

@ -9,33 +9,33 @@ export const Route = createFileRoute('/app')({
function RouteComponent() {
return (
<div className="h-full flex flex-col">
<>
<div className="flex flex-col px-4">
<Navbar />
</div>
<div className="h-full w-full overflow-hidden">
<div className="relative h-full w-full flex flex-col overflow-x-hidden pb-8">
<OverlayScrollbarsComponent className="h-full w-full flex flex-col overflow-x-hidden overflow-y-auto">
<div className="h-full flex-1 px-4">
<div className="flex-1 overflow-hidden">
<OverlayScrollbarsComponent
className="h-full w-full overflow-auto overflow-x-hidden"
>
<div className="h-full pb-4 px-4">
<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' }}
/>
<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' }}
/>
</div>
</OverlayScrollbarsComponent>
</div>
</div>
</OverlayScrollbarsComponent>
</div>
</div>
</>
);
}

View file

@ -16,7 +16,7 @@ function RouteComponent() {
return (
<Sidebar.Page>
<div>
<div className="h-full">
<h1>General Settings</h1>
<SettingsRow

View file

@ -11,7 +11,7 @@ export const Route = createFileRoute('/app/settings/minecraft')({
function RouteComponent() {
return (
<Sidebar.Page>
<div>
<div className="h-full">
<h1>Minecraft Settings</h1>
<SettingsRow.Header>Game</SettingsRow.Header>

View file

@ -75,11 +75,11 @@ function Sidebar(props: SidebarProps) {
return (
<div className="w-56 flex flex-col pr-2">
{Object.keys(props.links).map((section, i) => (
<div className="flex flex-col gap-y-2">
<div key={i}>
<div className="flex flex-col gap-y-2" key={i}>
<div>
<h3 className="m-1.5 mt-5 text-xs text-fg-secondary font-medium">{section.toUpperCase()}</h3>
<div className="flex flex-col gap-y-1 fill-fg-primary text-fg-primary font-medium">
{props.links[section].map((link) => {
{props.links[section].map((link, i) => {
if (!link)
return;
@ -88,6 +88,7 @@ function Sidebar(props: SidebarProps) {
className={
`px-3 py-1 rounded-md text-md border border-component-bg hover:bg-component-bg-hover active:bg-component-bg-pressed ${isActive(link[2], link[3]) ? 'bg-component-bg border-border/05' : 'border-transparent'}`
}
key={i}
onClick={() => goto(link[2], link[3])}
>
<span className="flex flex-row items-center gap-x-3 *:w-5">
@ -113,7 +114,7 @@ interface SidebarPageProps {
Sidebar.Page = function (props: SidebarPageProps) {
return (
<div {...props} className={`flex flex-col flex-1 gap-y-1 ${props.className || ''}`}>
<div {...props} className={`flex flex-col flex-1 gap-y-1 overflow-y-auto h-full ${props.className || ''}`}>
{props.children}
</div>
);

View file

@ -60,6 +60,10 @@
--color-ghost-overlay: rgba(255, 255, 255, 0.05);
--color-ghost-overlay-hover: rgba(255, 255, 255, 0.10);
--color-scrollbar-bg: rgba(255, 255, 255, 0.05);
--color-scrollbar-bg-hover: rgba(255, 255, 255, 0.10);
--color-scrollbar-bg-pressed: rgba(255, 255, 255, 0.15);
/* other */
--font-sans: "Poppins", sans-serif;
--text-xs: 0.688rem;
@ -83,3 +87,21 @@ body {
@apply bg-page text-fg-primary text-base;
}
.os-scrollbar {
--os-size: 10px;
--os-track-border-radius: var(--border-radius-lg);
--os-handle-bg: var(--color-scrollbar-bg);
--os-handle-bg-hover: var(--color-scrollbar-bg-hover);
--os-handle-bg-active: var(--color-scrollbar-bg-pressed);
--os-handle-perpendicular-size-hover: 140%;
--os-handle-perpendicular-size-active: 140%;
--os-handle-interactive-area-offset: 4px;
}
.os-scrollbar-offset-right {
--os-padding-axis: 10;
}
.os-hide-horizontal-scrollbar > .os-scrollbar-horizontal {
display: none;
}