feat: group user-created OneClient clusters by release line

This commit is contained in:
Julian Chang 2026-10-01 00:48:41 -04:00
parent 51608f7d94
commit 03c00b4199
No known key found for this signature in database
4 changed files with 82 additions and 56 deletions

View file

@ -4,6 +4,7 @@ use freya::router::RouterContext;
use crate::components::{Button, Icon, IconType};
use oneclient_common::parse_mc_version;
use oneclient_core::clusters::ClusterKind;
use crate::hooks::{
settled_or_loading, use_active_cluster_id, use_clusters, use_dispatch, use_game_snapshot,
@ -75,14 +76,19 @@ impl Component for ActiveClusterPanel {
};
let version = format!("{} {}", cluster.mc_version, cluster.mc_loader);
let (title, subtitle) = if cluster.user_created {
let (title, subtitle) = if cluster.user_created && cluster.kind != ClusterKind::OneClient {
(cluster.name.clone(), version)
} else {
let subtitle = match metadata {
Some(m) => format!("OneClient · {}", m.name),
None => "OneClient".to_string(),
};
(version, subtitle)
let title = if cluster.user_created {
cluster.name.clone()
} else {
version
};
(title, subtitle)
};
let cluster_id = cluster.id;
let syncing = launch_syncing(&launcher, cluster.uses_bundles());

View file

@ -88,11 +88,11 @@ pub enum GridSelection {
}
pub fn split_clusters(clusters: &[Cluster]) -> (ClusterGroups, Vec<Cluster>) {
let (instances, provisioned): (Vec<&Cluster>, Vec<&Cluster>) =
clusters.iter().partition(|cluster| cluster.user_created);
let (instances, oneclient): (Vec<&Cluster>, Vec<&Cluster>) =
clusters.iter().partition(|cluster| cluster.is_isolated());
(
group_clusters_by_release(provisioned),
group_clusters_by_release(oneclient),
sort_clusters_for_home(instances.into_iter().cloned().collect()),
)
}
@ -407,6 +407,26 @@ mod tests {
}
}
#[test]
fn user_created_oneclient_clusters_join_their_release_line() {
let provisioned = versioned(1, "26.1");
let mine = Cluster {
user_created: true,
..versioned(2, "26.1")
};
let vanilla = Cluster {
user_created: true,
kind: oneclient_db::models::ClusterKind::Vanilla,
..versioned(3, "26.1")
};
let (groups, instances) = split_clusters(&[provisioned, mine, vanilla]);
let line = ReleaseLine::from_version("26.1").unwrap();
let grouped: Vec<i64> = groups[&line].iter().map(|c| c.id).collect();
assert_eq!(grouped, vec![1, 2]);
assert_eq!(instances.iter().map(|c| c.id).collect::<Vec<_>>(), vec![3]);
}
#[test]
fn a_newly_created_instance_comes_first_on_home() {
let now = chrono::Utc::now();

View file

@ -271,7 +271,6 @@ fn kind_for(choice: TypeChoice, loader: Option<GameLoader>) -> ClusterKind {
fn suggested_name(
version: Option<&String>,
choice: TypeChoice,
kind: ClusterKind,
loader: Option<GameLoader>,
) -> String {
@ -279,11 +278,10 @@ fn suggested_name(
return String::new();
};
let full = match (choice, kind, loader) {
(TypeChoice::OneClient, _, _) => version.clone(),
(_, ClusterKind::Vanilla, _) => format!("{version} Vanilla"),
(_, _, Some(loader)) => format!("{version} {loader}"),
(_, _, None) => version.clone(),
let full = match (kind, loader) {
(ClusterKind::Vanilla, _) => format!("{version} Vanilla"),
(_, Some(loader)) => format!("{version} {loader}"),
(_, None) => version.clone(),
};
if validate_instance_name(&full).is_ok() {
return full;
@ -359,7 +357,7 @@ pub fn resolve(w: Wizard) -> Picks {
let bundles = use_bundle_context(version.as_ref(), loader, oneclient, step == Step::Bundles);
let kind = kind_for(choice, loader);
let suggested = suggested_name(version.as_ref(), choice, kind, loader);
let suggested = suggested_name(version.as_ref(), kind, loader);
let declined = w.declined.read().clone().unwrap_or_else(|| {
bundles
.archives

View file

@ -92,8 +92,7 @@ impl Component for Clusters {
let active_id = use_active_cluster_id();
let show_create = use_state(|| false);
let mut selected = use_state(|| None::<GridSelection>);
let mut selected_version = use_state(|| None::<VersionKey>);
let mut selected_loader = use_state(|| None::<GameLoader>);
let mut selected_cluster = use_state(|| None::<i64>);
let mut filter = use_state(|| Filter::All);
let query = use_state(String::new);
let loaders = use_state(Vec::<String>::new);
@ -145,7 +144,7 @@ impl Component for Clusters {
if selected.read().is_none() {
*selected.write() = match active_cluster.as_ref() {
Some(cluster) if cluster.user_created => Some(GridSelection::Instance(cluster.id)),
Some(cluster) if cluster.is_isolated() => Some(GridSelection::Instance(cluster.id)),
other => default_line(&groups, other.cloned())
.map(GridSelection::Line)
.or_else(fallback),
@ -172,25 +171,15 @@ impl Component for Clusters {
.and_then(|line| groups.get(&line).cloned())
.unwrap_or_default();
if line.is_some() && selected_version.read().is_none() {
let preferred = active_cluster
.as_ref()
.and_then(|c| parse_mc_version(&c.mc_version))
.and_then(|p| p.key());
*selected_version.write() = default_version_key(&clusters_for_line, preferred);
if line.is_some() && selected_cluster.read().is_none() {
*selected_cluster.write() =
default_line_cluster(&clusters_for_line, active_cluster.as_ref());
}
if line.is_some() && selected_loader.read().is_none() {
let preferred = active_cluster.as_ref().map(|c| c.mc_loader);
*selected_loader.write() = default_loader(&clusters_for_line, preferred);
}
let cluster = resolve_cluster(
&clusters_for_line,
*selected_version.read(),
*selected_loader.read(),
)
.or_else(|| clusters_for_line.first().cloned());
let cluster = selected_cluster
.read()
.and_then(|id| clusters_for_line.iter().find(|c| c.id == id).cloned())
.or_else(|| clusters_for_line.first().cloned());
let instance = match current {
Some(GridSelection::Instance(id)) => instances.iter().find(|c| c.id == id).cloned(),
@ -268,8 +257,7 @@ impl Component for Clusters {
};
VersionCard::new(line, list, caption, is_selected, move |_| {
selected.set(Some(item));
selected_version.set(None);
selected_loader.set(None);
selected_cluster.set(None);
})
.into_element()
})
@ -293,14 +281,10 @@ impl Component for Clusters {
major: line.major,
key: parsed.and_then(|p| p.key()),
loader: cluster.mc_loader,
user_created: cluster.user_created,
base: Sidebar {
picker: line_picker(
line,
&cluster,
&clusters_for_line,
selected_version,
selected_loader,
),
picker: line_picker(line, &cluster, &clusters_for_line, selected_cluster),
description: cluster.description.clone(),
..Sidebar::base(&cluster, sidebar_w)
},
}
@ -431,6 +415,29 @@ fn cluster_caption(cluster: &Cluster) -> String {
format!("{} \u{b7} {}", cluster.mc_version, cluster.mc_loader)
}
fn picker_caption(cluster: &Cluster) -> String {
if cluster.user_created {
format!("{} \u{b7} {}", cluster.name, cluster_caption(cluster))
} else {
cluster_caption(cluster)
}
}
fn default_line_cluster(clusters: &[Cluster], active: Option<&Cluster>) -> Option<i64> {
if let Some(active) = active.filter(|a| clusters.iter().any(|c| c.id == a.id)) {
return Some(active.id);
}
let preferred_key = active
.and_then(|c| parse_mc_version(&c.mc_version))
.and_then(|p| p.key());
resolve_cluster(
clusters,
default_version_key(clusters, preferred_key),
default_loader(clusters, active.map(|c| c.mc_loader)),
)
.map(|c| c.id)
}
fn bottom_border() -> Border {
Border::new()
.fill(colors::component_border())
@ -554,25 +561,18 @@ fn line_picker(
line: ReleaseLine,
cluster: &Cluster,
siblings: &[Cluster],
mut selected_version: State<Option<VersionKey>>,
mut selected_loader: State<Option<GameLoader>>,
mut selected_cluster: State<Option<i64>>,
) -> Option<Element> {
if siblings.len() <= 1 {
return None;
}
let options: Vec<String> = siblings.iter().map(cluster_caption).collect();
let options: Vec<String> = siblings.iter().map(picker_caption).collect();
let current = siblings
.iter()
.position(|c| c.id == cluster.id)
.unwrap_or(0);
let picks: Vec<(Option<VersionKey>, GameLoader)> = siblings
.iter()
.map(|c| {
let key = parse_mc_version(&c.mc_version).and_then(|p| p.key());
(key, c.mc_loader)
})
.collect();
let ids: Vec<i64> = siblings.iter().map(|c| c.id).collect();
Some(
rect()
@ -596,9 +596,8 @@ fn line_picker(
.width(Size::fill())
.height(Size::px(40.))
.on_select(move |idx: usize| {
if let Some((key, loader)) = picks.get(idx).copied() {
selected_version.set(key);
selected_loader.set(Some(loader));
if let Some(id) = ids.get(idx).copied() {
selected_cluster.set(Some(id));
}
}),
)
@ -663,6 +662,7 @@ struct LineSidebar {
major: u32,
key: Option<VersionKey>,
loader: GameLoader,
user_created: bool,
base: Sidebar,
}
@ -672,8 +672,10 @@ impl Component for LineSidebar {
let mut sidebar = self.base.clone();
if let Some(metadata) = metadata {
sidebar.title = metadata.name;
sidebar.description = metadata.long_description;
if !self.user_created {
sidebar.title = metadata.name;
}
sidebar.description = sidebar.description.or(metadata.long_description);
}
sidebar
}