diff --git a/.changeset/loaders_filter.md b/.changeset/loaders_filter.md new file mode 100644 index 00000000..74ba509b --- /dev/null +++ b/.changeset/loaders_filter.md @@ -0,0 +1,5 @@ +--- +default: patch +--- + +# Added a filter for choosing modpack loaders \ No newline at end of file diff --git a/packages/oneclient_app/src/hooks/active_cluster.rs b/packages/oneclient_app/src/hooks/active_cluster.rs index 8d14d0a9..18c31f41 100644 --- a/packages/oneclient_app/src/hooks/active_cluster.rs +++ b/packages/oneclient_app/src/hooks/active_cluster.rs @@ -1,6 +1,7 @@ use std::collections::{HashMap, HashSet}; use freya::prelude::*; +use oneclient_common::domain::GameLoader; use oneclient_content::packages::ProviderId; use oneclient_content::packages::types::SearchSort; use oneclient_db::models::ClusterId; @@ -124,6 +125,7 @@ pub struct BrowserUiState { pub provider: ProviderId, pub categories: Vec, pub sort: SearchSort, + pub loader: Option, pub page: usize, } @@ -134,6 +136,7 @@ impl Default for BrowserUiState { provider: ProviderId::Modrinth, categories: Vec::new(), sort: SearchSort::Relevance, + loader: None, page: 0, } } diff --git a/packages/oneclient_app/src/view/app/browser/index/mod.rs b/packages/oneclient_app/src/view/app/browser/index/mod.rs index 24667f97..84bc687d 100644 --- a/packages/oneclient_app/src/view/app/browser/index/mod.rs +++ b/packages/oneclient_app/src/view/app/browser/index/mod.rs @@ -5,6 +5,7 @@ use freya::animation::{ }; use freya::prelude::*; use freya::router::RouterContext; +use oneclient_common::domain::GameLoader; use oneclient_common::parse_mc_version; use oneclient_common::search::normalize_query; use oneclient_content::packages::types::ProjectSummary; @@ -68,6 +69,13 @@ const SORTS: [(SearchSort, &str); 4] = [ (SearchSort::Updated, "Updated"), ]; +const LOADERS: [(Option, &str); 4] = [ + (None, "Any loader"), + (Some(GameLoader::Fabric), "Fabric"), + (Some(GameLoader::Forge), "Forge"), + (Some(GameLoader::NeoForge), "NeoForge"), +]; + const BROWSE_TYPES: [(&str, &str); 5] = [ ("mod", "Mods"), ("texture", "Textures"), @@ -183,6 +191,7 @@ impl Component for BrowserBody { let compatible_only = use_browser_compat(); let selected_categories = use_state(|| saved.categories.clone()); let sort = use_state(|| saved.sort); + let loader = use_state(|| saved.loader); let page = use_state(|| saved.page); { @@ -194,6 +203,7 @@ impl Component for BrowserBody { provider: *provider.read(), categories: selected_categories.read().clone(), sort: *sort.read(), + loader: *loader.read(), page: *page.read(), }; store.write().insert(key.clone(), snapshot); @@ -212,23 +222,24 @@ impl Component for BrowserBody { let cats = selected_categories.read().clone(); let targets_cluster = content_type != ContentType::Modpack; - let (game_versions, loaders) = match (compat && targets_cluster, &cluster) { - (true, Some(c)) => { - let loaders = if content_type == ContentType::Mod { - vec![c.mc_loader] - } else { - Vec::new() - }; - (vec![c.mc_version.clone()], loaders) - } - _ => (Vec::new(), Vec::new()), + let compat_cluster = cluster.as_ref().filter(|_| compat && targets_cluster); + let game_versions = compat_cluster + .map(|c| vec![c.mc_version.clone()]) + .unwrap_or_default(); + let loader_fixed = content_type == ContentType::Mod && compat_cluster.is_some(); + let loader_filterable = + matches!(content_type, ContentType::Mod | ContentType::Modpack) && !loader_fixed; + let picked_loader = (*loader.read()).filter(|_| loader_filterable); + let loaders = match (loader_fixed, compat_cluster) { + (true, Some(c)) => vec![c.mc_loader], + _ => picked_loader.into_iter().collect(), }; let mut page_state = page; // Normalised like the search key so a query differing only by a space does not reset the page let sort_by = *sort.read(); let signature = format!( - "{provider_id:?}|{}|{compat}|{}|{sort_by:?}", + "{provider_id:?}|{}|{compat}|{}|{sort_by:?}|{picked_loader:?}", normalize_query(&debounced_query.read()).to_lowercase(), cats.join(",") ); @@ -396,6 +407,7 @@ impl Component for BrowserBody { pages, pending, sort, + loader_filterable.then_some(loader), view_mode, )) .child( @@ -528,6 +540,7 @@ fn results_toolbar( pages: Option, pending: bool, sort: State, + loader: Option>>, view_mode: State, ) -> impl IntoElement { // Blank rather than a stale count while a page is in flight @@ -554,6 +567,7 @@ fn results_toolbar( .color(colors::fg_primary().with_a(140)), ) .child(rect().width(Size::flex(1.0))) + .maybe_child(loader.map(|loader| LoaderPicker { loader })) .child(SortPicker { sort }) .child( SegmentedControl::new(view_mode) @@ -596,6 +610,33 @@ impl Component for SortPicker { } } +#[derive(PartialEq)] +struct LoaderPicker { + loader: State>, +} + +impl Component for LoaderPicker { + fn render(&self) -> impl IntoElement { + let mut loader = self.loader; + let current = *loader.read(); + + let labels: Vec = LOADERS.iter().map(|(_, l)| (*l).to_string()).collect(); + let selected = LOADERS + .iter() + .find(|(l, _)| *l == current) + .map_or("Any loader", |(_, l)| *l); + + Dropdown::new(selected, labels) + .width(Size::px(132.)) + .height(Size::px(30.)) + .on_select(move |idx: usize| { + if let Some((picked, _)) = LOADERS.get(idx) { + loader.set(*picked); + } + }) + } +} + /// Falls back to the bare version while the manifest hasn't arrived or doesn't cover it fn version_name(metadata: &[VersionMetadata], cluster: &Cluster) -> String { if cluster.user_created { diff --git a/packages/oneclient_content/src/packages/provider/curseforge.rs b/packages/oneclient_content/src/packages/provider/curseforge.rs index 2f820806..10c510bc 100644 --- a/packages/oneclient_content/src/packages/provider/curseforge.rs +++ b/packages/oneclient_content/src/packages/provider/curseforge.rs @@ -57,7 +57,14 @@ impl PackageProvider for CurseForgeProvider { .to_string(), ); params.append_pair("index", &filters.offset.unwrap_or(0).to_string()); - params.append_pair("sortField", "6"); + params.append_pair( + "sortField", + &filters + .sort + .unwrap_or_default() + .curseforge_sort_field() + .to_string(), + ); params.append_pair("sortOrder", "desc"); let query = filters.normalized_query(); if !query.is_empty() { diff --git a/packages/oneclient_content/src/packages/types.rs b/packages/oneclient_content/src/packages/types.rs index ce354bf7..141aabee 100644 --- a/packages/oneclient_content/src/packages/types.rs +++ b/packages/oneclient_content/src/packages/types.rs @@ -50,6 +50,15 @@ impl SearchSort { Self::Updated => "updated", } } + + pub fn curseforge_sort_field(self) -> u32 { + match self { + Self::Relevance => 1, + Self::Downloads => 6, + Self::Newest => 11, + Self::Updated => 3, + } + } } #[derive(Debug, Clone, Serialize, Deserialize)]