feat: Added working search filters
This commit is contained in:
parent
5012f2c3a6
commit
3025502d82
14 changed files with 172 additions and 131 deletions
|
|
@ -55,6 +55,7 @@ pub async fn get_provider_package_version(
|
||||||
pub struct ProviderSearchQuery {
|
pub struct ProviderSearchQuery {
|
||||||
query: Option<String>,
|
query: Option<String>,
|
||||||
limit: Option<u8>,
|
limit: Option<u8>,
|
||||||
|
offset: Option<u32>,
|
||||||
game_versions: Option<Vec<String>>,
|
game_versions: Option<Vec<String>>,
|
||||||
categories: Option<Vec<String>>,
|
categories: Option<Vec<String>>,
|
||||||
loaders: Option<Vec<Loader>>,
|
loaders: Option<Vec<Loader>>,
|
||||||
|
|
@ -69,6 +70,7 @@ pub async fn search_provider_packages(provider: Providers, query: ProviderSearch
|
||||||
.search(
|
.search(
|
||||||
query.query,
|
query.query,
|
||||||
query.limit,
|
query.limit,
|
||||||
|
query.offset,
|
||||||
query.game_versions,
|
query.game_versions,
|
||||||
query.categories,
|
query.categories,
|
||||||
query.loaders,
|
query.loaders,
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import type { ProviderSearchResults, Providers, SearchResult } from '@onelauncher/client/bindings';
|
import type { Providers, SearchResult } from '@onelauncher/client/bindings';
|
||||||
import { ChevronDownIcon, Download01Icon, HeartIcon } from '@untitled-theme/icons-solid';
|
import { ChevronDownIcon, Download01Icon, HeartIcon } from '@untitled-theme/icons-solid';
|
||||||
import { For, Match, Show, Switch, createSignal } from 'solid-js';
|
import { For, Match, Show, Switch, createSignal } from 'solid-js';
|
||||||
import Button from '../base/Button';
|
import Button from '../base/Button';
|
||||||
|
|
@ -6,11 +6,13 @@ import useBrowser from '~ui/hooks/useBrowser';
|
||||||
import useSettings from '~ui/hooks/useSettings';
|
import useSettings from '~ui/hooks/useSettings';
|
||||||
import { abbreviateNumber } from '~utils';
|
import { abbreviateNumber } from '~utils';
|
||||||
|
|
||||||
type SearchResultsContainerProps = ProviderSearchResults & {
|
interface SearchResultsContainerProps {
|
||||||
|
provider: Providers;
|
||||||
|
results: SearchResult[];
|
||||||
header: string;
|
header: string;
|
||||||
category: string;
|
category: string;
|
||||||
collapsable?: boolean;
|
collapsable?: boolean;
|
||||||
};
|
}
|
||||||
|
|
||||||
function SearchResultsContainer(props: SearchResultsContainerProps) {
|
function SearchResultsContainer(props: SearchResultsContainerProps) {
|
||||||
const { settings } = useSettings();
|
const { settings } = useSettings();
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { useNavigate } from '@solidjs/router';
|
import { useNavigate } from '@solidjs/router';
|
||||||
import { type Accessor, type Context, For, type ParentProps, type Setter, Show, createContext, createEffect, createSignal, onMount, untrack, useContext } from 'solid-js';
|
import { type Accessor, type Context, For, type ParentProps, type Setter, Show, createContext, createEffect, createSignal, on, onMount, untrack, useContext } from 'solid-js';
|
||||||
import type { Cluster, ManagedPackage, PackageType, ProviderSearchQuery, ProviderSearchResults, Providers } from '@onelauncher/client/bindings';
|
import type { Cluster, ManagedPackage, PackageType, ProviderSearchQuery, ProviderSearchResults, Providers } from '@onelauncher/client/bindings';
|
||||||
import useCommand, { tryResult } from './useCommand';
|
import useCommand, { tryResult } from './useCommand';
|
||||||
import { useRecentCluster } from './useCluster';
|
import { useRecentCluster } from './useCluster';
|
||||||
|
|
@ -46,6 +46,7 @@ export function BrowserProvider(props: ParentProps) {
|
||||||
const [searchOptions, setSearchOptions] = createSignal<ProviderSearchQuery>({
|
const [searchOptions, setSearchOptions] = createSignal<ProviderSearchQuery>({
|
||||||
query: '',
|
query: '',
|
||||||
limit: 20,
|
limit: 20,
|
||||||
|
offset: 0,
|
||||||
categories: null,
|
categories: null,
|
||||||
game_versions: null,
|
game_versions: null,
|
||||||
loaders: null,
|
loaders: null,
|
||||||
|
|
@ -119,6 +120,21 @@ export function BrowserProvider(props: ParentProps) {
|
||||||
setCluster(recentCluster());
|
setCluster(recentCluster());
|
||||||
});
|
});
|
||||||
|
|
||||||
|
createEffect(on(cluster, (cluster) => {
|
||||||
|
if (cluster !== undefined)
|
||||||
|
setSearchOptions((prev) => {
|
||||||
|
const opts: ProviderSearchQuery = {
|
||||||
|
...prev,
|
||||||
|
game_versions: [cluster.meta.mc_version],
|
||||||
|
};
|
||||||
|
|
||||||
|
if (cluster.meta.loader !== undefined)
|
||||||
|
opts.loaders = [cluster.meta.loader];
|
||||||
|
|
||||||
|
return opts;
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<BrowserContext.Provider value={controller}>
|
<BrowserContext.Provider value={controller}>
|
||||||
{props.children}
|
{props.children}
|
||||||
|
|
|
||||||
|
|
@ -2,26 +2,34 @@ import { ChevronLeftIcon, ChevronRightIcon } from '@untitled-theme/icons-solid';
|
||||||
import { For, Show, createSignal } from 'solid-js';
|
import { For, Show, createSignal } from 'solid-js';
|
||||||
import Button from '~ui/components/base/Button';
|
import Button from '~ui/components/base/Button';
|
||||||
|
|
||||||
interface PaginationOptions {
|
export interface PaginationOptions {
|
||||||
itemsCount: () => number;
|
itemsCount: () => number;
|
||||||
itemsPerPage: () => number;
|
itemsPerPage: () => number;
|
||||||
initialPage?: () => number;
|
initialPage?: () => number;
|
||||||
};
|
};
|
||||||
|
|
||||||
function usePagination(options: PaginationOptions) {
|
function usePagination(initialOptions: PaginationOptions) {
|
||||||
const [page, setPage] = createSignal(Math.max(1, options.initialPage?.() || 1));
|
const [options, setOptions] = createSignal(initialOptions);
|
||||||
|
|
||||||
|
// eslint-disable-next-line solid/reactivity -- -
|
||||||
|
const [page, setPage] = createSignal(Math.max(1, options().initialPage?.() || 1));
|
||||||
|
|
||||||
const next = () => setPage(page => page + 1);
|
const next = () => setPage(page => page + 1);
|
||||||
const prev = () => setPage(page => page - 1);
|
const prev = () => setPage(page => page - 1);
|
||||||
|
|
||||||
const totalPages = () => Math.ceil(options.itemsCount() / options.itemsPerPage());
|
const totalPages = () => Math.ceil(options().itemsCount() / options().itemsPerPage());
|
||||||
|
|
||||||
const hasNext = () => page() < totalPages();
|
const hasNext = () => page() < totalPages();
|
||||||
const hasPrev = () => page() > 1;
|
const hasPrev = () => page() > 1;
|
||||||
|
|
||||||
const offset = () => (page() - 1) * options.itemsPerPage();
|
const offset = () => (page() - 1) * options().itemsPerPage();
|
||||||
|
|
||||||
|
// Exported
|
||||||
|
function reset(options: PaginationOptions) {
|
||||||
|
setOptions(options);
|
||||||
|
setPage(1);
|
||||||
|
}
|
||||||
|
|
||||||
// Navigation
|
|
||||||
function getVisiblePageNumbers(page: number) {
|
function getVisiblePageNumbers(page: number) {
|
||||||
const pages: number[] = [];
|
const pages: number[] = [];
|
||||||
|
|
||||||
|
|
@ -97,6 +105,7 @@ function usePagination(options: PaginationOptions) {
|
||||||
hasPrev,
|
hasPrev,
|
||||||
totalPages,
|
totalPages,
|
||||||
offset,
|
offset,
|
||||||
|
reset,
|
||||||
Navigation,
|
Navigation,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,23 +0,0 @@
|
||||||
import { useSearchParams } from '@solidjs/router';
|
|
||||||
import ScrollableContainer from '~ui/components/ScrollableContainer';
|
|
||||||
|
|
||||||
function BrowserCategory() {
|
|
||||||
const [params] = useSearchParams<{
|
|
||||||
category: string;
|
|
||||||
}>();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<h1>
|
|
||||||
Category =
|
|
||||||
{' '}
|
|
||||||
{params.category}
|
|
||||||
</h1>
|
|
||||||
<ScrollableContainer>
|
|
||||||
<span>test</span>
|
|
||||||
</ScrollableContainer>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default BrowserCategory;
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { ChevronRightIcon } from '@untitled-theme/icons-solid';
|
import { ChevronRightIcon } from '@untitled-theme/icons-solid';
|
||||||
import { Show } from 'solid-js';
|
import { Show, onMount } from 'solid-js';
|
||||||
import type { ManagedPackage } from '@onelauncher/client/bindings';
|
import type { ManagedPackage } from '@onelauncher/client/bindings';
|
||||||
import { BrowserContent } from './BrowserRoot';
|
import { BrowserContent } from './BrowserRoot';
|
||||||
import useBrowser from '~ui/hooks/useBrowser';
|
import useBrowser from '~ui/hooks/useBrowser';
|
||||||
|
|
@ -10,6 +10,13 @@ import SearchResultsContainer from '~ui/components/content/SearchResults';
|
||||||
function BrowserMain() {
|
function BrowserMain() {
|
||||||
const browser = useBrowser();
|
const browser = useBrowser();
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
browser.setSearchQuery(prev => ({
|
||||||
|
...prev,
|
||||||
|
categories: [],
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<BrowserContent>
|
<BrowserContent>
|
||||||
<div class="flex flex-col gap-8">
|
<div class="flex flex-col gap-8">
|
||||||
|
|
|
||||||
|
|
@ -145,6 +145,9 @@ function BrowserSidebar(props: { package: ManagedPackage; authors: ManagedUser[]
|
||||||
{' '}
|
{' '}
|
||||||
<span class="text-fg-primary">{props.package.provider}</span>
|
<span class="text-fg-primary">{props.package.provider}</span>
|
||||||
</p>
|
</p>
|
||||||
|
<Show when={props.package.is_archived === true}>
|
||||||
|
<p class="w-fit rounded-full bg-code-warn/10 px-2 py-1 text-xs text-code-warn">Archived</p>
|
||||||
|
</Show>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p class="max-h-22 flex-1 overflow-hidden text-sm text-fg-secondary line-height-snug">{props.package.description}</p>
|
<p class="max-h-22 flex-1 overflow-hidden text-sm text-fg-secondary line-height-snug">{props.package.description}</p>
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,7 @@
|
||||||
import { Route } from '@solidjs/router';
|
import { Route, useLocation } from '@solidjs/router';
|
||||||
import { For, type JSX, type ParentProps } from 'solid-js';
|
import { For, type JSX, type ParentProps } from 'solid-js';
|
||||||
import { SearchMdIcon, Settings01Icon } from '@untitled-theme/icons-solid';
|
import { SearchMdIcon, Settings01Icon } from '@untitled-theme/icons-solid';
|
||||||
import type { PackageType } from '@onelauncher/client/bindings';
|
|
||||||
import BrowserMain from './BrowserMain';
|
import BrowserMain from './BrowserMain';
|
||||||
import BrowserCategory from './BrowserCategory';
|
|
||||||
import BrowserPackage from './BrowserPackage';
|
import BrowserPackage from './BrowserPackage';
|
||||||
import BrowserSearch from './BrowserSearch';
|
import BrowserSearch from './BrowserSearch';
|
||||||
import Button from '~ui/components/base/Button';
|
import Button from '~ui/components/base/Button';
|
||||||
|
|
@ -15,7 +13,6 @@ function BrowserRoutes() {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Route path="/" component={BrowserMain} />
|
<Route path="/" component={BrowserMain} />
|
||||||
<Route path="/category" component={BrowserCategory} />
|
|
||||||
<Route path="/package" component={BrowserPackage} children={<BrowserPackage.Routes />} />
|
<Route path="/package" component={BrowserPackage} children={<BrowserPackage.Routes />} />
|
||||||
<Route path="/search" component={BrowserSearch} />
|
<Route path="/search" component={BrowserSearch} />
|
||||||
</>
|
</>
|
||||||
|
|
@ -60,27 +57,49 @@ function BrowserToolbar() {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface BrowserSidebarCategory {
|
function BrowserCategories() {
|
||||||
name: string;
|
const browser = useBrowser();
|
||||||
sub: [string, string][];
|
const location = useLocation();
|
||||||
};
|
|
||||||
|
const getEnabledCategories = () => browser.searchQuery().categories || [];
|
||||||
|
|
||||||
|
const isEnabled = (category: string) => {
|
||||||
|
return getEnabledCategories().includes(category);
|
||||||
|
};
|
||||||
|
|
||||||
|
const toggleCategory = (category: string) => {
|
||||||
|
const enabledCategories = getEnabledCategories();
|
||||||
|
const index = enabledCategories.indexOf(category);
|
||||||
|
if (index > -1)
|
||||||
|
enabledCategories.splice(index, 1);
|
||||||
|
else
|
||||||
|
enabledCategories.push(category);
|
||||||
|
|
||||||
|
browser.setSearchQuery(prev => ({
|
||||||
|
...prev,
|
||||||
|
categories: enabledCategories,
|
||||||
|
}));
|
||||||
|
|
||||||
|
if (!location.pathname.includes('/search'))
|
||||||
|
browser.search();
|
||||||
|
};
|
||||||
|
|
||||||
function BrowserCategories(props: { categories: BrowserSidebarCategory[] }) {
|
|
||||||
return (
|
return (
|
||||||
<div class="top-0 grid grid-cols-[1fr_auto] h-fit min-w-50 gap-y-6">
|
<div class="top-0 grid grid-cols-[1fr_auto] h-fit min-w-50 gap-y-6">
|
||||||
<div />
|
<div />
|
||||||
<div class="flex flex-col gap-y-6">
|
<div class="flex flex-col gap-y-6">
|
||||||
<For each={props.categories.concat(browserCategories.provider())}>
|
<For each={browserCategories.byPackageType(browser.packageType())}>
|
||||||
{category => (
|
{category => (
|
||||||
<div class="flex flex-col gap-y-2">
|
<div class="flex flex-col gap-y-2">
|
||||||
<h6 class="my-1">{category.name}</h6>
|
<h6 class="my-1">{category.name}</h6>
|
||||||
<For each={category.sub}>
|
<For each={category.sub}>
|
||||||
{sub => (
|
{sub => (
|
||||||
<a
|
<p
|
||||||
href={sub[1]}
|
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!'}`}
|
||||||
class="text-md text-fg-primary capitalize hover:text-fg-secondary"
|
onClick={() => toggleCategory(sub)}
|
||||||
children={sub[0]}
|
>
|
||||||
/>
|
{sub}
|
||||||
|
</p>
|
||||||
)}
|
)}
|
||||||
</For>
|
</For>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -97,31 +116,21 @@ function BrowserSidebar() {
|
||||||
return (
|
return (
|
||||||
<div class="flex flex-col gap-y-4">
|
<div class="flex flex-col gap-y-4">
|
||||||
<div class="flex flex-col gap-y-1">
|
<div class="flex flex-col gap-y-1">
|
||||||
<h6 class="my-1">Active Cluster</h6>
|
<div class="flex flex-col gap-y-1">
|
||||||
<Button
|
<h6 class="my-1">Active Cluster</h6>
|
||||||
buttonStyle="secondary"
|
<Button
|
||||||
children={controller.cluster()?.meta.name || 'None'}
|
buttonStyle="secondary"
|
||||||
iconLeft={<Settings01Icon />}
|
children={controller.cluster()?.meta.name || 'None'}
|
||||||
onClick={controller.displayClusterSelector}
|
iconLeft={<Settings01Icon />}
|
||||||
/>
|
onClick={controller.displayClusterSelector}
|
||||||
</div>
|
/>
|
||||||
|
</div>
|
||||||
<div class="flex flex-col gap-y-1">
|
|
||||||
<h6 class="my-1">Search Filters</h6>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export type BrowserContentProps = ParentProps & {
|
export function BrowserContent(props: ParentProps) {
|
||||||
categories?: BrowserSidebarCategory[];
|
|
||||||
};
|
|
||||||
|
|
||||||
const categories = (packageType: PackageType) => browserCategories.byPackageType(packageType);
|
|
||||||
|
|
||||||
export function BrowserContent(props: BrowserContentProps) {
|
|
||||||
const browser = useBrowser();
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div class="relative h-full flex flex-1 flex-col items-center gap-2">
|
<div class="relative h-full flex flex-1 flex-col items-center gap-2">
|
||||||
<div class="h-full w-full max-w-screen-xl flex flex-1 flex-col items-center gap-y-2">
|
<div class="h-full w-full max-w-screen-xl flex flex-1 flex-col items-center gap-y-2">
|
||||||
|
|
@ -132,7 +141,7 @@ export function BrowserContent(props: BrowserContentProps) {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="grid grid-cols-[220px_auto_220px] w-full max-w-screen-xl gap-x-6 pb-8">
|
<div class="grid grid-cols-[220px_auto_220px] w-full max-w-screen-xl gap-x-6 pb-8">
|
||||||
<BrowserCategories categories={categories(browser.packageType()).concat(props.categories || [])} />
|
<BrowserCategories />
|
||||||
|
|
||||||
<div class="h-full flex flex-col gap-y-4">
|
<div class="h-full flex flex-col gap-y-4">
|
||||||
<div class="h-full flex-1">
|
<div class="h-full flex-1">
|
||||||
|
|
|
||||||
|
|
@ -5,28 +5,53 @@ import SearchResultsContainer from '~ui/components/content/SearchResults';
|
||||||
import Spinner from '~ui/components/Spinner';
|
import Spinner from '~ui/components/Spinner';
|
||||||
import useBrowser from '~ui/hooks/useBrowser';
|
import useBrowser from '~ui/hooks/useBrowser';
|
||||||
import useCommand from '~ui/hooks/useCommand';
|
import useCommand from '~ui/hooks/useCommand';
|
||||||
|
import usePagination, { type PaginationOptions } from '~ui/hooks/usePagination';
|
||||||
|
|
||||||
function BrowserSearch() {
|
function BrowserSearch() {
|
||||||
const browser = useBrowser();
|
const browser = useBrowser();
|
||||||
|
|
||||||
const [results, { refetch }] = useCommand(() => bridge.commands.searchProviderPackages('Modrinth', browser.searchQuery()));
|
const [results, { refetch }] = useCommand(() => bridge.commands.searchProviderPackages('Modrinth', browser.searchQuery()));
|
||||||
|
|
||||||
createEffect(on(() => browser.searchQuery().query, () => {
|
const paginationOptions = (): PaginationOptions => ({
|
||||||
|
itemsCount: () => results()?.total || 0,
|
||||||
|
itemsPerPage: () => browser.searchQuery().limit || 20,
|
||||||
|
});
|
||||||
|
|
||||||
|
const pagination = usePagination(paginationOptions());
|
||||||
|
|
||||||
|
createEffect(on(browser.searchQuery, () => {
|
||||||
refetch();
|
refetch();
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
createEffect(on(() => results()?.total, (curr, prev) => {
|
||||||
|
if (curr !== prev)
|
||||||
|
pagination.reset(paginationOptions());
|
||||||
|
}));
|
||||||
|
|
||||||
|
createEffect(on(pagination.page, () => {
|
||||||
|
browser.setSearchQuery(prev => ({
|
||||||
|
...prev,
|
||||||
|
offset: pagination.offset(),
|
||||||
|
}));
|
||||||
|
}));
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<BrowserContent>
|
<BrowserContent>
|
||||||
<Spinner.Suspense>
|
<pagination.Navigation />
|
||||||
<Show when={results() !== undefined}>
|
|
||||||
<SearchResultsContainer
|
<div class="my-2">
|
||||||
category="test"
|
<Spinner.Suspense>
|
||||||
header="Results"
|
<Show when={results() !== undefined}>
|
||||||
provider="Modrinth"
|
<SearchResultsContainer
|
||||||
results={results()!.results}
|
category="test"
|
||||||
/>
|
header="Results"
|
||||||
</Show>
|
provider="Modrinth"
|
||||||
</Spinner.Suspense>
|
results={results()!.results}
|
||||||
|
/>
|
||||||
|
</Show>
|
||||||
|
</Spinner.Suspense>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<pagination.Navigation />
|
||||||
</BrowserContent>
|
</BrowserContent>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,12 @@
|
||||||
import type { BrowserListView, PackageType } from '@onelauncher/client/bindings';
|
import type { BrowserListView, PackageType } from '@onelauncher/client/bindings';
|
||||||
import type { BrowserSidebarCategory } from '~ui/pages/browser/BrowserRoot';
|
|
||||||
import { LOADERS, PROVIDERS } from '~utils';
|
|
||||||
|
|
||||||
export const BROWSER_VIEWS: BrowserListView[] = ['grid', 'list', 'preview'] as const;
|
export const BROWSER_VIEWS: BrowserListView[] = ['grid', 'list', 'preview'] as const;
|
||||||
|
|
||||||
|
export interface BrowserSidebarCategory {
|
||||||
|
name: string;
|
||||||
|
sub: string[];
|
||||||
|
};
|
||||||
|
|
||||||
export const browserCategories = {
|
export const browserCategories = {
|
||||||
byPackageType(packageType: PackageType): BrowserSidebarCategory[] {
|
byPackageType(packageType: PackageType): BrowserSidebarCategory[] {
|
||||||
switch (packageType) {
|
switch (packageType) {
|
||||||
|
|
@ -16,6 +19,8 @@ export const browserCategories = {
|
||||||
case 'shaderpack':
|
case 'shaderpack':
|
||||||
return this.shaderpack();
|
return this.shaderpack();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return [];
|
||||||
},
|
},
|
||||||
|
|
||||||
mod(): BrowserSidebarCategory[] {
|
mod(): BrowserSidebarCategory[] {
|
||||||
|
|
@ -23,15 +28,14 @@ export const browserCategories = {
|
||||||
{
|
{
|
||||||
name: 'Categories',
|
name: 'Categories',
|
||||||
sub: [
|
sub: [
|
||||||
['Adventure', '/adventure'],
|
'adventure',
|
||||||
['Magic', '/magic'],
|
'magic',
|
||||||
['Technology', '/technology'],
|
'technology',
|
||||||
['Utility', '/utility'],
|
'utility',
|
||||||
['Worldgen', '/worldgen'],
|
'worldgen',
|
||||||
['Miscellaneous', '/miscellaneous'],
|
'miscellaneous',
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
...this.loader(false),
|
|
||||||
];
|
];
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
@ -46,31 +50,4 @@ export const browserCategories = {
|
||||||
shaderpack(): BrowserSidebarCategory[] {
|
shaderpack(): BrowserSidebarCategory[] {
|
||||||
return [];
|
return [];
|
||||||
},
|
},
|
||||||
|
|
||||||
provider(): BrowserSidebarCategory[] {
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
name: 'Providers',
|
|
||||||
sub: PROVIDERS.map(provider => [provider, `/${provider.toLowerCase()}`]),
|
|
||||||
},
|
|
||||||
];
|
|
||||||
},
|
|
||||||
|
|
||||||
loader(vanilla: boolean = false): BrowserSidebarCategory[] {
|
|
||||||
const loaders: [string, string][] = [];
|
|
||||||
|
|
||||||
for (const loader of LOADERS) {
|
|
||||||
if (vanilla === false && loader === 'vanilla')
|
|
||||||
continue;
|
|
||||||
|
|
||||||
loaders.push([loader, `/${loader.toLowerCase()}`]);
|
|
||||||
}
|
|
||||||
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
name: 'Loaders',
|
|
||||||
sub: loaders,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ async fn main() -> Result<()> {
|
||||||
let results = provider.search(
|
let results = provider.search(
|
||||||
Some("chatting".to_string()),
|
Some("chatting".to_string()),
|
||||||
Some(5),
|
Some(5),
|
||||||
|
Some(0),
|
||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
|
|
|
||||||
|
|
@ -44,6 +44,7 @@ impl Providers {
|
||||||
&self,
|
&self,
|
||||||
query: Option<String>,
|
query: Option<String>,
|
||||||
limit: Option<u8>,
|
limit: Option<u8>,
|
||||||
|
offset: Option<u32>,
|
||||||
game_versions: Option<Vec<String>>,
|
game_versions: Option<Vec<String>>,
|
||||||
categories: Option<Vec<String>>,
|
categories: Option<Vec<String>>,
|
||||||
loaders: Option<Vec<Loader>>,
|
loaders: Option<Vec<Loader>>,
|
||||||
|
|
@ -54,6 +55,7 @@ impl Providers {
|
||||||
Providers::Modrinth => modrinth::search(
|
Providers::Modrinth => modrinth::search(
|
||||||
query,
|
query,
|
||||||
limit,
|
limit,
|
||||||
|
offset,
|
||||||
Some(|mut builder: FacetBuilder| {
|
Some(|mut builder: FacetBuilder| {
|
||||||
if let Some(game_versions) = game_versions {
|
if let Some(game_versions) = game_versions {
|
||||||
for version in game_versions {
|
for version in game_versions {
|
||||||
|
|
@ -63,7 +65,7 @@ impl Providers {
|
||||||
|
|
||||||
if let Some(categories) = categories {
|
if let Some(categories) = categories {
|
||||||
for category in categories {
|
for category in categories {
|
||||||
builder.or(Facet(
|
builder.and(Facet(
|
||||||
"categories".to_string(),
|
"categories".to_string(),
|
||||||
FacetOperation::Eq,
|
FacetOperation::Eq,
|
||||||
category,
|
category,
|
||||||
|
|
@ -73,7 +75,7 @@ impl Providers {
|
||||||
|
|
||||||
if let Some(package_types) = package_types {
|
if let Some(package_types) = package_types {
|
||||||
for package_type in package_types {
|
for package_type in package_types {
|
||||||
builder.or(Facet(
|
builder.and(Facet(
|
||||||
"project_types".to_string(),
|
"project_types".to_string(),
|
||||||
FacetOperation::Eq,
|
FacetOperation::Eq,
|
||||||
package_type.get_name().to_string(),
|
package_type.get_name().to_string(),
|
||||||
|
|
|
||||||
|
|
@ -36,8 +36,7 @@ pub struct ModrinthPackage {
|
||||||
// pub donation_urls: Vec<DonationUrl>,
|
// pub donation_urls: Vec<DonationUrl>,
|
||||||
pub project_type: PackageType,
|
pub project_type: PackageType,
|
||||||
pub downloads: u32,
|
pub downloads: u32,
|
||||||
#[serde(default)]
|
pub icon_url: Option<String>,
|
||||||
pub icon_url: String,
|
|
||||||
#[serde(alias = "project_id")]
|
#[serde(alias = "project_id")]
|
||||||
pub id: String,
|
pub id: String,
|
||||||
pub team: String,
|
pub team: String,
|
||||||
|
|
@ -57,6 +56,8 @@ pub struct ModrinthPackage {
|
||||||
pub license: Option<License>,
|
pub license: Option<License>,
|
||||||
// #[serde(default)]
|
// #[serde(default)]
|
||||||
// pub gallery: Vec<Gallery>,
|
// pub gallery: Vec<Gallery>,
|
||||||
|
#[serde(default)]
|
||||||
|
pub status: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<ModrinthPackage> for ManagedPackage {
|
impl From<ModrinthPackage> for ManagedPackage {
|
||||||
|
|
@ -71,7 +72,7 @@ impl From<ModrinthPackage> for ManagedPackage {
|
||||||
versions: value.versions,
|
versions: value.versions,
|
||||||
game_versions: value.game_versions,
|
game_versions: value.game_versions,
|
||||||
loaders: value.loaders,
|
loaders: value.loaders,
|
||||||
icon_url: Some(value.icon_url),
|
icon_url: value.icon_url,
|
||||||
created: value.published,
|
created: value.published,
|
||||||
updated: value.updated,
|
updated: value.updated,
|
||||||
client: value.client_side,
|
client: value.client_side,
|
||||||
|
|
@ -87,6 +88,7 @@ impl From<ModrinthPackage> for ManagedPackage {
|
||||||
team: value.team,
|
team: value.team,
|
||||||
organization: value.organization,
|
organization: value.organization,
|
||||||
},
|
},
|
||||||
|
is_archived: value.status == "archived",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -114,6 +116,7 @@ pub struct Gallery {
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
struct SearchResults {
|
struct SearchResults {
|
||||||
hits: Vec<SearchResult>,
|
hits: Vec<SearchResult>,
|
||||||
|
total_hits: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
|
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||||
|
|
@ -241,20 +244,23 @@ impl FacetBuilder {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn build(&self) -> String {
|
pub fn build(&self) -> String {
|
||||||
let mut builder = String::new();
|
let mut builder: Vec<String> = vec![];
|
||||||
|
|
||||||
for facet in &self.facets {
|
for facet in &self.facets {
|
||||||
builder.push('[');
|
let mut stringified = String::new();
|
||||||
|
stringified.push('[');
|
||||||
for (i, f) in facet.iter().enumerate() {
|
for (i, f) in facet.iter().enumerate() {
|
||||||
if i != 0 {
|
if i != 0 {
|
||||||
builder.push(',');
|
stringified.push(',');
|
||||||
}
|
}
|
||||||
builder.push_str(format!("\"{}\"", f.to_string()).as_str());
|
stringified.push_str(format!("\"{}\"", f.to_string()).as_str());
|
||||||
}
|
}
|
||||||
builder.push(']');
|
stringified.push(']');
|
||||||
|
|
||||||
|
builder.push(stringified);
|
||||||
}
|
}
|
||||||
|
|
||||||
builder
|
builder.join(",")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -306,6 +312,7 @@ pub async fn list() -> Result<Vec<ModrinthPackage>> {
|
||||||
pub async fn search<F>(
|
pub async fn search<F>(
|
||||||
query: Option<String>,
|
query: Option<String>,
|
||||||
limit: Option<u8>,
|
limit: Option<u8>,
|
||||||
|
offset: Option<u32>,
|
||||||
facets: Option<F>,
|
facets: Option<F>,
|
||||||
) -> Result<ProviderSearchResults>
|
) -> Result<ProviderSearchResults>
|
||||||
where
|
where
|
||||||
|
|
@ -316,9 +323,10 @@ where
|
||||||
let results: SearchResults = serde_json::from_slice(
|
let results: SearchResults = serde_json::from_slice(
|
||||||
&fetch(
|
&fetch(
|
||||||
format_url!(
|
format_url!(
|
||||||
"/search?query={}&limit={}{}",
|
"/search?query={}&limit={}&offset={}{}",
|
||||||
query.unwrap_or_default(),
|
query.unwrap_or_default(),
|
||||||
limit.unwrap_or(10),
|
limit.unwrap_or(10),
|
||||||
|
offset.unwrap_or(0),
|
||||||
if facets.is_empty() {
|
if facets.is_empty() {
|
||||||
"".to_string()
|
"".to_string()
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -335,6 +343,7 @@ where
|
||||||
Ok(ProviderSearchResults {
|
Ok(ProviderSearchResults {
|
||||||
provider: Providers::Modrinth,
|
provider: Providers::Modrinth,
|
||||||
results: results.hits,
|
results: results.hits,
|
||||||
|
total: results.total_hits,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -155,6 +155,7 @@ pub struct SearchResult {
|
||||||
pub struct ProviderSearchResults {
|
pub struct ProviderSearchResults {
|
||||||
pub provider: Providers,
|
pub provider: Providers,
|
||||||
pub results: Vec<SearchResult>,
|
pub results: Vec<SearchResult>,
|
||||||
|
pub total: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A struct that represents a Package.
|
/// A struct that represents a Package.
|
||||||
|
|
@ -587,8 +588,9 @@ pub struct ManagedPackage {
|
||||||
pub categories: Vec<String>,
|
pub categories: Vec<String>,
|
||||||
pub optional_categories: Option<Vec<String>>,
|
pub optional_categories: Option<Vec<String>>,
|
||||||
pub license: Option<License>,
|
pub license: Option<License>,
|
||||||
|
|
||||||
pub author: Author,
|
pub author: Author,
|
||||||
|
|
||||||
|
pub is_archived: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "specta", derive(specta::Type))]
|
#[cfg_attr(feature = "specta", derive(specta::Type))]
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue