feat: added filter for loaders in browser view (#946)
* feat: added filter for loaders in browser view * changeset
This commit is contained in:
parent
5a8728ef04
commit
d21c0dc22b
5 changed files with 77 additions and 12 deletions
5
.changeset/loaders_filter.md
Normal file
5
.changeset/loaders_filter.md
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
default: patch
|
||||
---
|
||||
|
||||
# Added a filter for choosing modpack loaders
|
||||
|
|
@ -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<String>,
|
||||
pub sort: SearchSort,
|
||||
pub loader: Option<GameLoader>,
|
||||
pub page: usize,
|
||||
}
|
||||
|
||||
|
|
@ -134,6 +136,7 @@ impl Default for BrowserUiState {
|
|||
provider: ProviderId::Modrinth,
|
||||
categories: Vec::new(),
|
||||
sort: SearchSort::Relevance,
|
||||
loader: None,
|
||||
page: 0,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<GameLoader>, &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<usize>,
|
||||
pending: bool,
|
||||
sort: State<SearchSort>,
|
||||
loader: Option<State<Option<GameLoader>>>,
|
||||
view_mode: State<ViewLayout>,
|
||||
) -> 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<Option<GameLoader>>,
|
||||
}
|
||||
|
||||
impl Component for LoaderPicker {
|
||||
fn render(&self) -> impl IntoElement {
|
||||
let mut loader = self.loader;
|
||||
let current = *loader.read();
|
||||
|
||||
let labels: Vec<String> = 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 {
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -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)]
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue