feat: added resource packs and shaders sections inside browse view (#819)
This commit is contained in:
parent
53b27a1c7b
commit
a6c03c6f6d
6 changed files with 153 additions and 30 deletions
|
|
@ -8,8 +8,8 @@ use crate::{
|
|||
Route,
|
||||
components::{Avatar, Icon, IconType},
|
||||
hooks::{
|
||||
settled_or_loading, try_default_account, use_active_cluster_id, use_clusters,
|
||||
use_current_account, use_dispatch, use_notifications_snapshot,
|
||||
settled_or_loading, try_default_account, use_active_cluster_id, use_browser_type,
|
||||
use_clusters, use_current_account, use_dispatch, use_notifications_snapshot,
|
||||
},
|
||||
theme,
|
||||
utils::sort_clusters_for_home,
|
||||
|
|
@ -126,6 +126,7 @@ fn navbar_center() -> impl IntoElement {
|
|||
fn browse_target() -> Route {
|
||||
let clusters = settled_or_loading(&use_clusters()).unwrap_or_default();
|
||||
let active = *use_active_cluster_id().read();
|
||||
let package_type = use_browser_type().read().clone();
|
||||
|
||||
let cluster_id = active
|
||||
.filter(|id| clusters.iter().any(|cluster| cluster.id == *id))
|
||||
|
|
@ -134,7 +135,7 @@ fn browse_target() -> Route {
|
|||
match cluster_id {
|
||||
Some(cluster_id) => Route::Browser {
|
||||
cluster_id,
|
||||
package_type: "mod".to_string(),
|
||||
package_type,
|
||||
pick_cluster: true,
|
||||
},
|
||||
None => Route::Clusters {},
|
||||
|
|
|
|||
|
|
@ -55,6 +55,17 @@ pub fn use_browser_compat() -> State<bool> {
|
|||
consume_root_context::<BrowserCompatState>().0
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct BrowserTypeState(pub State<String>);
|
||||
|
||||
pub fn use_provide_browser_type(state: BrowserTypeState) {
|
||||
use_hook(move || provide_root_context(state));
|
||||
}
|
||||
|
||||
pub fn use_browser_type() -> State<String> {
|
||||
consume_root_context::<BrowserTypeState>().0
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct LinkConfirmState(pub State<Option<String>>);
|
||||
|
||||
|
|
|
|||
|
|
@ -14,10 +14,11 @@ pub use overlay_claims::{
|
|||
pub use view_state::{PersistedView, use_view_state};
|
||||
|
||||
pub use active_cluster::{
|
||||
ActiveClusterState, BrowserCompatState, BrowserStateStore, BrowserUiState, LinkConfirmState,
|
||||
OnboardingSelectionState, SplashState, StartMaximizedState, use_active_cluster_id,
|
||||
use_browser_compat, use_browser_state_store, use_link_confirm, use_onboarding_selection,
|
||||
use_provide_active_cluster, use_provide_browser_compat, use_provide_browser_state,
|
||||
ActiveClusterState, BrowserCompatState, BrowserStateStore, BrowserTypeState, BrowserUiState,
|
||||
LinkConfirmState, OnboardingSelectionState, SplashState, StartMaximizedState,
|
||||
use_active_cluster_id, use_browser_compat, use_browser_state_store, use_browser_type,
|
||||
use_link_confirm, use_onboarding_selection, use_provide_active_cluster,
|
||||
use_provide_browser_compat, use_provide_browser_state, use_provide_browser_type,
|
||||
use_provide_link_confirm, use_provide_onboarding_selection, use_provide_splash,
|
||||
use_provide_start_maximized, use_splash, use_start_maximized,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -20,9 +20,10 @@ use oneclient_core::clusters::Cluster;
|
|||
use oneclient_db::models::ClusterId;
|
||||
|
||||
use crate::hooks::{
|
||||
ActiveClusterState, BrowserCompatState, BrowserStateStore, use_active_cluster_id, use_clusters,
|
||||
use_game_snapshot, use_launcher, use_provide_active_cluster, use_provide_browser_compat,
|
||||
use_provide_browser_state, use_splash,
|
||||
ActiveClusterState, BrowserCompatState, BrowserStateStore, BrowserTypeState,
|
||||
use_active_cluster_id, use_clusters, use_game_snapshot, use_launcher,
|
||||
use_provide_active_cluster, use_provide_browser_compat, use_provide_browser_state,
|
||||
use_provide_browser_type, use_splash,
|
||||
};
|
||||
use crate::theme::colors;
|
||||
use oneclient_events::LaunchStage;
|
||||
|
|
@ -43,6 +44,9 @@ impl Component for AppShell {
|
|||
let browser_state = use_state(HashMap::new);
|
||||
use_provide_browser_state(BrowserStateStore(browser_state));
|
||||
|
||||
let browser_type = use_state(|| "mod".to_string());
|
||||
use_provide_browser_type(BrowserTypeState(browser_type));
|
||||
|
||||
// `FileDrop` bubbles so anything a drop zone doesn't `stop_propagation()` lands here
|
||||
let mut drop_hovering = use_state(|| false);
|
||||
let mut drop_pending = use_state(Vec::<PathBuf>::new);
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ use crate::hooks::{
|
|||
BROWSE_PAGE_SIZE, BrowserUiState, bundles_with_status_items, category_list,
|
||||
cluster_content_items, content_type_for_slug, pick_version_metadata, search_items,
|
||||
search_pending, search_total, settled_or_loading, use_browser_compat, use_browser_state_store,
|
||||
use_bundles_with_status, use_cluster_content, use_clusters, use_debounced,
|
||||
use_browser_type, use_bundles_with_status, use_cluster_content, use_clusters, use_debounced,
|
||||
use_package_categories, use_package_search, use_versions, use_view_state, versions_metadata,
|
||||
};
|
||||
use crate::routes::Route;
|
||||
|
|
@ -45,6 +45,11 @@ use skeletons::{SkeletonListRow, skeleton_grid_row};
|
|||
const CARD_BG: Color = Color::from_rgb(26, 34, 41);
|
||||
const CARD_NAME: Color = Color::from_rgb(213, 219, 255);
|
||||
const SCROLLBAR_GUTTER: f32 = 18.;
|
||||
const CATEGORY_SIDEBAR_W: f32 = 190.;
|
||||
const PROVIDER_LABELS_W: f32 = 900.;
|
||||
const WIDE_SEARCH_W: f32 = 780.;
|
||||
const SEARCH_W: f32 = 260.;
|
||||
const SEARCH_COMPACT_W: f32 = 170.;
|
||||
const CARD_H: f32 = 240.;
|
||||
const BANNER_H: f32 = 100.;
|
||||
const CARD_TEXT_H: f32 = CARD_H - BANNER_H;
|
||||
|
|
@ -62,12 +67,17 @@ pub(super) struct GridMetrics {
|
|||
pub banner_h: f32,
|
||||
}
|
||||
|
||||
const BROWSE_TYPES: [(&str, &str); 3] = [
|
||||
("mod", "Mods"),
|
||||
("texture", "Textures"),
|
||||
("shader", "Shaders"),
|
||||
];
|
||||
|
||||
fn type_title(package_type: &str) -> &'static str {
|
||||
match package_type {
|
||||
"shader" => "Shaders",
|
||||
"texture" => "Textures",
|
||||
_ => "Mods",
|
||||
}
|
||||
BROWSE_TYPES
|
||||
.iter()
|
||||
.find(|(slug, _)| *slug == package_type)
|
||||
.map_or("Mods", |(_, title)| *title)
|
||||
}
|
||||
|
||||
fn encode_package_id(provider: ProviderId, id: &str) -> String {
|
||||
|
|
@ -83,6 +93,37 @@ pub struct Browser {
|
|||
}
|
||||
|
||||
impl Component for Browser {
|
||||
fn render(&self) -> impl IntoElement {
|
||||
let mut last_type = use_browser_type();
|
||||
use_side_effect_with_deps(&self.package_type, move |package_type| {
|
||||
last_type.set_if_modified(package_type.clone());
|
||||
});
|
||||
|
||||
BrowserBody {
|
||||
cluster_id: self.cluster_id,
|
||||
package_type: self.package_type.clone(),
|
||||
pick_cluster: self.pick_cluster,
|
||||
key: DiffKey::None,
|
||||
}
|
||||
.key((self.cluster_id, self.package_type.as_str()))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq)]
|
||||
struct BrowserBody {
|
||||
cluster_id: i64,
|
||||
package_type: String,
|
||||
pick_cluster: bool,
|
||||
key: DiffKey,
|
||||
}
|
||||
|
||||
impl KeyExt for BrowserBody {
|
||||
fn write_key(&mut self) -> &mut DiffKey {
|
||||
&mut self.key
|
||||
}
|
||||
}
|
||||
|
||||
impl Component for BrowserBody {
|
||||
fn render(&self) -> impl IntoElement {
|
||||
let cluster_id = self.cluster_id;
|
||||
let package_type = self.package_type.clone();
|
||||
|
|
@ -191,6 +232,7 @@ impl Component for Browser {
|
|||
let mode = *view_mode.read();
|
||||
|
||||
let mut grid_width = use_state(|| 0f32);
|
||||
let controls_width = use_state(|| 0f32);
|
||||
let cols = resolved_columns(*grid_columns.read());
|
||||
let card_w = cell_width(
|
||||
(*grid_width.read() - SCROLLBAR_GUTTER).max(0.),
|
||||
|
|
@ -278,7 +320,18 @@ impl Component for Browser {
|
|||
package_type: package_type.clone(),
|
||||
}),
|
||||
))
|
||||
.child(controls(provider, query, view_mode, grid_columns))
|
||||
.child(controls(
|
||||
TypePicker {
|
||||
cluster_id,
|
||||
package_type: package_type.clone(),
|
||||
pick_cluster: self.pick_cluster,
|
||||
},
|
||||
provider,
|
||||
query,
|
||||
view_mode,
|
||||
grid_columns,
|
||||
controls_width,
|
||||
))
|
||||
.child(
|
||||
rect()
|
||||
.horizontal()
|
||||
|
|
@ -319,6 +372,10 @@ impl Component for Browser {
|
|||
),
|
||||
)
|
||||
}
|
||||
|
||||
fn render_key(&self) -> DiffKey {
|
||||
self.key.clone().or(self.default_key())
|
||||
}
|
||||
}
|
||||
|
||||
fn page_title(
|
||||
|
|
@ -341,7 +398,7 @@ fn page_title(
|
|||
// Without the picker the cluster is fixed so it's just a subtitle
|
||||
None => cluster_name.map(|name| {
|
||||
label()
|
||||
.text(format!("for {name}"))
|
||||
.text(name)
|
||||
.font_size(14.)
|
||||
.color(colors::fg_secondary())
|
||||
.into_element()
|
||||
|
|
@ -364,6 +421,41 @@ fn version_name(metadata: &[VersionMetadata], cluster: &Cluster) -> String {
|
|||
.unwrap_or_else(|| cluster.mc_version.clone())
|
||||
}
|
||||
|
||||
#[derive(PartialEq)]
|
||||
struct TypePicker {
|
||||
cluster_id: i64,
|
||||
package_type: String,
|
||||
pick_cluster: bool,
|
||||
}
|
||||
|
||||
impl Component for TypePicker {
|
||||
fn render(&self) -> impl IntoElement {
|
||||
let cluster_id = self.cluster_id;
|
||||
let pick_cluster = self.pick_cluster;
|
||||
let current = self.package_type.clone();
|
||||
|
||||
let labels: Vec<String> = BROWSE_TYPES
|
||||
.iter()
|
||||
.map(|(_, title)| (*title).to_string())
|
||||
.collect();
|
||||
let selected = type_title(&self.package_type).to_string();
|
||||
|
||||
Dropdown::new(selected, labels)
|
||||
.width(Size::px(CATEGORY_SIDEBAR_W))
|
||||
.height(Size::px(30.))
|
||||
.on_select(move |idx: usize| {
|
||||
let picked = BROWSE_TYPES.get(idx).filter(|(slug, _)| *slug != current);
|
||||
if let Some((slug, _)) = picked {
|
||||
let _ = RouterContext::get().push(Route::Browser {
|
||||
cluster_id,
|
||||
package_type: (*slug).to_string(),
|
||||
pick_cluster,
|
||||
});
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// The choice lives in the route so back and forward keep the cluster being browsed
|
||||
#[derive(PartialEq)]
|
||||
struct ClusterPicker {
|
||||
|
|
@ -396,12 +488,6 @@ impl Component for ClusterPicker {
|
|||
.cross_align(Alignment::Center)
|
||||
.spacing(8.)
|
||||
.margin(Gaps::new(4., 0., 0., 0.))
|
||||
.child(
|
||||
label()
|
||||
.text("for")
|
||||
.font_size(14.)
|
||||
.color(colors::fg_secondary()),
|
||||
)
|
||||
.child(
|
||||
Dropdown::new(selected, labels)
|
||||
.width(Size::px(240.))
|
||||
|
|
@ -420,17 +506,34 @@ impl Component for ClusterPicker {
|
|||
}
|
||||
|
||||
fn controls(
|
||||
type_picker: TypePicker,
|
||||
provider: State<ProviderId>,
|
||||
query: State<String>,
|
||||
view_mode: State<ViewLayout>,
|
||||
grid_columns: State<u8>,
|
||||
mut controls_width: State<f32>,
|
||||
) -> impl IntoElement {
|
||||
let measured = *controls_width.read();
|
||||
let show_provider_labels = measured == 0. || measured >= PROVIDER_LABELS_W;
|
||||
let search_width = if measured == 0. || measured >= WIDE_SEARCH_W {
|
||||
SEARCH_W
|
||||
} else {
|
||||
SEARCH_COMPACT_W
|
||||
};
|
||||
|
||||
rect()
|
||||
.horizontal()
|
||||
.width(Size::fill())
|
||||
.cross_align(Alignment::Center)
|
||||
.spacing(12.)
|
||||
.content(Content::Flex)
|
||||
.on_sized(move |event: Event<SizedEventData>| {
|
||||
let w = event.data().area.width();
|
||||
if (w - *controls_width.peek()).abs() > 0.5 {
|
||||
*controls_width.write() = w;
|
||||
}
|
||||
})
|
||||
.child(type_picker)
|
||||
.child(rect().width(Size::flex(1.0)))
|
||||
.maybe_child(
|
||||
(*view_mode.read() == ViewLayout::Grid)
|
||||
|
|
@ -446,16 +549,19 @@ fn controls(
|
|||
.child(
|
||||
SegmentedControl::new(provider)
|
||||
.no_tint()
|
||||
.segments(ProviderId::REMOTE_PROVIDERS.iter().map(|provider| {
|
||||
Segment::new(*provider)
|
||||
.icon(IconType::from(*provider))
|
||||
.label(provider.to_string())
|
||||
.segments(ProviderId::REMOTE_PROVIDERS.iter().map(move |provider| {
|
||||
let segment = Segment::new(*provider).icon(IconType::from(*provider));
|
||||
if show_provider_labels {
|
||||
segment.label(provider.to_string())
|
||||
} else {
|
||||
segment
|
||||
}
|
||||
}))
|
||||
.into_element(),
|
||||
)
|
||||
.child(
|
||||
TextInput::new(query)
|
||||
.width(Size::px(260.))
|
||||
.width(Size::px(search_width))
|
||||
.placeholder("Search for content")
|
||||
.leading(
|
||||
Icon::new(IconType::SearchMd)
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ impl Component for CategorySidebar {
|
|||
|
||||
rect()
|
||||
.vertical()
|
||||
.width(Size::px(190.))
|
||||
.width(Size::px(CATEGORY_SIDEBAR_W))
|
||||
.height(Size::fill())
|
||||
.spacing(8.)
|
||||
.child(
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue