feat: Category mapping + search results tweaks
This commit is contained in:
parent
02f1aaa429
commit
f50d99a2bc
8 changed files with 176 additions and 94 deletions
|
|
@ -20,11 +20,10 @@ function Markdown(props: MarkdownProps) {
|
|||
e.preventDefault();
|
||||
}}
|
||||
onClick={(e) => {
|
||||
e.stopImmediatePropagation();
|
||||
e.preventDefault();
|
||||
|
||||
// TODO: Create a custom renderer or just use something else instead of markdown-it :D
|
||||
if (e.target.nodeName === 'A') {
|
||||
e.stopImmediatePropagation();
|
||||
e.preventDefault();
|
||||
|
||||
const target = e.target as HTMLAnchorElement;
|
||||
if (target.href.startsWith('http'))
|
||||
promptOpen(target.href);
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import type { Providers, SearchResult } from '@onelauncher/client/bindings';
|
||||
import { ChevronDownIcon, Download01Icon, HeartIcon } from '@untitled-theme/icons-solid';
|
||||
import type { JSX } from 'solid-js';
|
||||
import { For, Match, Show, Switch, createSignal } from 'solid-js';
|
||||
import Button from '../base/Button';
|
||||
import useBrowser from '~ui/hooks/useBrowser';
|
||||
|
|
@ -9,7 +10,7 @@ import { abbreviateNumber } from '~utils';
|
|||
interface SearchResultsContainerProps {
|
||||
provider: Providers;
|
||||
results: SearchResult[];
|
||||
header: string;
|
||||
header: string | JSX.Element;
|
||||
category: string;
|
||||
collapsable?: boolean;
|
||||
}
|
||||
|
|
@ -22,7 +23,15 @@ function SearchResultsContainer(props: SearchResultsContainerProps) {
|
|||
return (
|
||||
<div class="relative flex flex-1 flex-col gap-3">
|
||||
<div class="flex flex-1 flex-row justify-between">
|
||||
<h5 class="ml-2">{props.header}</h5>
|
||||
<Switch>
|
||||
<Match when={typeof props.header === 'string'}>
|
||||
<h5 class="ml-2">{props.header}</h5>
|
||||
</Match>
|
||||
|
||||
<Match when>
|
||||
{props.header}
|
||||
</Match>
|
||||
</Switch>
|
||||
</div>
|
||||
|
||||
<div
|
||||
|
|
@ -32,6 +41,10 @@ function SearchResultsContainer(props: SearchResultsContainerProps) {
|
|||
}}
|
||||
>
|
||||
<Switch>
|
||||
<Match when={props.results.length === 0}>
|
||||
<p class="mb-8 text-center text-fg-secondary">No results found</p>
|
||||
</Match>
|
||||
|
||||
<Match when={settings().browser_list_view === 'list'}>
|
||||
<PackageList {...props} />
|
||||
</Match>
|
||||
|
|
@ -107,16 +120,29 @@ function PackageItem(props: SearchResult & { provider: Providers; row?: boolean
|
|||
'w-full h-28': !props.row,
|
||||
}}
|
||||
>
|
||||
<img class="absolute z-0 max-w-none w-7/6 opacity-50 filter-blur-xl" src={props.icon_url || ''} alt={`Icon for ${props.title}`} />
|
||||
<img
|
||||
class="relative z-1 aspect-ratio-square rounded-md image-render-auto"
|
||||
classList={{
|
||||
'w-2/5': !props.row,
|
||||
'w-3/4': props.row,
|
||||
}}
|
||||
src={props.icon_url || ''}
|
||||
alt={`Icon for ${props.title}`}
|
||||
/>
|
||||
<Show
|
||||
fallback={(
|
||||
<div
|
||||
class="aspect-ratio-square rounded-md bg-gray-05"
|
||||
classList={{
|
||||
'w-2/5': !props.row,
|
||||
'w-3/4': props.row,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
when={props.icon_url}
|
||||
>
|
||||
<img class="absolute z-0 max-w-none w-7/6 opacity-50 filter-blur-xl" src={props.icon_url!} alt={`Icon for ${props.title}`} />
|
||||
<img
|
||||
class="relative z-1 aspect-ratio-square rounded-md image-render-auto"
|
||||
classList={{
|
||||
'w-2/5': !props.row,
|
||||
'w-3/4': props.row,
|
||||
}}
|
||||
src={props.icon_url!}
|
||||
alt={`Icon for ${props.title}`}
|
||||
/>
|
||||
</Show>
|
||||
</div>
|
||||
<div class="flex flex-1 flex-col gap-2 p-3">
|
||||
<div class="flex flex-col gap-2">
|
||||
|
|
|
|||
|
|
@ -10,6 +10,8 @@ import Modal, { createModal } from '~ui/components/overlay/Modal';
|
|||
import Dropdown from '~ui/components/base/Dropdown';
|
||||
import Button from '~ui/components/base/Button';
|
||||
|
||||
export type ProviderSearchOptions = ProviderSearchQuery & { provider: Providers };
|
||||
|
||||
interface BrowserControllerType {
|
||||
cluster: Accessor<Cluster | undefined>;
|
||||
setCluster: Setter<Cluster | undefined>;
|
||||
|
|
@ -20,8 +22,8 @@ interface BrowserControllerType {
|
|||
displayClusterSelector: () => void;
|
||||
|
||||
search: () => void;
|
||||
searchQuery: Accessor<ProviderSearchQuery>;
|
||||
setSearchQuery: Setter<ProviderSearchQuery>;
|
||||
searchQuery: Accessor<ProviderSearchOptions>;
|
||||
setSearchQuery: Setter<ProviderSearchOptions>;
|
||||
|
||||
packageType: Accessor<PackageType>;
|
||||
setPackageType: Setter<PackageType>;
|
||||
|
|
@ -43,7 +45,8 @@ export function BrowserProvider(props: ParentProps) {
|
|||
// Active cluster that results should be "focused" on
|
||||
const [cluster, setCluster] = createSignal<Cluster>();
|
||||
|
||||
const [searchOptions, setSearchOptions] = createSignal<ProviderSearchQuery>({
|
||||
const [searchOptions, setSearchOptions] = createSignal<ProviderSearchOptions>({
|
||||
provider: 'Modrinth',
|
||||
query: '',
|
||||
limit: 20,
|
||||
offset: 0,
|
||||
|
|
@ -123,7 +126,7 @@ export function BrowserProvider(props: ParentProps) {
|
|||
createEffect(on(cluster, (cluster) => {
|
||||
if (cluster !== undefined)
|
||||
setSearchOptions((prev) => {
|
||||
const opts: ProviderSearchQuery = {
|
||||
const opts: ProviderSearchOptions = {
|
||||
...prev,
|
||||
game_versions: [cluster.meta.mc_version],
|
||||
};
|
||||
|
|
|
|||
|
|
@ -86,7 +86,9 @@ function usePagination(initialOptions: PaginationOptions) {
|
|||
<span>...</span>
|
||||
</Show>
|
||||
|
||||
<PaginationBtn page={totalPages()} />
|
||||
<Show when={totalPages() > 1}>
|
||||
<PaginationBtn page={totalPages()} />
|
||||
</Show>
|
||||
|
||||
<Button
|
||||
buttonStyle="iconSecondary"
|
||||
|
|
|
|||
|
|
@ -77,14 +77,14 @@ function BrowserPackage(props: ParentProps) {
|
|||
|
||||
return (
|
||||
<Spinner.Suspense>
|
||||
<div class="h-full flex flex-1 flex-row items-start gap-x-4 pb-8">
|
||||
<div class="h-full flex flex-1 flex-row items-start gap-x-4">
|
||||
<Show
|
||||
when={pkg() !== undefined && authors() !== undefined}
|
||||
children={(
|
||||
<>
|
||||
<BrowserSidebar package={pkg()!} authors={authors()!} />
|
||||
|
||||
<div class="min-h-full flex flex-1 flex-col items-start gap-y-4">
|
||||
<div class="min-h-full flex flex-1 flex-col items-start gap-y-4 pb-8">
|
||||
<div class="flex flex-none flex-row gap-x-1 rounded-lg bg-component-bg p-1">
|
||||
<For each={links}>
|
||||
{link => (
|
||||
|
|
@ -200,7 +200,7 @@ function BrowserSidebar(props: { package: ManagedPackage; authors: ManagedUser[]
|
|||
</div>
|
||||
<LinkExternal01Icon class="h-4 w-4" />
|
||||
</div>
|
||||
<Show when={author.is_organization_user === true}>
|
||||
<Show when={author.is_organization_user === true && props.authors.length > 1}>
|
||||
<div class="h-px w-full bg-gray-05" />
|
||||
</Show>
|
||||
</>
|
||||
|
|
|
|||
|
|
@ -8,6 +8,8 @@ import Button from '~ui/components/base/Button';
|
|||
import TextField from '~ui/components/base/TextField';
|
||||
import useBrowser from '~ui/hooks/useBrowser';
|
||||
import { browserCategories } from '~utils/browser';
|
||||
import Dropdown from '~ui/components/base/Dropdown';
|
||||
import { PROVIDERS } from '~utils';
|
||||
|
||||
function BrowserRoutes() {
|
||||
return (
|
||||
|
|
@ -88,23 +90,19 @@ function BrowserCategories() {
|
|||
<div class="top-0 grid grid-cols-[1fr_auto] h-fit min-w-50 gap-y-6">
|
||||
<div />
|
||||
<div class="flex flex-col gap-y-6">
|
||||
<For each={browserCategories.byPackageType(browser.packageType())}>
|
||||
{category => (
|
||||
<div class="flex flex-col gap-y-2">
|
||||
<h6 class="my-1">{category.name}</h6>
|
||||
<For each={category.sub}>
|
||||
{sub => (
|
||||
<p
|
||||
class={`text-md capitalize text-fg-primary hover:text-fg-primary-hover ${isEnabled(sub) ? 'text-opacity-100! hover:text-opacity-90!' : 'text-opacity-60! hover:text-opacity-70!'}`}
|
||||
onClick={() => toggleCategory(sub)}
|
||||
>
|
||||
{sub}
|
||||
</p>
|
||||
)}
|
||||
</For>
|
||||
</div>
|
||||
)}
|
||||
</For>
|
||||
<div class="flex flex-col gap-y-2">
|
||||
<h6 class="my-1">Categories</h6>
|
||||
<For each={browserCategories.byPackageType(browser.packageType())}>
|
||||
{category => (
|
||||
<p
|
||||
class={`text-md capitalize text-fg-primary hover:text-fg-primary-hover ${isEnabled(category.id) ? 'text-opacity-100! hover:text-opacity-90!' : 'text-opacity-60! hover:text-opacity-70!'}`}
|
||||
onClick={() => toggleCategory(category.id)}
|
||||
>
|
||||
{category.display}
|
||||
</p>
|
||||
)}
|
||||
</For>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
@ -115,7 +113,7 @@ function BrowserSidebar() {
|
|||
|
||||
return (
|
||||
<div class="flex flex-col gap-y-4">
|
||||
<div class="flex flex-col gap-y-1">
|
||||
<div class="flex flex-col gap-y-4">
|
||||
<div class="flex flex-col gap-y-1">
|
||||
<h6 class="my-1">Active Cluster</h6>
|
||||
<Button
|
||||
|
|
@ -125,6 +123,24 @@ function BrowserSidebar() {
|
|||
onClick={controller.displayClusterSelector}
|
||||
/>
|
||||
</div>
|
||||
<div class="flex flex-col gap-y-1">
|
||||
<h6 class="my-1">Provider</h6>
|
||||
<Dropdown
|
||||
selected={() => PROVIDERS.indexOf(controller.searchQuery().provider)}
|
||||
onChange={index => controller.setSearchQuery(prev => ({
|
||||
...prev,
|
||||
provider: PROVIDERS[index] || 'Modrinth',
|
||||
}))}
|
||||
>
|
||||
<For each={PROVIDERS}>
|
||||
{provider => (
|
||||
<Dropdown.Row>
|
||||
{provider}
|
||||
</Dropdown.Row>
|
||||
)}
|
||||
</For>
|
||||
</Dropdown>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -36,22 +36,25 @@ function BrowserSearch() {
|
|||
|
||||
return (
|
||||
<BrowserContent>
|
||||
<pagination.Navigation />
|
||||
<Spinner.Suspense>
|
||||
<Show when={results() !== undefined}>
|
||||
<SearchResultsContainer
|
||||
category="test"
|
||||
provider="Modrinth"
|
||||
results={results()!.results}
|
||||
header={(
|
||||
<div class="w-full flex flex-row items-end justify-between">
|
||||
<h5 class="ml-2">Results</h5>
|
||||
<pagination.Navigation />
|
||||
</div>
|
||||
)}
|
||||
/>
|
||||
</Show>
|
||||
|
||||
<div class="my-2">
|
||||
<Spinner.Suspense>
|
||||
<Show when={results() !== undefined}>
|
||||
<SearchResultsContainer
|
||||
category="test"
|
||||
header="Results"
|
||||
provider="Modrinth"
|
||||
results={results()!.results}
|
||||
/>
|
||||
</Show>
|
||||
</Spinner.Suspense>
|
||||
</div>
|
||||
|
||||
<pagination.Navigation />
|
||||
<div class="mt-2">
|
||||
<pagination.Navigation />
|
||||
</div>
|
||||
</Spinner.Suspense>
|
||||
</BrowserContent>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,53 +1,86 @@
|
|||
import type { BrowserListView, PackageType } from '@onelauncher/client/bindings';
|
||||
import type { BrowserListView, PackageType, Providers } from '@onelauncher/client/bindings';
|
||||
|
||||
export const BROWSER_VIEWS: BrowserListView[] = ['grid', 'list', 'preview'] as const;
|
||||
|
||||
export interface BrowserSidebarCategory {
|
||||
name: string;
|
||||
sub: string[];
|
||||
export interface CategoryItem {
|
||||
display: string;
|
||||
id: string;
|
||||
}
|
||||
|
||||
type ModCategories = [
|
||||
'adventure',
|
||||
'library',
|
||||
'equipment',
|
||||
'decoration',
|
||||
'food',
|
||||
'magic',
|
||||
'performance',
|
||||
'storage',
|
||||
'technology',
|
||||
'utility',
|
||||
'worldgen',
|
||||
];
|
||||
|
||||
const modMapping: Record<Providers, { [key in ModCategories[number]]: CategoryItem }> = {
|
||||
Modrinth: {
|
||||
adventure: {
|
||||
display: 'Adventure',
|
||||
id: 'adventure',
|
||||
},
|
||||
library: {
|
||||
display: 'Library',
|
||||
id: 'library',
|
||||
},
|
||||
decoration: {
|
||||
display: 'Decoration',
|
||||
id: 'decoration',
|
||||
},
|
||||
equipment: {
|
||||
display: 'Equipment',
|
||||
id: 'equipment',
|
||||
},
|
||||
food: {
|
||||
display: 'Food',
|
||||
id: 'food',
|
||||
},
|
||||
magic: {
|
||||
display: 'Magic',
|
||||
id: 'magic',
|
||||
},
|
||||
performance: {
|
||||
display: 'Performance',
|
||||
id: 'optimization',
|
||||
},
|
||||
storage: {
|
||||
display: 'Storage',
|
||||
id: 'storage',
|
||||
},
|
||||
technology: {
|
||||
display: 'Technology',
|
||||
id: 'technology',
|
||||
},
|
||||
utility: {
|
||||
display: 'Utility',
|
||||
id: 'utility',
|
||||
},
|
||||
worldgen: {
|
||||
display: 'World Generation',
|
||||
id: 'worldgen',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const browserCategories = {
|
||||
byPackageType(packageType: PackageType): BrowserSidebarCategory[] {
|
||||
byPackageType(packageType: PackageType): CategoryItem[] {
|
||||
switch (packageType) {
|
||||
case 'mod':
|
||||
return this.mod();
|
||||
case 'datapack':
|
||||
return this.datapack();
|
||||
case 'resourcepack':
|
||||
return this.resourcepack();
|
||||
case 'shaderpack':
|
||||
return this.shaderpack();
|
||||
}
|
||||
|
||||
return [];
|
||||
},
|
||||
|
||||
mod(): BrowserSidebarCategory[] {
|
||||
return [
|
||||
{
|
||||
name: 'Categories',
|
||||
sub: [
|
||||
'adventure',
|
||||
'magic',
|
||||
'technology',
|
||||
'utility',
|
||||
'worldgen',
|
||||
'miscellaneous',
|
||||
],
|
||||
},
|
||||
];
|
||||
},
|
||||
|
||||
datapack(): BrowserSidebarCategory[] {
|
||||
return [];
|
||||
},
|
||||
|
||||
resourcepack(): BrowserSidebarCategory[] {
|
||||
return [];
|
||||
},
|
||||
|
||||
shaderpack(): BrowserSidebarCategory[] {
|
||||
return [];
|
||||
mod(provider: Providers = 'Modrinth'): CategoryItem[] {
|
||||
return Object.values(modMapping[provider]);
|
||||
},
|
||||
};
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue