refactor: categories pt. 1 (pain)
This commit is contained in:
parent
6841051b7a
commit
b4746b8ec6
6 changed files with 167 additions and 112 deletions
|
|
@ -1,5 +1,7 @@
|
|||
import type { Provider, SearchResult } from '@/bindings.gen';
|
||||
import { getCategories } from '@/hooks/useBrowser';
|
||||
import { abbreviateNumber, LOADERS, upperFirst } from '@/utils';
|
||||
import { categoryNameFromId } from '@/utils/browser';
|
||||
import { Show, Tooltip } from '@onelauncher/common/components';
|
||||
import { Link } from '@tanstack/react-router';
|
||||
import { Download01Icon } from '@untitled-theme/icons-react';
|
||||
|
|
@ -22,7 +24,10 @@ export function PackageGrid({ items, provider }: { items: Array<SearchResult>; p
|
|||
}
|
||||
|
||||
export function PackageItem({ provider, ...item }: SearchResult & { provider: Provider }) {
|
||||
const loaders = useMemo(() => item.categories.filter(cat => includes(LOADERS, cat)), [item.categories]);
|
||||
const categoryName = categoryNameFromId[item.package_type];
|
||||
if (!(categoryName in item.categories))
|
||||
throw new Error('invalid categories');
|
||||
const loaders = useMemo(() => getCategories(item.categories).filter(cat => includes(LOADERS, cat)), [item.categories]);
|
||||
|
||||
return (
|
||||
<Link
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import type { ClusterModel, GameLoader, ManagedPackage, ManagedVersion, Paginated, Provider, SearchQuery, SearchResult } from '@/bindings.gen';
|
||||
import type { ClusterModel, GameLoader, ManagedPackage, ManagedVersion, PackageCategories, Paginated, Provider, SearchQuery, SearchResult } from '@/bindings.gen';
|
||||
import type { BindingCommands } from '@/types/global';
|
||||
import type { UndefinedInitialDataOptions } from '@tanstack/react-query';
|
||||
import type { Dispatch, SetStateAction } from 'react';
|
||||
|
|
@ -37,7 +37,7 @@ export function BrowserProvider(props: any) {
|
|||
categories: null,
|
||||
game_versions: null,
|
||||
loaders: null,
|
||||
package_types: ['mod'],
|
||||
package_type: 'mod',
|
||||
},
|
||||
query: null,
|
||||
limit: 18 as unknown as bigint,
|
||||
|
|
@ -67,6 +67,20 @@ export function BrowserProvider(props: any) {
|
|||
);
|
||||
}
|
||||
|
||||
export function getCategories(categories: PackageCategories) {
|
||||
if ('Mod' in categories)
|
||||
return categories.Mod;
|
||||
if ('ResourcePack' in categories)
|
||||
return categories.ResourcePack;
|
||||
if ('Shader' in categories)
|
||||
return categories.Shader;
|
||||
if ('DataPack' in categories)
|
||||
return categories.DataPack;
|
||||
if ('ModPack' in categories)
|
||||
return categories.ModPack;
|
||||
return [];
|
||||
}
|
||||
|
||||
export function useBrowserSearch(provider: Provider, query: SearchQuery, options?: Omit<UndefinedInitialDataOptions<Paginated<SearchResult>>, 'queryKey' | 'queryFn'> | undefined) {
|
||||
const validFilters = useMemo(() => Object.values(query.filters ?? {}).filter(a => a).length > 0, [query.filters]);
|
||||
return useCommand('searchPackages', () => bindings.core.searchPackages(provider, validFilters ? query : { ...query, filters: null }), options);
|
||||
|
|
@ -76,8 +90,8 @@ export function usePackageData(provider: Provider, slug: string, options?: Omit<
|
|||
return useCommand(key, () => bindings.core.getPackage(provider, slug), options);
|
||||
}
|
||||
|
||||
export function usePackageVersions(provider: Provider, slug: string, { mc_versions, loaders, offset, limit, ...options }: { mc_versions?: Array<string> | null; loaders?: Array<GameLoader> | null; offset?: number; limit: number } & Omit<UndefinedInitialDataOptions<Paginated<ManagedVersion>>, 'queryKey' | 'queryFn'>, key: false | BindingCommands | (string & {}) = `getPackageVersions.${provider}.${slug}.${mc_versions?.join('')}.${loaders?.join('')}`) {
|
||||
return useCommand(key, () => bindings.core.getPackageVersions(provider, slug, mc_versions ?? null, loaders ?? null, (offset ?? 0) as unknown as bigint, limit as unknown as bigint), options);
|
||||
export function usePackageVersions(provider: Provider, slug: string, { mc_version, loader, offset, limit, ...options }: { mc_version?: string | null; loader?: GameLoader | null; offset?: number; limit: number } & Omit<UndefinedInitialDataOptions<Paginated<ManagedVersion>>, 'queryKey' | 'queryFn'>, key: false | BindingCommands | (string & {}) = `getPackageVersions.${provider}.${slug}.${mc_version}.${loader}`) {
|
||||
return useCommand(key, () => bindings.core.getPackageVersions(provider, slug, mc_version ?? null, loader ?? null, (offset ?? 0) as unknown as bigint, limit as unknown as bigint), options);
|
||||
}
|
||||
|
||||
export function useDownloadPackage(cluster: ClusterModel, provider: Provider, version: ManagedVersion, skipCompatibility = false) {
|
||||
|
|
|
|||
|
|
@ -81,7 +81,9 @@ function RouteComponent() {
|
|||
rehypePlugins={[rehypeRaw]}
|
||||
remarkPlugins={[remarkGfm]}
|
||||
>
|
||||
{packageData.data?.body}
|
||||
{'Raw' in (packageData.data?.body ?? { Raw: '' })
|
||||
? (packageData.data?.body ?? { Raw: '' }).Raw
|
||||
: packageData.data?.body.Url}
|
||||
</Markdown>
|
||||
</div>
|
||||
</TabPanel>
|
||||
|
|
@ -254,8 +256,8 @@ function InstallButton() {
|
|||
const clusters = useClusters();
|
||||
const browserContext = useBrowserContext();
|
||||
const { data: versions, isLoading: versionsLoading } = usePackageVersions(provider, slug, {
|
||||
mc_versions: browserContext.cluster ? [browserContext.cluster.mc_version] : [],
|
||||
loaders: browserContext.cluster ? [browserContext.cluster.mc_loader] : [],
|
||||
mc_version: browserContext.cluster ? browserContext.cluster.mc_version : null,
|
||||
loader: browserContext.cluster ? browserContext.cluster.mc_loader : null,
|
||||
limit: 1,
|
||||
});
|
||||
const version = useMemo(() => {
|
||||
|
|
|
|||
|
|
@ -1,14 +1,17 @@
|
|||
import type { Filters } from '@/bindings.gen';
|
||||
import type { Filters, PackageCategories } from '@/bindings.gen';
|
||||
import type { Key, NonReadonly } from '@/utils';
|
||||
import type { PropsWithChildren } from 'react';
|
||||
import ProviderIcon from '@/components/content/ProviderIcon';
|
||||
import { BrowserProvider, useBrowserContext } from '@/hooks/useBrowser';
|
||||
import { useClusters } from '@/hooks/useCluster';
|
||||
import { PROVIDERS } from '@/utils';
|
||||
import { browserCategories } from '@/utils/browser';
|
||||
import { bindings } from '@/main';
|
||||
import { PROVIDERS, upperFirst } from '@/utils';
|
||||
import { browserCategories, categoryNameFromId } from '@/utils/browser';
|
||||
import { useCommand } from '@onelauncher/common';
|
||||
import { AnimatedOutlet, Dropdown, Show, TextField } from '@onelauncher/common/components';
|
||||
import { createFileRoute } from '@tanstack/react-router';
|
||||
import { SearchMdIcon } from '@untitled-theme/icons-react';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { SelectValue } from 'react-aria-components';
|
||||
|
||||
export const Route = createFileRoute('/app/browser')({
|
||||
|
|
@ -59,19 +62,25 @@ const defaultFilters: Filters = {
|
|||
categories: null,
|
||||
game_versions: null,
|
||||
loaders: null,
|
||||
package_types: null,
|
||||
package_type: null,
|
||||
};
|
||||
|
||||
type CategoryFilter = Partial<NonReadonly<typeof browserCategories>>;
|
||||
|
||||
function BrowserCategories() {
|
||||
const context = useBrowserContext();
|
||||
const categories = browserCategories.byPackageType((context.query.filters?.package_types ?? ['mod'])[0], context.provider);
|
||||
const categoryName = useMemo(() => categoryNameFromId[context.query.filters?.package_type ?? 'mod'], [context.query.filters]);
|
||||
const categories = browserCategories[categoryName];
|
||||
|
||||
function switchCategory(category: string) {
|
||||
const newCategories = context.query.filters?.categories?.includes(category)
|
||||
? context.query.filters.categories.filter(cat => cat !== category)
|
||||
: [...(context.query.filters?.categories ?? []), category];
|
||||
context.setQuery({ ...context.query, filters: { ...defaultFilters, ...context.query.filters, categories: newCategories.length > 0 ? newCategories : null } });
|
||||
}
|
||||
const switchCategory = useMemo(() => (category: string) => {
|
||||
const isCorrectPackageType = context.query.filters && context.query.filters.categories && categoryName in context.query.filters.categories;
|
||||
const isEnabled = isCorrectPackageType && context.query.filters.categories[categoryName].includes(category);
|
||||
const newCategories = isCorrectPackageType
|
||||
? { [categoryName]: isEnabled ? context.query.filters.categories?.[categoryName].filter(c => c !== category) : [...context.query.filters.categories[categoryName], category] }
|
||||
: { [categoryName]: [category] };
|
||||
|
||||
context.setQuery({ ...context.query, filters: { ...defaultFilters, ...context.query.filters, categories: newCategories } });
|
||||
}, [context, categoryName]);
|
||||
|
||||
return (
|
||||
<div className="top-0 h-fit min-w-50">
|
||||
|
|
@ -82,12 +91,12 @@ function BrowserCategories() {
|
|||
<h5 className="my-1 text-sm uppercase opacity-60 font-light">Categories</h5>
|
||||
{categories.map(category => (
|
||||
<p
|
||||
aria-selected={context.query.filters?.categories?.includes(category.id)}
|
||||
aria-selected={context.query.filters?.categories?.[categoryName].includes(category)}
|
||||
className="text-sm capitalize opacity-60 hover:opacity-90 text-fg-primary hover:text-fg-primary-hover aria-selected:opacity-100"
|
||||
key={category.id}
|
||||
onClick={() => switchCategory(category.id)}
|
||||
key={category}
|
||||
onClick={() => switchCategory(category)}
|
||||
>
|
||||
{category.display}
|
||||
{category}
|
||||
</p>
|
||||
))}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,97 +1,116 @@
|
|||
import type { PackageType, Provider } from '@/bindings.gen';
|
||||
import type { PackageCategories, PackageModCategory, PackageType, Provider } from '@/bindings.gen';
|
||||
import type { Key } from '.';
|
||||
|
||||
export const BROWSER_VIEWS: Array<string> = ['grid', 'list'] as const;
|
||||
|
||||
export interface CategoryItem {
|
||||
display: string;
|
||||
id: string;
|
||||
}
|
||||
const ModCategories = [
|
||||
'Adventure',
|
||||
'Library',
|
||||
'Equipment',
|
||||
'Patches',
|
||||
'Cosmetic',
|
||||
'Food',
|
||||
'Magic',
|
||||
'Information',
|
||||
'Misc',
|
||||
'Performance',
|
||||
'Redstone',
|
||||
'ServerUtil',
|
||||
'Storage',
|
||||
'Technology',
|
||||
'Farming',
|
||||
'Automation',
|
||||
'Transport',
|
||||
'Utility',
|
||||
'QoL',
|
||||
'WorldGen',
|
||||
'Mobs',
|
||||
'Economy',
|
||||
'Social',
|
||||
] as const;
|
||||
|
||||
const ModpackCategories = [
|
||||
'Technology',
|
||||
'Quests',
|
||||
'Optimization',
|
||||
'Multiplayer',
|
||||
'Magic',
|
||||
'LightWeight',
|
||||
'Combat',
|
||||
'Challenging',
|
||||
'Adventure',
|
||||
] as const;
|
||||
|
||||
const ResourcePackCategories = [
|
||||
'X8',
|
||||
'X16',
|
||||
'X32',
|
||||
'X48',
|
||||
'X64',
|
||||
'X128',
|
||||
'X256',
|
||||
'X512',
|
||||
'VanillaLike',
|
||||
'Utility',
|
||||
'Tweaks',
|
||||
'Themed',
|
||||
'Simplistic',
|
||||
'Realistic',
|
||||
'Modded',
|
||||
'Decoration',
|
||||
'Cursed',
|
||||
'Combat',
|
||||
'Audio',
|
||||
'Blocks',
|
||||
'CoreShaders',
|
||||
'Gui',
|
||||
'Fonts',
|
||||
'Equipment',
|
||||
'Environment',
|
||||
'Entities',
|
||||
'Items',
|
||||
'Locale',
|
||||
'Models',
|
||||
] as const;
|
||||
|
||||
const ShaderCategories = [
|
||||
'VanillaLike',
|
||||
'SemiRealistic',
|
||||
'Realistic',
|
||||
'Fantasy',
|
||||
'Cursed',
|
||||
'Cartoon',
|
||||
'Bloom',
|
||||
'Atmosphere',
|
||||
'Reflections',
|
||||
'Shadows',
|
||||
'PBR',
|
||||
'PathTracing',
|
||||
'Foliage',
|
||||
'ColoredLightning',
|
||||
'Potato',
|
||||
'Low',
|
||||
'Medium',
|
||||
'High',
|
||||
'Ultra',
|
||||
] as const;
|
||||
|
||||
export const browserCategories = {
|
||||
byPackageType(packageType: PackageType, provider: Provider): Array<CategoryItem> {
|
||||
switch (packageType) {
|
||||
case 'mod':
|
||||
return this.mod(provider);
|
||||
}
|
||||
Mod: ModCategories,
|
||||
ResourcePack: ResourcePackCategories,
|
||||
ModPack: ModpackCategories,
|
||||
Shader: ShaderCategories,
|
||||
DataPack: ModCategories,
|
||||
} as const satisfies Record<Key<PackageCategories>, ReadonlyArray<string>>;
|
||||
|
||||
return [];
|
||||
},
|
||||
|
||||
mod(provider: Provider = 'Modrinth'): Array<CategoryItem> {
|
||||
return Object.values(modMapping[provider]);
|
||||
},
|
||||
type CategoryIdMap = {
|
||||
[K in keyof typeof browserCategories as Lowercase<K>]: K
|
||||
};
|
||||
|
||||
const modMapping: Record<Provider, Array<CategoryItem>> = {
|
||||
CurseForge: [
|
||||
{ display: 'Adventure', id: '422' },
|
||||
{ display: 'API and Library', id: '421' },
|
||||
{ display: 'Armor and Tools', id: '434' },
|
||||
{ display: 'Biomes', id: '407' },
|
||||
{ display: 'Bug Fixes', id: '6821' },
|
||||
{ display: 'Cosmetic', id: '424' },
|
||||
{ display: 'Dimensions', id: '410' },
|
||||
{ display: 'Education', id: '5299' },
|
||||
{ display: 'Energy', id: '417' },
|
||||
{ display: 'Farming', id: '416' },
|
||||
{ display: 'Food', id: '436' },
|
||||
{ display: 'Forestry', id: '433' },
|
||||
{ display: 'Magic', id: '419' },
|
||||
{ display: 'Miscellaneous', id: '425' },
|
||||
{ display: 'Mobs', id: '411' },
|
||||
{ display: 'Ores and Resources', id: '408' },
|
||||
{ display: 'Performance', id: '6814' },
|
||||
{ display: 'Player Transport', id: '414' },
|
||||
{ display: 'Redstone', id: '4558' },
|
||||
{ display: 'Storage', id: '420' },
|
||||
{ display: 'Structures', id: '409' },
|
||||
{ display: 'Technology', id: '412' },
|
||||
{ display: 'Utility & QoL', id: '5191' },
|
||||
{ display: 'World Generation', id: '406' },
|
||||
|
||||
// { display: 'Map and Information', id: '423' },
|
||||
// { display: 'Server Utility', id: '435' },
|
||||
// { display: 'Twitch Integration', id: '4671' },
|
||||
// { display: 'Create', id: '6484' },
|
||||
// { display: 'Buildcraft', id: '432' },
|
||||
// { display: 'Industrial Craft', id: '429' },
|
||||
// { display: 'KubeJS', id: '5314' },
|
||||
// { display: 'Genetics', id: '418' },
|
||||
// { display: 'Blood Magic', id: '4485' },
|
||||
// { display: 'Automation', id: '4843' },
|
||||
// { display: 'CraftTweaker', id: '4773' },
|
||||
// { display: 'Addons', id: '426' },
|
||||
// { display: 'Energy, Fluid, and Item Transport', id: '415' },
|
||||
// { display: 'Thermal Expansion', id: '427' },
|
||||
// { display: 'Thaumcraft', id: '430' },
|
||||
// { display: 'Processing', id: '413' },
|
||||
// { display: 'Tinker\'s Construct', id: '428' },
|
||||
// { display: 'Skyblock', id: '6145' },
|
||||
// { display: 'Galacticraft', id: '5232' },
|
||||
// { display: 'Applied Energistics 2', id: '4545' },
|
||||
// { display: 'Integrated Dynamics', id: '6954' },
|
||||
// { display: 'MCreator', id: '4906' },
|
||||
],
|
||||
Modrinth: [
|
||||
{ display: 'Adventure', id: 'adventure' },
|
||||
{ display: 'Cursed', id: 'cursed' },
|
||||
{ display: 'Decoration', id: 'decoration' },
|
||||
{ display: 'Economy', id: 'economy' },
|
||||
{ display: 'Equipment', id: 'equipment' },
|
||||
{ display: 'Food', id: 'food' },
|
||||
{ display: 'Game Mechanics', id: 'game-mechanics' },
|
||||
{ display: 'Library', id: 'library' },
|
||||
{ display: 'Magic', id: 'magic' },
|
||||
{ display: 'Management', id: 'management' },
|
||||
{ display: 'Minigame', id: 'minigame' },
|
||||
{ display: 'Mobs', id: 'mobs' },
|
||||
{ display: 'Optimization', id: 'optimization' },
|
||||
{ display: 'Social', id: 'social' },
|
||||
{ display: 'Storage', id: 'storage' },
|
||||
{ display: 'Technology', id: 'technology' },
|
||||
{ display: 'Transportation', id: 'transportation' },
|
||||
{ display: 'Utility', id: 'utility' },
|
||||
{ display: 'World Generation', id: 'worldgen' },
|
||||
],
|
||||
SkyClient: [],
|
||||
export const categoryNameFromId: CategoryIdMap = {
|
||||
mod: 'Mod',
|
||||
resourcepack: 'ResourcePack',
|
||||
modpack: 'ModPack',
|
||||
shader: 'Shader',
|
||||
datapack: 'DataPack',
|
||||
};
|
||||
|
|
|
|||
|
|
@ -133,3 +133,9 @@ export function formatAsDuration(seconds: number | bigint | Date): string {
|
|||
export function includes<T, TArray extends T>(list: { includes: (arg0: TArray) => boolean }, element: T): element is TArray {
|
||||
return list.includes(element as unknown as TArray);
|
||||
}
|
||||
|
||||
export type Key<T> = T extends Record<infer K, any> ? K : never;
|
||||
|
||||
export type NonReadonly<T> = {
|
||||
[P in keyof T]: T[P] extends readonly infer O ? O : P
|
||||
};
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue