From 5012f2c3a6c3ce20e652da0b647083778941cdd0 Mon Sep 17 00:00:00 2001 From: LynithDev <61880709+LynithDev@users.noreply.github.com> Date: Wed, 4 Sep 2024 22:03:37 +0200 Subject: [PATCH] feat: Browser Search sorta works, gotta implement filtering --- apps/desktop/src/api/commands/package.rs | 26 ++++++++------ .../src/ui/components/base/TextField.tsx | 18 +++++++--- apps/frontend/src/ui/hooks/useBrowser.tsx | 30 ++++++++++++++-- .../src/ui/pages/browser/BrowserMain.tsx | 8 ++++- .../src/ui/pages/browser/BrowserRoot.tsx | 30 ++++++++++------ .../src/ui/pages/browser/BrowserSearch.tsx | 34 +++++++++++++++++++ apps/testing/examples/search_packages.rs | 21 ++++++++++++ packages/core/src/api/package/content/mod.rs | 13 ++++++- .../core/src/api/package/content/modrinth.rs | 4 +-- packages/core/src/store/package.rs | 7 ++++ 10 files changed, 159 insertions(+), 32 deletions(-) create mode 100644 apps/frontend/src/ui/pages/browser/BrowserSearch.tsx create mode 100644 apps/testing/examples/search_packages.rs diff --git a/apps/desktop/src/api/commands/package.rs b/apps/desktop/src/api/commands/package.rs index 07cdb30c..cb2a3cf8 100644 --- a/apps/desktop/src/api/commands/package.rs +++ b/apps/desktop/src/api/commands/package.rs @@ -51,25 +51,29 @@ pub async fn get_provider_package_version( Ok(provider.get_version(&version).await?) } -#[specta::specta] -#[tauri::command] -pub async fn search_provider_packages( - provider: Providers, +#[derive(specta::Type, serde::Deserialize, serde::Serialize)] +pub struct ProviderSearchQuery { query: Option, limit: Option, game_versions: Option>, categories: Option>, loaders: Option>, + package_types: Option>, open_source: Option, -) -> Result { +} + +#[specta::specta] +#[tauri::command] +pub async fn search_provider_packages(provider: Providers, query: ProviderSearchQuery) -> Result { Ok(provider .search( - query, - limit, - game_versions, - categories, - loaders, - open_source, + query.query, + query.limit, + query.game_versions, + query.categories, + query.loaders, + query.package_types, + query.open_source, ) .await?) } diff --git a/apps/frontend/src/ui/components/base/TextField.tsx b/apps/frontend/src/ui/components/base/TextField.tsx index dfb72bd8..36745b6a 100644 --- a/apps/frontend/src/ui/components/base/TextField.tsx +++ b/apps/frontend/src/ui/components/base/TextField.tsx @@ -12,7 +12,7 @@ type TextFieldProps = { } & JSX.InputHTMLAttributes; function TextField(props: TextFieldProps) { - const [split, rest] = splitProps(props, ['iconLeft', 'iconRight', 'inputFilter', 'onValidInput', 'onValidSubmit', 'labelClass']); + const [split, rest] = splitProps(props, ['iconLeft', 'iconRight', 'inputFilter', 'onValidInput', 'onValidSubmit', 'labelClass', 'onInput', 'onChange']); const [isValid, setIsValid] = createSignal(true); const id = createUniqueId(); @@ -30,9 +30,19 @@ function TextField(props: TextFieldProps) { split.onValidInput(value); } - function onSubmit() { + function onInput(e: Event) { + validate(); + + // @ts-expect-error -- I do not care about the event types anymore + split.onInput?.(e); + } + + function onChange(e: Event) { if (isValid() && split.onValidSubmit) split.onValidSubmit(ref.value); + + // @ts-expect-error -- I do not care about the event types anymore + split.onChange?.(e); } onMount(() => { @@ -46,8 +56,8 @@ function TextField(props: TextFieldProps) { id={id} type="text" ref={ref} - onInput={validate} - onChange={onSubmit} + onInput={onInput} + onChange={onChange} {...rest} /> {split.iconRight && {split.iconRight}} diff --git a/apps/frontend/src/ui/hooks/useBrowser.tsx b/apps/frontend/src/ui/hooks/useBrowser.tsx index 7c8eacd8..43fe3c4f 100644 --- a/apps/frontend/src/ui/hooks/useBrowser.tsx +++ b/apps/frontend/src/ui/hooks/useBrowser.tsx @@ -1,6 +1,6 @@ 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 { Cluster, ManagedPackage, PackageType, ProviderSearchResults, Providers } from '@onelauncher/client/bindings'; +import type { Cluster, ManagedPackage, PackageType, ProviderSearchQuery, ProviderSearchResults, Providers } from '@onelauncher/client/bindings'; import useCommand, { tryResult } from './useCommand'; import { useRecentCluster } from './useCluster'; import { bridge } from '~imports'; @@ -19,6 +19,10 @@ interface BrowserControllerType { displayCategory: (category: string) => void; displayClusterSelector: () => void; + search: () => void; + searchQuery: Accessor; + setSearchQuery: Setter; + packageType: Accessor; setPackageType: Setter; @@ -33,11 +37,24 @@ export function BrowserProvider(props: ParentProps) { const [mainPageCache, setMainPageCache] = createSignal(); const [featured, setFeatured] = createSignal(); + // Used for the current "Browser Mode". It'll only show packages of the selected type const [packageType, setPackageType] = createSignal('mod'); + // Active cluster that results should be "focused" on const [cluster, setCluster] = createSignal(); - const recentCluster = useRecentCluster(); + + const [searchOptions, setSearchOptions] = createSignal({ + query: '', + limit: 20, + categories: null, + game_versions: null, + loaders: null, + package_types: ['mod'], + open_source: null, + }); + const navigate = useNavigate(); + const recentCluster = useRecentCluster(); const modal = createModal(props => ( { + navigate('/browser/search'); + }, + searchQuery: searchOptions, + setSearchQuery: setSearchOptions, + packageType, setPackageType, async refreshCache() { - const res = await tryResult(() => bridge.commands.searchProviderPackages('Modrinth', null, 10, null, null, null, null)); + const opts = untrack(searchOptions); + const res = await tryResult(() => bridge.commands.searchProviderPackages('Modrinth', opts)); setMainPageCache(res); diff --git a/apps/frontend/src/ui/pages/browser/BrowserMain.tsx b/apps/frontend/src/ui/pages/browser/BrowserMain.tsx index 3cdbdea2..74b545d3 100644 --- a/apps/frontend/src/ui/pages/browser/BrowserMain.tsx +++ b/apps/frontend/src/ui/pages/browser/BrowserMain.tsx @@ -33,6 +33,12 @@ interface FeaturedProps { } function Featured(props: FeaturedProps) { + const browser = useBrowser(); + + const open = () => { + browser.displayPackage(props.package.id, props.package.provider); + }; + return (
Featured
@@ -55,7 +61,7 @@ function Featured(props: FeaturedProps) { buttonStyle="ghost" iconRight={} children="View Package" - onClick={() => { }} + onClick={open} />
diff --git a/apps/frontend/src/ui/pages/browser/BrowserRoot.tsx b/apps/frontend/src/ui/pages/browser/BrowserRoot.tsx index 12776c73..896c4bea 100644 --- a/apps/frontend/src/ui/pages/browser/BrowserRoot.tsx +++ b/apps/frontend/src/ui/pages/browser/BrowserRoot.tsx @@ -1,10 +1,11 @@ import { Route } from '@solidjs/router'; -import { For, type ParentProps } from 'solid-js'; +import { For, type JSX, type ParentProps } from 'solid-js'; import { SearchMdIcon, Settings01Icon } from '@untitled-theme/icons-solid'; import type { PackageType } from '@onelauncher/client/bindings'; import BrowserMain from './BrowserMain'; import BrowserCategory from './BrowserCategory'; import BrowserPackage from './BrowserPackage'; +import BrowserSearch from './BrowserSearch'; import Button from '~ui/components/base/Button'; import TextField from '~ui/components/base/TextField'; import useBrowser from '~ui/hooks/useBrowser'; @@ -16,6 +17,7 @@ function BrowserRoutes() { } /> + ); } @@ -27,23 +29,31 @@ function BrowserRoot(props: ParentProps) { } function BrowserToolbar() { - // const controller = useBrowser(); + const browser = useBrowser(); + + const onKeyPress: JSX.EventHandlerUnion = (e) => { + if (e.key === 'Enter') { + const query = e.currentTarget.value; + + browser.setSearchQuery(prev => ({ + ...prev, + query, + })); + + browser.search(); + } + }; return (
-
- {/*
+
} + value={browser.searchQuery().query || ''} placeholder="Search for content" + onKeyPress={onKeyPress} />
diff --git a/apps/frontend/src/ui/pages/browser/BrowserSearch.tsx b/apps/frontend/src/ui/pages/browser/BrowserSearch.tsx new file mode 100644 index 00000000..9d42df5f --- /dev/null +++ b/apps/frontend/src/ui/pages/browser/BrowserSearch.tsx @@ -0,0 +1,34 @@ +import { Show, createEffect, on } from 'solid-js'; +import { BrowserContent } from './BrowserRoot'; +import { bridge } from '~imports'; +import SearchResultsContainer from '~ui/components/content/SearchResults'; +import Spinner from '~ui/components/Spinner'; +import useBrowser from '~ui/hooks/useBrowser'; +import useCommand from '~ui/hooks/useCommand'; + +function BrowserSearch() { + const browser = useBrowser(); + + const [results, { refetch }] = useCommand(() => bridge.commands.searchProviderPackages('Modrinth', browser.searchQuery())); + + createEffect(on(() => browser.searchQuery().query, () => { + refetch(); + })); + + return ( + + + + + + + + ); +} + +export default BrowserSearch; diff --git a/apps/testing/examples/search_packages.rs b/apps/testing/examples/search_packages.rs new file mode 100644 index 00000000..8d4eec99 --- /dev/null +++ b/apps/testing/examples/search_packages.rs @@ -0,0 +1,21 @@ +use onelauncher::{data::PackageType, package::content, Result, State}; + +#[tokio::main] +async fn main() -> Result<()> { + let _ = State::get().await?; + + let provider = content::Providers::Modrinth; + let results = provider.search( + Some("chatting".to_string()), + Some(5), + None, + None, + None, + Some(vec![PackageType::Mod]), + None + ).await?; + + println!("{:#?}", results); + + Ok(()) +} diff --git a/packages/core/src/api/package/content/mod.rs b/packages/core/src/api/package/content/mod.rs index 9af9edfe..11877bb0 100644 --- a/packages/core/src/api/package/content/mod.rs +++ b/packages/core/src/api/package/content/mod.rs @@ -5,7 +5,7 @@ use modrinth::{Facet, FacetOperation}; use serde::{Deserialize, Serialize}; -use crate::data::{Loader, ManagedPackage, ManagedUser, ManagedVersion}; +use crate::data::{Loader, ManagedPackage, ManagedUser, ManagedVersion, PackageType}; use crate::package::content::modrinth::FacetBuilder; use crate::store::{Author, ProviderSearchResults}; use crate::Result; @@ -47,6 +47,7 @@ impl Providers { game_versions: Option>, categories: Option>, loaders: Option>, + package_types: Option>, open_source: Option, ) -> Result { match self { @@ -70,6 +71,16 @@ impl Providers { } } + if let Some(package_types) = package_types { + for package_type in package_types { + builder.or(Facet( + "project_types".to_string(), + FacetOperation::Eq, + package_type.get_name().to_string(), + )); + } + } + if let Some(loaders) = loaders { for loader in loaders { builder.or(Facet( diff --git a/packages/core/src/api/package/content/modrinth.rs b/packages/core/src/api/package/content/modrinth.rs index fb272f94..e8d2fe09 100644 --- a/packages/core/src/api/package/content/modrinth.rs +++ b/packages/core/src/api/package/content/modrinth.rs @@ -249,7 +249,7 @@ impl FacetBuilder { if i != 0 { builder.push(','); } - builder.push_str(f.to_string().as_str()); + builder.push_str(format!("\"{}\"", f.to_string()).as_str()); } builder.push(']'); } @@ -322,7 +322,7 @@ where if facets.is_empty() { "".to_string() } else { - format!("&facets={}", facets) + format!("&facets=[{}]", facets) } ) .as_str(), diff --git a/packages/core/src/store/package.rs b/packages/core/src/store/package.rs index 9de4343a..7ca9f4bc 100644 --- a/packages/core/src/store/package.rs +++ b/packages/core/src/store/package.rs @@ -28,6 +28,8 @@ pub enum PackageType { ResourcePack, /// Represents a shaderpack file ShaderPack, + /// Represents a modpack file + ModPack, } impl PackageType { @@ -59,6 +61,7 @@ impl PackageType { "datapacks" => Some(PackageType::DataPack), "resourcepacks" => Some(PackageType::ResourcePack), "shaderpacks" => Some(PackageType::ShaderPack), + "modpack" => Some(PackageType::ModPack), _ => None, } } @@ -69,6 +72,7 @@ impl PackageType { PackageType::DataPack => "datapack", PackageType::ResourcePack => "resourcepack", PackageType::ShaderPack => "shaderpack", + PackageType::ModPack => "modpack", } } @@ -78,6 +82,7 @@ impl PackageType { PackageType::DataPack => "datapacks", PackageType::ResourcePack => "resourcepacks", PackageType::ShaderPack => "shaderpacks", + PackageType::ModPack => "modpack", } } @@ -283,6 +288,7 @@ impl PackageManager { PackageType::DataPack => &self.datapacks, PackageType::ResourcePack => &self.resourcepacks, PackageType::ShaderPack => &self.shaderpacks, + _ => unreachable!(), } } @@ -293,6 +299,7 @@ impl PackageManager { PackageType::DataPack => &mut self.datapacks, PackageType::ResourcePack => &mut self.resourcepacks, PackageType::ShaderPack => &mut self.shaderpacks, + _ => unreachable!(), } }