feat: browser

This commit is contained in:
Szedann 2025-08-20 14:08:06 +02:00
parent e6a865657c
commit b2f8a14acb
3 changed files with 75 additions and 26 deletions

View file

@ -230,25 +230,14 @@ export type VersionType =
*/
"old_beta"
<<<<<<< HEAD
const ARGS_MAP = { 'folders':'{"openCluster":["folder_name"],"fromCluster":["folder_name"]}', 'events':'{"ingress":["event"],"process":["event"],"message":["event"]}', 'oneclient':'{"getClustersGroupedByMajor":[],"openDevTools":[]}', 'core':'{"getWorlds":["id"],"launchCluster":["id","uuid"],"getDefaultUser":["fallback"],"getPackageVersions":["provider","slug","mc_version","loader","offset","limit"],"getClusterById":["id"],"downloadPackage":["provider","package_id","version_id","cluster_id","skip_compatibility"],"getProfileOrDefault":["name"],"updateClusterProfile":["name","profile"],"writeSettings":["setting"],"updateClusterById":["id","request"],"getMultiplePackages":["provider","slugs"],"removeCluster":["id"],"getUser":["uuid"],"getGlobalProfile":[],"openMsaLogin":[],"getPackageBody":["provider","body"],"getLogByName":["id","name"],"getLoadersForVersion":["mc_version"],"searchPackages":["provider","query"],"readSettings":[],"removeUser":["uuid"],"getClusters":[],"getGameVersions":[],"getUsers":[],"getPackage":["provider","slug"],"getUsersFromAuthor":["provider","author"],"getScreenshots":["id"],"getLogs":["id"],"setDefaultUser":["uuid"],"createCluster":["options"]}' }
export type Router = { 'events': { ingress: (event: IngressPayload) => Promise<void>,
message: (event: MessagePayload) => Promise<void>,
process: (event: ProcessPayload) => Promise<void> },
'folders': { fromCluster: (folderName: string) => Promise<string>,
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<string>,
openCluster: (folderName: string) => Promise<null> },
'oneclient': { openDevTools: () => Promise<void>,
getClustersGroupedByMajor: () => Promise<Partial<{ [key in number]: ClusterModel[] }>> },
=======
const ARGS_MAP = { 'oneclient':'{"openDevTools":[],"getClustersGroupedByMajor":[]}', 'events':'{"message":["event"],"process":["event"],"ingress":["event"]}', 'core':'{"getLoadersForVersion":["mc_version"],"writeSettings":["setting"],"getLogs":["id"],"getWorlds":["id"],"getClusterById":["id"],"launchCluster":["id","uuid"],"getPackage":["provider","slug"],"getGlobalProfile":[],"downloadPackage":["provider","package_id","version_id","cluster_id","skip_compatibility"],"readSettings":[],"updateClusterById":["id","request"],"getUser":["uuid"],"getPackageBody":["provider","body"],"fetchMinecraftProfile":["uuid"],"createCluster":["options"],"updateClusterProfile":["name","profile"],"getScreenshots":["id"],"removeUser":["uuid"],"getDefaultUser":["fallback"],"openMsaLogin":[],"getGameVersions":[],"setDefaultUser":["uuid"],"getUsersFromAuthor":["provider","author"],"removeCluster":["id"],"getLogByName":["id","name"],"getProfileOrDefault":["name"],"getClusters":[],"getMultiplePackages":["provider","slugs"],"getUsers":[],"searchPackages":["provider","query"],"getPackageVersions":["provider","slug","mc_version","loader","offset","limit"]}', 'folders':'{"openCluster":["folder_name"],"fromCluster":["folder_name"]}' }
export type Router = { 'events': { ingress: (event: IngressPayload) => Promise<void>,
'events': { ingress: (event: IngressPayload) => Promise<void>,
message: (event: MessagePayload) => Promise<void>,
process: (event: ProcessPayload) => Promise<void> },
'oneclient': { openDevTools: () => Promise<void>,
getClustersGroupedByMajor: () => Promise<Partial<{ [key in number]: ClusterModel[] }>> },
'folders': { fromCluster: (folderName: string) => Promise<string>,
openCluster: (folderName: string) => Promise<null> },
>>>>>>> 04cd2183d01fa465f38ac0b802c81eb344a7ecf9
'core': { getClusters: () => Promise<ClusterModel[]>,
getClusterById: (id: number) => Promise<ClusterModel | null>,
removeCluster: (id: number) => Promise<null>,
@ -278,12 +267,8 @@ getPackageBody: (provider: Provider, body: ManagedPackageBody) => Promise<string
getMultiplePackages: (provider: Provider, slugs: string[]) => Promise<ManagedPackage[]>,
getPackageVersions: (provider: Provider, slug: string, mcVersion: string | null, loader: GameLoader | null, offset: number, limit: number) => Promise<Paginated<ManagedVersion>>,
downloadPackage: (provider: Provider, packageId: string, versionId: string, clusterId: number, skipCompatibility: boolean | null) => Promise<PackageModel>,
<<<<<<< HEAD
getUsersFromAuthor: (provider: Provider, author: PackageAuthor) => Promise<ManagedUser[]> } };
=======
getUsersFromAuthor: (provider: Provider, author: PackageAuthor) => Promise<ManagedUser[]>,
fetchMinecraftProfile: (uuid: string) => Promise<MojangPlayerProfile> } };
>>>>>>> 04cd2183d01fa465f38ac0b802c81eb344a7ecf9
export type { InferCommandOutput }

View file

@ -1,23 +1,29 @@
import type { PackageType, Provider } from '@/bindings.gen';
import type { Filters, PackageCategories, PackageType, Provider } from '@/bindings.gen';
import { LoaderSuspense } from '@/components';
import { PackageGrid } from '@/components/PackageItem';
import { bindings } from '@/main';
import { browserCategories, categoryNameFromId } from '@/utils/browser';
import { useCommandSuspense, usePagination } from '@onelauncher/common';
import { Dropdown, Show, TextField } from '@onelauncher/common/components';
import { createFileRoute } from '@tanstack/react-router';
import { SearchMdIcon } from '@untitled-theme/icons-react';
import { useMemo } from 'react';
import { useEffect, useMemo, useRef, useState } from 'react';
interface BrowserSearchRouteSearchParams {
query?: string;
packageType: PackageType;
categories: Array<string>;
page: number;
}
export const Route = createFileRoute('/app/cluster/browser/')({
component: RouteComponent,
validateSearch: (search): BrowserSearchRouteSearchParams => {
return {
query: search.query as string,
query: (search.query ?? '') as string,
packageType: search.packageType as PackageType,
categories: search.categories as Array<string>,
categories: (search.categories ?? []) as Array<string>,
page: Number(search.page ?? 0),
};
},
});
@ -39,6 +45,9 @@ function RouteComponent() {
<div className="h-full flex flex-col gap-y-4">
<div className="h-full flex-1">
<LoaderSuspense spinner={{ size: 'large' }}>
<SearchResult />
</LoaderSuspense>
</div>
</div>
@ -54,17 +63,18 @@ function BrowserCategories() {
const search = Route.useSearch();
const { categories, packageType } = search;
const navigate = Route.useNavigate();
const navRef = useRef(navigate);
const switchCategory = useMemo(() => (category: string) => {
const newCategories = categories.includes(category)
? categories.filter(element => element !== category)
: [...categories, category];
navigate({
navRef.current({
search: {
...search,
categories: newCategories,
},
});
}, []);
}, [search, categories]);
return (
<div className="top-0 h-fit min-w-50">
@ -97,6 +107,7 @@ function BrowserToolbar() {
const setQuery = (query: string) => {
navigate({ search: { ...search, query } });
};
const [searchBar, setSearchBar] = useState(query);
return (
<div className="w-full flex flex-row justify-between bg-page">
<div className="flex flex-row gap-2">
@ -120,7 +131,7 @@ function BrowserToolbar() {
<TextField
className="min-w-64"
iconLeft={<SearchMdIcon className="scale-75" />}
// onChange={e => setQuery(e.currentTarget.value)}
onChange={e => setSearchBar(e.currentTarget.value)}
onKeyDown={(e) => {
if (e.key !== 'Enter')
return;
@ -128,9 +139,61 @@ function BrowserToolbar() {
setQuery(e.currentTarget.value);
}}
placeholder="Search for content"
value={query}
value={searchBar}
/>
</div>
</div>
);
}
interface SearchQuery {
query: string | null;
offset: number | null;
limit: number;
sort: 'Relevance' | 'Downloads' | 'Newest' | 'Updated' | null;
filters: Filters | null;
}
const itemsPerPage = 25;
function SearchResult() {
const search = Route.useSearch();
const { cluster } = Route.useRouteContext();
const navigate = Route.useNavigate();
const navRef = useRef(navigate);
const query = useMemo<SearchQuery>(() => ({
query: search.query ?? null,
offset: (search.page - 1) * itemsPerPage,
limit: itemsPerPage,
sort: 'Relevance',
filters: {
game_versions: [cluster.mc_version],
categories: { [categoryNameFromId[search.packageType]]: search.categories } as PackageCategories,
loaders: [cluster.mc_loader],
package_type: search.packageType,
},
}), [cluster, search]);
const results = useCommandSuspense(['searchPackages', search], () => bindings.core.searchPackages(
search.provider,
query,
));
const pagination = usePagination({
itemsPerPage: query.limit,
itemsCount: results.data.total,
});
const paginationRef = useRef(pagination);
useEffect(() => {
navRef.current({ search: prev => ({ ...prev, page: pagination.page }) });
}, [pagination.page]);
useEffect(() => {
paginationRef.current.reset();
}, [pagination.totalPages]);
return (
<div className="flex flex-col gap-2">
<div className="flex justify-end">
<pagination.Navigation />
</div>
<PackageGrid items={results.data.items} provider={search.provider} />
</div>
);
}

View file

@ -67,6 +67,7 @@ function RouteComponent() {
<TabList className="sticky top-0 z-10 shadow-lg" floating={floating} ref={tabListRef}>
<Tab from={Route.id} search={search} to="/app/cluster/overview">Overview</Tab>
<Tab from={Route.id} search={search} to="/app/cluster/logs">Logs</Tab>
<Tab from={Route.id} search={{ ...search, packageType: 'mod' }} to="/app/cluster/browser">Browser</Tab>
</TabList>
<div className="relative pb-8">