Bugs
Fixed #492 Fixed #510 Fixed #506 Fixed #568 Fixed #582 Fixed #578 Fixed #567 Fixed #588 Fixed #585 Fixed #571 Fixed #587 Fixed #584
This commit is contained in:
parent
68337a79dc
commit
29d1f1c9d2
125 changed files with 9155 additions and 824 deletions
670
Cargo.lock
generated
670
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
13
Cargo.toml
13
Cargo.toml
|
|
@ -28,12 +28,24 @@ oneclient_macro = { path = "./packages/oneclient_macro" }
|
|||
# core (extra)
|
||||
gamemode_rs = {path = "./packages/gamemode_rs"}
|
||||
interfrost = {version = "=1.3.2"}
|
||||
discord-rich-presence = {version = "=1.1.0"}
|
||||
|
||||
# logging
|
||||
tracing = {version = "=0.1.44"}
|
||||
tracing-futures = {version = "=0.2.5"}
|
||||
tracing-subscriber = {version = "=0.3.23", features = ["env-filter", "fmt", "ansi", "json", "parking_lot", "registry"]}
|
||||
|
||||
sentry = {version = "=0.48.5", default-features = false, features = [
|
||||
"backtrace",
|
||||
"contexts",
|
||||
"debug-images",
|
||||
"panic",
|
||||
"release-health",
|
||||
"reqwest",
|
||||
"rustls",
|
||||
]}
|
||||
sentry-tracing = {version = "=0.48.5"}
|
||||
|
||||
# database
|
||||
sqlx = {version = "=0.9.0", default-features = false, features = [
|
||||
"runtime-tokio",
|
||||
|
|
@ -65,6 +77,7 @@ reqwest = {version = "=0.13.3", default-features = false, features = [
|
|||
"form",
|
||||
"hickory-dns",
|
||||
]}
|
||||
reqwest-websocket = {version = "=0.6.0"}
|
||||
tokio = {version = "=1.52.3", features = ["full"]}
|
||||
tokio-stream = {version = "=0.1.18", features = ["full"]}
|
||||
tokio-util = {version = "=0.7.18", features = ["full"]}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
|
||||
use freya::prelude::*;
|
||||
|
||||
struct MinApp;
|
||||
|
|
|
|||
|
|
@ -4,17 +4,12 @@ pub struct AppAssets;
|
|||
|
||||
impl AppAssets {
|
||||
pub fn get_bytes(path: &str) -> Option<bytes::Bytes> {
|
||||
AppAssets::get(path).map(|file| {
|
||||
match file.data {
|
||||
std::borrow::Cow::Borrowed(slice) => {
|
||||
bytes::Bytes::copy_from_slice(slice)
|
||||
},
|
||||
std::borrow::Cow::Owned(vec) => {
|
||||
tracing::warn!("Cloning asset data for '{}'", path);
|
||||
bytes::Bytes::from(vec)
|
||||
},
|
||||
AppAssets::get(path).map(|file| match file.data {
|
||||
std::borrow::Cow::Borrowed(slice) => bytes::Bytes::copy_from_slice(slice),
|
||||
std::borrow::Cow::Owned(vec) => {
|
||||
tracing::warn!("Cloning asset data for '{}'", path);
|
||||
bytes::Bytes::from(vec)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
|
||||
use std::path::PathBuf;
|
||||
|
||||
use oneclient_core::notification::UserChoice;
|
||||
|
|
@ -12,25 +11,43 @@ use crate::notifications::{ClusterUpdateSummary, NotificationSpec};
|
|||
pub enum BridgeCommand {
|
||||
ReloadSettings,
|
||||
SaveSettings,
|
||||
SetSettings { settings: LauncherSettings },
|
||||
RecordSeenVersion { version: String },
|
||||
SetSettings {
|
||||
settings: LauncherSettings,
|
||||
},
|
||||
RecordSeenVersion {
|
||||
version: String,
|
||||
},
|
||||
MarkOnboardingSeen,
|
||||
AcceptTos {
|
||||
terms_version: u32,
|
||||
privacy_version: u32,
|
||||
},
|
||||
|
||||
SaveGlobalProfile { profile: GameSettingsProfile },
|
||||
UpdateGlobalProfile { update: ProfileUpdate },
|
||||
SaveGlobalProfile {
|
||||
profile: GameSettingsProfile,
|
||||
},
|
||||
UpdateGlobalProfile {
|
||||
update: ProfileUpdate,
|
||||
},
|
||||
|
||||
CreateSettingsProfile { name: String },
|
||||
CreateSettingsProfile {
|
||||
name: String,
|
||||
},
|
||||
CreateProfileFromGlobal {
|
||||
name: String,
|
||||
mem_max: Option<u32>,
|
||||
force_fullscreen: Option<bool>,
|
||||
},
|
||||
UpsertNamedProfile { profile: GameSettingsProfile },
|
||||
UpsertNamedProfile {
|
||||
profile: GameSettingsProfile,
|
||||
},
|
||||
UpdateNamedProfile {
|
||||
name: String,
|
||||
update: ProfileUpdate,
|
||||
},
|
||||
DeleteNamedProfile { name: String },
|
||||
DeleteNamedProfile {
|
||||
name: String,
|
||||
},
|
||||
|
||||
UpdateClusterProfile {
|
||||
cluster_id: ClusterId,
|
||||
|
|
@ -49,8 +66,12 @@ pub enum BridgeCommand {
|
|||
vendor: oneclient_core::java::JavaVendor,
|
||||
major: u32,
|
||||
},
|
||||
AddCustomJavaRuntime { path: PathBuf },
|
||||
RemoveJavaRuntime { path: String },
|
||||
AddCustomJavaRuntime {
|
||||
path: PathBuf,
|
||||
},
|
||||
RemoveJavaRuntime {
|
||||
path: String,
|
||||
},
|
||||
|
||||
ToggleNotificationCenter,
|
||||
CloseNotificationCenter,
|
||||
|
|
@ -64,14 +85,20 @@ pub enum BridgeCommand {
|
|||
AnswerPrompt(UserChoice),
|
||||
OpenClusterUpdate(ClusterUpdateSummary),
|
||||
CloseClusterUpdate,
|
||||
SendNotification { spec: NotificationSpec },
|
||||
SendNotification {
|
||||
spec: NotificationSpec,
|
||||
},
|
||||
SendTestProgress {
|
||||
current: u64,
|
||||
total: u64,
|
||||
},
|
||||
|
||||
LaunchCluster { cluster_id: ClusterId },
|
||||
KillCluster { cluster_id: ClusterId },
|
||||
LaunchCluster {
|
||||
cluster_id: ClusterId,
|
||||
},
|
||||
KillCluster {
|
||||
cluster_id: ClusterId,
|
||||
},
|
||||
DismissGameError,
|
||||
|
||||
ImportLocalFile {
|
||||
|
|
@ -91,7 +118,9 @@ pub enum BridgeCommand {
|
|||
bundle_name: String,
|
||||
skip_compatibility: bool,
|
||||
},
|
||||
ApplyBundleUpdates { cluster_id: ClusterId },
|
||||
ApplyBundleUpdates {
|
||||
cluster_id: ClusterId,
|
||||
},
|
||||
SyncBundles,
|
||||
|
||||
ImportLauncher {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
|
||||
mod commands;
|
||||
mod runtime;
|
||||
mod snapshot;
|
||||
|
|
|
|||
|
|
@ -9,8 +9,8 @@ use tokio::sync::{mpsc, watch};
|
|||
|
||||
use crate::components::IconType;
|
||||
use crate::notifications::{
|
||||
ClusterUpdateSummary, InboxEntry, MESSAGE_TOAST_TTL, NotificationAction, NotificationActionKind,
|
||||
NotificationSpec, NotificationState, PendingPrompt, PendingPromptView,
|
||||
ClusterUpdateSummary, InboxEntry, MESSAGE_TOAST_TTL, NotificationAction,
|
||||
NotificationActionKind, NotificationSpec, NotificationState, PendingPrompt, PendingPromptView,
|
||||
};
|
||||
|
||||
use super::commands::BridgeCommand;
|
||||
|
|
@ -243,6 +243,7 @@ impl CoreBridgeRuntime {
|
|||
LauncherState::initialize(notifier).await?;
|
||||
|
||||
oneclient_core::status::start();
|
||||
oneclient_core::plus::start();
|
||||
|
||||
let data_dir = oneclient_core::paths::launcher_dir()
|
||||
.map(|p| p.display().to_string())
|
||||
|
|
@ -277,10 +278,12 @@ impl CoreBridgeRuntime {
|
|||
snapshots.settings.error = None;
|
||||
let state = LauncherState::get()?;
|
||||
let loaded = store::load_settings(Some(&state.services.notifier)).await;
|
||||
let discord_enabled = loaded.discord_enabled;
|
||||
{
|
||||
let mut lock = state.settings.write();
|
||||
*lock = loaded;
|
||||
}
|
||||
state.discord.set_enabled(discord_enabled);
|
||||
refresh_settings(snapshots)?;
|
||||
}
|
||||
BridgeCommand::SaveSettings => {
|
||||
|
|
@ -298,6 +301,7 @@ impl CoreBridgeRuntime {
|
|||
mutate_settings(snapshots, |s| *s = settings)?;
|
||||
let state = LauncherState::get()?;
|
||||
let snapshot = state.settings.read().clone();
|
||||
state.discord.set_enabled(snapshot.discord_enabled);
|
||||
if let Err(err) = save_settings(&snapshot).await {
|
||||
snapshots.settings.error = Some(err.to_string());
|
||||
}
|
||||
|
|
@ -319,6 +323,20 @@ impl CoreBridgeRuntime {
|
|||
snapshots.settings.error = Some(err.to_string());
|
||||
}
|
||||
}
|
||||
BridgeCommand::AcceptTos {
|
||||
terms_version,
|
||||
privacy_version,
|
||||
} => {
|
||||
mutate_settings(snapshots, |settings| {
|
||||
settings.accepted_tos_version = terms_version;
|
||||
settings.accepted_privacy_version = privacy_version;
|
||||
})?;
|
||||
let state = LauncherState::get()?;
|
||||
let snapshot = state.settings.read().clone();
|
||||
if let Err(err) = save_settings(&snapshot).await {
|
||||
snapshots.settings.error = Some(err.to_string());
|
||||
}
|
||||
}
|
||||
|
||||
BridgeCommand::SaveGlobalProfile { profile } => {
|
||||
let state = LauncherState::get()?;
|
||||
|
|
@ -609,20 +627,18 @@ impl CoreBridgeRuntime {
|
|||
version_id,
|
||||
} => {
|
||||
let state = LauncherState::get()?;
|
||||
match install_package(&state, provider, &project_id, &version_id, cluster_id).await
|
||||
{
|
||||
Ok(()) => {
|
||||
state
|
||||
.services
|
||||
.notifier
|
||||
.send_info("Installed", "Package added to cluster");
|
||||
bump_clusters_generation(snapshots);
|
||||
tokio::spawn(async move {
|
||||
let notifier = state.services.notifier.clone();
|
||||
match install_package(&state, provider, &project_id, &version_id, cluster_id)
|
||||
.await
|
||||
{
|
||||
Ok(name) => {
|
||||
notifier.send_info("Installed", &format!("Added {name}"));
|
||||
notifier.invalidate_clusters();
|
||||
}
|
||||
Err(err) => notifier.send_error("Install failed", &err.to_string()),
|
||||
}
|
||||
Err(err) => state
|
||||
.services
|
||||
.notifier
|
||||
.send_error("Install failed", &err.to_string()),
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
BridgeCommand::InstallBundle {
|
||||
|
|
@ -631,15 +647,24 @@ impl CoreBridgeRuntime {
|
|||
skip_compatibility,
|
||||
} => {
|
||||
let state = LauncherState::get()?;
|
||||
oneclient_core::install_bundle(
|
||||
cluster_id,
|
||||
&bundle_name,
|
||||
skip_compatibility,
|
||||
state.bundles.as_ref(),
|
||||
&state.services,
|
||||
)
|
||||
.await?;
|
||||
bump_clusters_generation(snapshots);
|
||||
tokio::spawn(async move {
|
||||
let notifier = state.services.notifier.clone();
|
||||
match oneclient_core::install_bundle(
|
||||
cluster_id,
|
||||
&bundle_name,
|
||||
skip_compatibility,
|
||||
state.bundles.as_ref(),
|
||||
&state.services,
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(_) => {
|
||||
notifier.send_info("Installed", &format!("Added {bundle_name}"));
|
||||
notifier.invalidate_clusters();
|
||||
}
|
||||
Err(err) => notifier.send_error("Install failed", &err.to_string()),
|
||||
}
|
||||
});
|
||||
}
|
||||
BridgeCommand::ApplyBundleUpdates { cluster_id } => {
|
||||
let state = LauncherState::get()?;
|
||||
|
|
@ -792,7 +817,7 @@ async fn install_package(
|
|||
project_id: &str,
|
||||
version_id: &str,
|
||||
cluster_id: i64,
|
||||
) -> anyhow::Result<()> {
|
||||
) -> anyhow::Result<String> {
|
||||
let provider_impl = state.services.packages.get(provider)?;
|
||||
let project = provider_impl
|
||||
.get_project(project_id, &state.services)
|
||||
|
|
@ -823,7 +848,7 @@ async fn install_package(
|
|||
child.finish();
|
||||
session.finish();
|
||||
result?;
|
||||
Ok(())
|
||||
Ok(project.name)
|
||||
}
|
||||
|
||||
fn apply_game_stage(snapshots: &mut BridgeSnapshot, cluster_id: i64, stage: LaunchStage) {
|
||||
|
|
|
|||
|
|
@ -32,10 +32,7 @@ impl GameSnapshot {
|
|||
}
|
||||
|
||||
pub fn logs_for(&self, cluster_id: i64) -> Arc<Vec<Arc<str>>> {
|
||||
self.logs
|
||||
.get(&cluster_id)
|
||||
.cloned()
|
||||
.unwrap_or_default()
|
||||
self.logs.get(&cluster_id).cloned().unwrap_or_default()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -60,7 +60,9 @@ impl Component for ActiveClusterPanel {
|
|||
let p = intro.get().value();
|
||||
let slide_x = (p - 1.0) * 48.0;
|
||||
|
||||
let parsed = active.as_ref().and_then(|c| parse_mc_version(&c.mc_version));
|
||||
let parsed = active
|
||||
.as_ref()
|
||||
.and_then(|c| parse_mc_version(&c.mc_version));
|
||||
let metadata = use_version_metadata(
|
||||
parsed.as_ref().map(|p| p.major),
|
||||
parsed.and_then(|p| p.minor),
|
||||
|
|
@ -114,7 +116,11 @@ impl Component for ActiveClusterPanel {
|
|||
.spacing(8.)
|
||||
.cross_align(Alignment::Center)
|
||||
.margin(Gaps::new(8., 0., 0., 0.))
|
||||
.child(launch_button(cluster_id, dispatch, launch_button_state(&game, cluster_id)))
|
||||
.child(launch_button(
|
||||
cluster_id,
|
||||
dispatch,
|
||||
launch_button_state(&game, cluster_id),
|
||||
))
|
||||
.child(cluster_settings_button(cluster_id)),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,10 @@ use std::borrow::Cow;
|
|||
|
||||
use freya::prelude::*;
|
||||
|
||||
use crate::{components::{IconType, Icon}, theme::colors};
|
||||
use crate::{
|
||||
components::{Icon, IconType},
|
||||
theme::colors,
|
||||
};
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
|
||||
pub enum ButtonVariant {
|
||||
|
|
@ -285,8 +288,7 @@ impl Component for Button {
|
|||
|
||||
rect.get_style().clone_from(&self.style);
|
||||
|
||||
rect = rect.background(background)
|
||||
.color(foreground);
|
||||
rect = rect.background(background).color(foreground);
|
||||
|
||||
if let Some(border) = border_color {
|
||||
rect = rect.border(
|
||||
|
|
@ -420,7 +422,11 @@ fn size_layout(size: ButtonSize) -> (Gaps, CornerRadius, f32, Size, Size) {
|
|||
pub fn link_button() -> Button {
|
||||
Button::new()
|
||||
.primary()
|
||||
.child(Icon::new(IconType::LinkExternal01).size(14.).color(colors::fg_primary()))
|
||||
.child(
|
||||
Icon::new(IconType::LinkExternal01)
|
||||
.size(14.)
|
||||
.color(colors::fg_primary()),
|
||||
)
|
||||
.child(label().text("Open"))
|
||||
}
|
||||
|
||||
|
|
@ -433,4 +439,4 @@ pub fn open_folder_button(folder: std::path::PathBuf) -> Button {
|
|||
crate::platform::open_url(&folder.to_string_lossy());
|
||||
})
|
||||
.child(Icon::new(IconType::Folder).size(16.))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -49,7 +49,13 @@ fn dialog(summary: &ClusterUpdateSummary, dispatch: crate::BridgeDispatch) -> im
|
|||
.corner_radius(CornerRadius::new_all(16.))
|
||||
.background(CARD_BG)
|
||||
.border(border_all_color(1., colors::component_border()))
|
||||
.shadow(Shadow::from((0., 18., 52., 0., Color::from_argb(150, 0, 0, 0))))
|
||||
.shadow(Shadow::from((
|
||||
0.,
|
||||
18.,
|
||||
52.,
|
||||
0.,
|
||||
Color::from_argb(150, 0, 0, 0),
|
||||
)))
|
||||
.child(banner(summary))
|
||||
.child(content(summary, dispatch))
|
||||
}
|
||||
|
|
@ -93,7 +99,11 @@ fn banner(summary: &ClusterUpdateSummary) -> impl IntoElement {
|
|||
.horizontal()
|
||||
.cross_align(Alignment::Center)
|
||||
.spacing(5.)
|
||||
.child(Icon::new(IconType::RefreshCw01).size(12.).color(Color::WHITE))
|
||||
.child(
|
||||
Icon::new(IconType::RefreshCw01)
|
||||
.size(12.)
|
||||
.color(Color::WHITE),
|
||||
)
|
||||
.child(
|
||||
label()
|
||||
.text("Updated")
|
||||
|
|
|
|||
|
|
@ -81,8 +81,8 @@ impl Component for Dropdown {
|
|||
};
|
||||
|
||||
let visible = options.len().min(MAX_VISIBLE_OPTIONS);
|
||||
let list_h = visible as f32 * OPTION_HEIGHT
|
||||
+ visible.saturating_sub(1) as f32 * OPTION_SPACING;
|
||||
let list_h =
|
||||
visible as f32 * OPTION_HEIGHT + visible.saturating_sub(1) as f32 * OPTION_SPACING;
|
||||
|
||||
let offset_y = match (button_area(), list_size()) {
|
||||
(Some(button), Some(list)) => {
|
||||
|
|
@ -141,10 +141,10 @@ impl Component for Dropdown {
|
|||
.font_size(12.)
|
||||
.color(colors::fg_primary()),
|
||||
)
|
||||
.child(Icon::new(
|
||||
IconType::ChevronDown)
|
||||
.size(14.)
|
||||
.color(colors::fg_secondary()),
|
||||
.child(
|
||||
Icon::new(IconType::ChevronDown)
|
||||
.size(14.)
|
||||
.color(colors::fg_secondary()),
|
||||
),
|
||||
)
|
||||
.maybe_child(is_open.then(|| {
|
||||
|
|
|
|||
|
|
@ -83,9 +83,12 @@ pub fn use_art_bytes(
|
|||
let state = reader.state();
|
||||
match (&art_url, &*state) {
|
||||
(Some(url), QueryStateData::Settled { res: Ok(bytes), .. })
|
||||
| (Some(url), QueryStateData::Loading { res: Some(Ok(bytes)) }) => {
|
||||
(format!("{max_edge}|{url}"), bytes.clone())
|
||||
}
|
||||
| (
|
||||
Some(url),
|
||||
QueryStateData::Loading {
|
||||
res: Some(Ok(bytes)),
|
||||
},
|
||||
) => (format!("{max_edge}|{url}"), bytes.clone()),
|
||||
_ => (
|
||||
format!("{max_edge}|{HOME_BACKGROUND_ASSET}"),
|
||||
fallback.read().clone(),
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
|
||||
use std::sync::{Arc, LazyLock};
|
||||
|
||||
use freya::prelude::*;
|
||||
|
|
@ -289,12 +288,11 @@ fn auto_scroll_toggle(mut auto_scroll: State<bool>) -> impl IntoElement {
|
|||
} else {
|
||||
colors::fg_secondary()
|
||||
}))
|
||||
.child(
|
||||
label()
|
||||
.text("Auto-scroll")
|
||||
.font_size(11.)
|
||||
.color(if on { colors::brand() } else { colors::fg_secondary() }),
|
||||
)
|
||||
.child(label().text("Auto-scroll").font_size(11.).color(if on {
|
||||
colors::brand()
|
||||
} else {
|
||||
colors::fg_secondary()
|
||||
}))
|
||||
.into_element()
|
||||
}
|
||||
|
||||
|
|
@ -308,11 +306,15 @@ struct PosGeom {
|
|||
corrected_y: f32,
|
||||
}
|
||||
|
||||
fn pos_at_screen(lines: &[Arc<str>], geom: PosGeom, screen_x: f32, screen_y: f32) -> (usize, usize) {
|
||||
fn pos_at_screen(
|
||||
lines: &[Arc<str>],
|
||||
geom: PosGeom,
|
||||
screen_x: f32,
|
||||
screen_y: f32,
|
||||
) -> (usize, usize) {
|
||||
let region_y = screen_y - geom.viewport_top_px;
|
||||
let band_y = region_y - geom.corrected_y - BODY_PAD_TOP;
|
||||
let line =
|
||||
((band_y / LINE_H).floor() as i64).clamp(0, (geom.count as i64 - 1).max(0)) as usize;
|
||||
let line = ((band_y / LINE_H).floor() as i64).clamp(0, (geom.count as i64 - 1).max(0)) as usize;
|
||||
|
||||
let text_origin_x = ROW_PAD_X + geom.num_col_w + NUM_GAP;
|
||||
let local_x = screen_x - geom.viewport_left_px - geom.corrected_x - text_origin_x;
|
||||
|
|
|
|||
|
|
@ -55,11 +55,7 @@ impl Component for MajorVersionCard {
|
|||
let on_press = self.on_press.clone();
|
||||
|
||||
let opacity = if selected || hovered || focused {
|
||||
if selected {
|
||||
1.0
|
||||
} else {
|
||||
0.85
|
||||
}
|
||||
if selected { 1.0 } else { 0.85 }
|
||||
} else {
|
||||
0.6
|
||||
};
|
||||
|
|
@ -108,7 +104,10 @@ impl Component for MajorVersionCard {
|
|||
.main_align(Alignment::SpaceBetween)
|
||||
.corner_radius(CornerRadius::new_all(12.))
|
||||
.cross_align(Alignment::Start)
|
||||
.border(border_all_color(1., border_color).alignment(BorderAlignment::Inner))
|
||||
.border(
|
||||
border_all_color(1., border_color)
|
||||
.alignment(BorderAlignment::Inner),
|
||||
)
|
||||
.layer(Layer::Relative(3))
|
||||
.background(
|
||||
LinearGradient::new()
|
||||
|
|
|
|||
|
|
@ -84,7 +84,7 @@ pub fn use_microsoft_login() -> MicrosoftLogin {
|
|||
return;
|
||||
}
|
||||
|
||||
// open the browser immediately
|
||||
// open the browser immediately
|
||||
platform::open_url(session.auth_url());
|
||||
finish.mutate(session.clone());
|
||||
pending_login.set(Some(session));
|
||||
|
|
@ -100,7 +100,7 @@ pub fn use_microsoft_login() -> MicrosoftLogin {
|
|||
}
|
||||
});
|
||||
|
||||
// is true once the flow has some status updates (just to ensure the user has seen the popup) and the flow is still in progress
|
||||
// is true once the flow has some status updates (just to ensure the user has seen the popup) and the flow is still in progress
|
||||
let locked = status.as_ref().is_some_and(|s| s.current > 0);
|
||||
let in_flight = begin.read().state().is_loading() || finish.read().state().is_loading();
|
||||
let pending = in_flight && !*cancelled.read();
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ impl Component for Navbar {
|
|||
.window_drag()
|
||||
.width(Size::window_percent(100.))
|
||||
.height(Size::px(theme::NAVBAR_HEIGHT_PX))
|
||||
.position(Position::new_absolute().top(0.).left(0.).right(0.))
|
||||
.position(Position::new_absolute().top(0.).left(0.).right(0.)),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -95,8 +95,8 @@ struct NavLink {
|
|||
impl Component for NavLink {
|
||||
fn render(&self) -> impl IntoElement {
|
||||
let mut hovering = use_state(|| false);
|
||||
let a11y_id = use_a11y();
|
||||
let focused = use_focus(a11y_id);
|
||||
let a11y_id = use_a11y();
|
||||
let focused = use_focus(a11y_id);
|
||||
|
||||
let active = self.active;
|
||||
let target = self.target.clone();
|
||||
|
|
@ -125,12 +125,12 @@ impl Component for NavLink {
|
|||
.cross_align(Alignment::Center)
|
||||
.spacing(2.)
|
||||
.width(Size::px(nav_label.len() as f32 * 10. + 10.))
|
||||
// TODO: Hacky workaround for weird freya measurement bug, where if a rectangle has a transparent background (so in dec = 0), it will be measured weirdly.
|
||||
// This basically fools freya into thinking it indeed has a box and properly handles pointer events
|
||||
.background(Color::RED.with_a(0))
|
||||
.a11y_id(a11y_id)
|
||||
.a11y_focusable(true)
|
||||
.a11y_role(AccessibilityRole::Button)
|
||||
// TODO: Hacky workaround for weird freya measurement bug, where if a rectangle has a transparent background (so in dec = 0), it will be measured weirdly.
|
||||
// This basically fools freya into thinking it indeed has a box and properly handles pointer events
|
||||
.background(Color::RED.with_a(0))
|
||||
.a11y_id(a11y_id)
|
||||
.a11y_focusable(true)
|
||||
.a11y_role(AccessibilityRole::Button)
|
||||
.on_all_press(move |e: Event<PressEventData>| {
|
||||
e.prevent_default();
|
||||
let _ = RouterContext::get().push(target.clone());
|
||||
|
|
|
|||
|
|
@ -391,9 +391,12 @@ fn remote_icon(
|
|||
let reader = icon_query.read();
|
||||
let loaded = match (icon_url, &*reader.state()) {
|
||||
(Some(url), QueryStateData::Settled { res: Ok(bytes), .. })
|
||||
| (Some(url), QueryStateData::Loading { res: Some(Ok(bytes)) }) => {
|
||||
Some((url.clone(), bytes.clone()))
|
||||
}
|
||||
| (
|
||||
Some(url),
|
||||
QueryStateData::Loading {
|
||||
res: Some(Ok(bytes)),
|
||||
},
|
||||
) => Some((url.clone(), bytes.clone())),
|
||||
_ => None,
|
||||
};
|
||||
|
||||
|
|
@ -420,7 +423,11 @@ fn icon_box(icon: IconType, size: f32) -> Element {
|
|||
.height(Size::px(size))
|
||||
.corner_radius(CornerRadius::new_all(8.))
|
||||
.background(colors::component_bg())
|
||||
.child(Icon::new(icon).size(size * 0.4).color(colors::fg_secondary()))
|
||||
.child(
|
||||
Icon::new(icon)
|
||||
.size(size * 0.4)
|
||||
.color(colors::fg_secondary()),
|
||||
)
|
||||
.into_element()
|
||||
}
|
||||
|
||||
|
|
@ -436,7 +443,10 @@ fn meta_size(size: u64) -> impl IntoElement {
|
|||
}
|
||||
|
||||
fn provider_badge(provider: ProviderId) -> Element {
|
||||
badge(Icon::new(provider).size(12.).into_element(), provider.to_string())
|
||||
badge(
|
||||
Icon::new(provider).size(12.).into_element(),
|
||||
provider.to_string(),
|
||||
)
|
||||
}
|
||||
|
||||
fn local_badge() -> Element {
|
||||
|
|
|
|||
|
|
@ -128,7 +128,7 @@ impl Component for PlayerModel {
|
|||
|
||||
let (w, h) = (ctx.size.width, ctx.size.height);
|
||||
|
||||
if w <= 0.0 || h <= 0.0 {
|
||||
if w <= 0.0 || h <= 0.0 {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -49,7 +49,9 @@ impl Component for RecentsRow {
|
|||
conf.on_creation(OnCreation::Run);
|
||||
conf.on_change(OnChange::Rerun);
|
||||
let total = CARD_MS + (count.saturating_sub(1) as u64) * STAGGER_MS;
|
||||
AnimNum::new(0., 1.).time(total.max(1)).function(Function::Linear)
|
||||
AnimNum::new(0., 1.)
|
||||
.time(total.max(1))
|
||||
.function(Function::Linear)
|
||||
});
|
||||
let progress = intro.get().value();
|
||||
|
||||
|
|
@ -112,11 +114,7 @@ impl Component for ClusterCard {
|
|||
let eased = 1.0 - (1.0 - local).powi(3);
|
||||
let rise = (1.0 - eased) * CARD_RISE_PX;
|
||||
|
||||
let title = format!(
|
||||
"{} {}",
|
||||
self.cluster.mc_version,
|
||||
self.cluster.mc_loader
|
||||
);
|
||||
let title = format!("{} {}", self.cluster.mc_version, self.cluster.mc_loader);
|
||||
|
||||
let cluster_id = self.cluster.id;
|
||||
let on_press = move |_| {
|
||||
|
|
|
|||
|
|
@ -102,11 +102,15 @@ impl Component for ScreenshotViewer {
|
|||
.height(Size::fill())
|
||||
.child(preview),
|
||||
)
|
||||
.child(chevron_btn(IconType::ArrowRight, has_next, move |_| {
|
||||
if idx + 1 < len {
|
||||
index.set(idx + 1);
|
||||
}
|
||||
})),
|
||||
.child(chevron_btn(
|
||||
IconType::ArrowRight,
|
||||
has_next,
|
||||
move |_| {
|
||||
if idx + 1 < len {
|
||||
index.set(idx + 1);
|
||||
}
|
||||
},
|
||||
)),
|
||||
)
|
||||
.child(
|
||||
rect()
|
||||
|
|
@ -119,7 +123,9 @@ impl Component for ScreenshotViewer {
|
|||
.secondary()
|
||||
.on_press(move |_| {
|
||||
if let Some(dir) = open_path.parent() {
|
||||
crate::platform::open_url(&dir.to_string_lossy());
|
||||
crate::platform::open_url(
|
||||
&dir.to_string_lossy(),
|
||||
);
|
||||
}
|
||||
})
|
||||
.child(Icon::new(IconType::Folder).size(14.))
|
||||
|
|
@ -198,8 +204,12 @@ fn header_row(
|
|||
len: usize,
|
||||
on_close: impl Into<EventHandler<Event<PressEventData>>>,
|
||||
) -> impl IntoElement {
|
||||
let mut meta = format!("{} • {}", format_size(info.size_bytes), fmt_date(info.created));
|
||||
|
||||
let mut meta = format!(
|
||||
"{} • {}",
|
||||
format_size(info.size_bytes),
|
||||
fmt_date(info.created)
|
||||
);
|
||||
|
||||
if let Some(res) = info.resolution {
|
||||
meta = format!("{} • {}", format_res(res), meta);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -314,7 +314,11 @@ impl ScrollArea {
|
|||
let shift = *shift_held.read();
|
||||
|
||||
let h_delta = if horizontal {
|
||||
if shift && e.delta_x == 0.0 { e.delta_y } else { e.delta_x }
|
||||
if shift && e.delta_x == 0.0 {
|
||||
e.delta_y
|
||||
} else {
|
||||
e.delta_x
|
||||
}
|
||||
} else {
|
||||
0.0
|
||||
};
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ impl TabItem {
|
|||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[allow(dead_code)]
|
||||
pub fn count_text(mut self, text: impl Into<String>) -> Self {
|
||||
self.count = Some(text.into());
|
||||
self
|
||||
|
|
|
|||
|
|
@ -32,7 +32,6 @@ impl Variant {
|
|||
Self::Message
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
fn entry_icon(entry: &InboxEntry) -> (IconType, Color) {
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ impl Component for UpdatePromptOverlay {
|
|||
let Some(prompt) = snapshot.pending_prompt.clone() else {
|
||||
return rect().into_element();
|
||||
};
|
||||
|
||||
|
||||
if prompt.kind != PromptKind::Update {
|
||||
return rect().into_element();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,2 +1,2 @@
|
|||
pub const APP_ID: &str = "org.polyfrost.OneClient";
|
||||
pub const WINDOW_TITLE: &str = "OneClient";
|
||||
pub const WINDOW_TITLE: &str = "OneClient";
|
||||
|
|
|
|||
|
|
@ -42,6 +42,13 @@ impl BridgeDispatch {
|
|||
self.send(BridgeCommand::MarkOnboardingSeen);
|
||||
}
|
||||
|
||||
pub fn accept_tos(&self, terms_version: u32, privacy_version: u32) {
|
||||
self.send(BridgeCommand::AcceptTos {
|
||||
terms_version,
|
||||
privacy_version,
|
||||
});
|
||||
}
|
||||
|
||||
pub fn import_launcher(
|
||||
&self,
|
||||
source: oneclient_core::MigrationSource,
|
||||
|
|
@ -64,9 +71,7 @@ impl BridgeDispatch {
|
|||
}
|
||||
|
||||
pub fn create_settings_profile(&self, name: impl Into<String>) {
|
||||
self.send(BridgeCommand::CreateSettingsProfile {
|
||||
name: name.into(),
|
||||
});
|
||||
self.send(BridgeCommand::CreateSettingsProfile { name: name.into() });
|
||||
}
|
||||
|
||||
pub fn create_profile_from_global(
|
||||
|
|
@ -94,20 +99,11 @@ impl BridgeDispatch {
|
|||
}
|
||||
|
||||
pub fn delete_named_profile(&self, name: impl Into<String>) {
|
||||
self.send(BridgeCommand::DeleteNamedProfile {
|
||||
name: name.into(),
|
||||
});
|
||||
self.send(BridgeCommand::DeleteNamedProfile { name: name.into() });
|
||||
}
|
||||
|
||||
pub fn update_cluster_profile(
|
||||
&self,
|
||||
cluster_id: ClusterId,
|
||||
update: ProfileUpdate,
|
||||
) {
|
||||
self.send(BridgeCommand::UpdateClusterProfile {
|
||||
cluster_id,
|
||||
update,
|
||||
});
|
||||
pub fn update_cluster_profile(&self, cluster_id: ClusterId, update: ProfileUpdate) {
|
||||
self.send(BridgeCommand::UpdateClusterProfile { cluster_id, update });
|
||||
}
|
||||
|
||||
pub fn create_and_assign_cluster_profile(
|
||||
|
|
@ -121,11 +117,7 @@ impl BridgeDispatch {
|
|||
});
|
||||
}
|
||||
|
||||
pub fn set_cluster_loader_version(
|
||||
&self,
|
||||
cluster_id: ClusterId,
|
||||
version: impl Into<String>,
|
||||
) {
|
||||
pub fn set_cluster_loader_version(&self, cluster_id: ClusterId, version: impl Into<String>) {
|
||||
self.send(BridgeCommand::SetClusterLoaderVersion {
|
||||
cluster_id,
|
||||
version: version.into(),
|
||||
|
|
@ -318,6 +310,7 @@ impl NotificationBuilder {
|
|||
}
|
||||
|
||||
pub fn send(self) {
|
||||
self.dispatch.send(BridgeCommand::SendNotification { spec: self.spec });
|
||||
self.dispatch
|
||||
.send(BridgeCommand::SendNotification { spec: self.spec });
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,30 +19,30 @@ pub use queries::{
|
|||
CancelMicrosoftLoginKeys, CancelMicrosoftLoginMutation, ClusterAction, ClusterBundles,
|
||||
ClusterLogsQuery, FinishMicrosoftLoginMutation, LogAction, LogContentQuery, MigrationQuery,
|
||||
OnboardingBundlesQuery, RefreshAccountKeys, RemoveAccountKeys, ScreenshotAction,
|
||||
SetDefaultAccountKeys, UploadLogKeys, UploadLogMutation, UseLogAction, UseRefreshAccount,
|
||||
UseRemoveAccount, UseScreenshotAction, UseSetDefaultAccount, UseUploadLog, VERSIONS_PAGE_SIZE,
|
||||
accounts_have_microsoft, bundle_overrides_map, bundles_with_status_items, category_list,
|
||||
changelog_error, changelog_groups, changelog_is_loading, cluster_content_items,
|
||||
SetDefaultAccountKeys, TermsQuery, UploadLogKeys, UploadLogMutation, UseLogAction,
|
||||
UseRefreshAccount, UseRemoveAccount, UseScreenshotAction, UseSetDefaultAccount, UseUploadLog,
|
||||
VERSIONS_PAGE_SIZE, accounts_have_microsoft, bundle_overrides_map, bundles_with_status_items,
|
||||
category_list, changelog_error, changelog_groups, changelog_is_loading, cluster_content_items,
|
||||
content_type_for_slug, has_migration_data, invalidate_cluster_queries, invalidate_java_queries,
|
||||
invalidate_logs_queries, invalidate_profile_queries, invalidate_screenshots_queries,
|
||||
java_runtimes, loader_versions, login_code_already_handled, migration_detection,
|
||||
mutation_error, mutation_is_pending, onboarding_bundles_items, package_meta_batch,
|
||||
pick_version_metadata, project_detail, provider_versions, reset_login_code_dedup, search_items,
|
||||
search_pending, search_total, try_account, try_accounts, try_cluster_analytics,
|
||||
try_cluster_logs, try_cluster_screenshots, try_default_account, try_game_profile,
|
||||
try_global_analytics, try_log_content, use_account, use_accounts, use_add_microsoft_account,
|
||||
use_add_offline_account, use_begin_microsoft_login, use_bundle_overrides, use_bundle_updates,
|
||||
use_bundles_with_status, use_cached_image, use_cancel_microsoft_login, use_changelog,
|
||||
use_cluster_analytics, use_cluster_content, use_cluster_logs, use_cluster_mutation,
|
||||
use_cluster_profile, use_cluster_screenshots, use_cluster_settings, use_clusters,
|
||||
use_current_account, use_default_account, use_finish_microsoft_login, use_game_profile,
|
||||
use_global_analytics, use_java_runtimes, use_loader_versions, use_local_image, use_log_action,
|
||||
use_log_content, use_migration, use_named_profiles, use_onboarding_bundles,
|
||||
use_package_categories, use_package_meta_batch, use_package_project, use_package_search,
|
||||
use_package_versions, use_player_profile, use_player_skin, use_provider_versions,
|
||||
use_refresh_account, use_refresh_all_accounts, use_remove_account, use_screenshot_action,
|
||||
use_set_default_account, use_upload_log, use_version_metadata, use_versions, version_list,
|
||||
versions_total,
|
||||
search_pending, search_total, terms_document, terms_error, terms_is_loading, try_account,
|
||||
try_accounts, try_cluster_analytics, try_cluster_logs, try_cluster_screenshots,
|
||||
try_default_account, try_game_profile, try_global_analytics, try_log_content, use_account,
|
||||
use_accounts, use_add_microsoft_account, use_add_offline_account, use_begin_microsoft_login,
|
||||
use_bundle_overrides, use_bundle_updates, use_bundles_with_status, use_cached_image,
|
||||
use_cancel_microsoft_login, use_changelog, use_cluster_analytics, use_cluster_content,
|
||||
use_cluster_logs, use_cluster_mutation, use_cluster_profile, use_cluster_screenshots,
|
||||
use_cluster_settings, use_clusters, use_current_account, use_default_account,
|
||||
use_finish_microsoft_login, use_game_profile, use_global_analytics, use_java_runtimes,
|
||||
use_loader_versions, use_local_image, use_log_action, use_log_content, use_migration,
|
||||
use_named_profiles, use_onboarding_bundles, use_package_categories, use_package_meta_batch,
|
||||
use_package_project, use_package_search, use_package_versions, use_player_profile,
|
||||
use_player_skin, use_provider_versions, use_refresh_account, use_refresh_all_accounts,
|
||||
use_remove_account, use_screenshot_action, use_set_default_account, use_terms, use_upload_log,
|
||||
use_version_metadata, use_versions, version_list, versions_total,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ use freya::query::{Query, QueryCapability, QueryStateData, UseQuery, use_query};
|
|||
use oneclient_core::LauncherError;
|
||||
use oneclient_core::game::{Analytics, cluster_analytics, global_analytics};
|
||||
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||
pub struct GlobalAnalyticsKeys;
|
||||
|
||||
|
|
@ -33,7 +32,6 @@ pub fn try_global_analytics(query: &UseQuery<GlobalAnalyticsQuery>) -> Option<An
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||
pub struct ClusterAnalyticsKeys {
|
||||
pub cluster_id: i64,
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
|
||||
use std::sync::Mutex;
|
||||
|
||||
use freya::query::{
|
||||
|
|
|
|||
|
|
@ -52,7 +52,9 @@ pub fn onboarding_bundles_items(
|
|||
let reader = query.read();
|
||||
match &*reader.state() {
|
||||
QueryStateData::Settled { res: Ok(list), .. } => Some(list.clone()),
|
||||
QueryStateData::Loading { res: Some(Ok(list)) } => Some(list.clone()),
|
||||
QueryStateData::Loading {
|
||||
res: Some(Ok(list)),
|
||||
} => Some(list.clone()),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
|
@ -75,12 +77,8 @@ impl QueryCapability for BundlesWithStatusQuery {
|
|||
async fn run(&self, keys: &Self::Keys) -> Result<Self::Ok, Self::Err> {
|
||||
let _ = keys;
|
||||
let state = LauncherState::get()?;
|
||||
get_bundles_with_update_status(
|
||||
self.cluster_id,
|
||||
state.bundles.as_ref(),
|
||||
&state.services,
|
||||
)
|
||||
.await
|
||||
get_bundles_with_update_status(self.cluster_id, state.bundles.as_ref(), &state.services)
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -90,7 +88,9 @@ pub fn bundles_with_status_items(
|
|||
let reader = query.read();
|
||||
match &*reader.state() {
|
||||
QueryStateData::Settled { res: Ok(list), .. } => list.clone(),
|
||||
QueryStateData::Loading { res: Some(Ok(list)) } => list.clone(),
|
||||
QueryStateData::Loading {
|
||||
res: Some(Ok(list)),
|
||||
} => list.clone(),
|
||||
_ => Vec::new(),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
use freya::query::{Query, QueryCapability, QueryStateData, UseQuery, use_query};
|
||||
use oneclient_core::{fetch_changelog, parse_changelog, ChangelogGroup, LauncherError, LauncherState};
|
||||
use oneclient_core::{
|
||||
ChangelogGroup, LauncherError, LauncherState, fetch_changelog, parse_changelog,
|
||||
};
|
||||
|
||||
use crate::hooks::use_settings_snapshot;
|
||||
|
||||
|
|
@ -32,15 +34,14 @@ pub fn use_changelog() -> UseQuery<ChangelogQuery> {
|
|||
.trim()
|
||||
.to_string();
|
||||
|
||||
use_query(Query::new(
|
||||
ChangelogKeys { meta_url_base },
|
||||
ChangelogQuery,
|
||||
))
|
||||
use_query(Query::new(ChangelogKeys { meta_url_base }, ChangelogQuery))
|
||||
}
|
||||
|
||||
pub fn changelog_groups(query: &UseQuery<ChangelogQuery>) -> Option<Vec<ChangelogGroup>> {
|
||||
match &*query.read().state() {
|
||||
QueryStateData::Settled { res: Ok(groups), .. } => Some(groups.clone()),
|
||||
QueryStateData::Settled {
|
||||
res: Ok(groups), ..
|
||||
} => Some(groups.clone()),
|
||||
QueryStateData::Loading {
|
||||
res: Some(Ok(groups)),
|
||||
} => Some(groups.clone()),
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
|
||||
use freya::query::{Query, QueryCapability, QueryStateData, UseQuery, use_query};
|
||||
use oneclient_core::packages::{ContentType, PackageStore};
|
||||
use oneclient_core::{LauncherError, LauncherState, LinkedArtifactInfo};
|
||||
|
|
@ -51,7 +50,9 @@ pub fn cluster_content_items(query: &UseQuery<ClusterContentQuery>) -> Vec<Linke
|
|||
let reader = query.read();
|
||||
match &*reader.state() {
|
||||
QueryStateData::Settled { res: Ok(list), .. } => list.clone(),
|
||||
QueryStateData::Loading { res: Some(Ok(list)) } => list.clone(),
|
||||
QueryStateData::Loading {
|
||||
res: Some(Ok(list)),
|
||||
} => list.clone(),
|
||||
_ => Vec::new(),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
use freya::query::{Query, QueryCapability, UseQuery, use_query};
|
||||
use oneclient_core::clusters::{Cluster, ClusterManager};
|
||||
use oneclient_core::LauncherError;
|
||||
use oneclient_core::LauncherState;
|
||||
use oneclient_core::clusters::{Cluster, ClusterManager};
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||
pub struct ListClustersQuery;
|
||||
|
|
@ -23,5 +23,3 @@ impl QueryCapability for ListClustersQuery {
|
|||
pub fn use_clusters() -> UseQuery<ListClustersQuery> {
|
||||
use_query(Query::new(ListClustersKeys, ListClustersQuery))
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,4 @@
|
|||
use freya::query::{
|
||||
QueriesStorage, Query, QueryCapability, QueryStateData, UseQuery, use_query,
|
||||
};
|
||||
use freya::query::{QueriesStorage, Query, QueryCapability, QueryStateData, UseQuery, use_query};
|
||||
use oneclient_core::LauncherState;
|
||||
use oneclient_core::java::{AvailableJava, JavaManager, JavaRuntime, JavaVendor};
|
||||
|
||||
|
|
@ -31,7 +29,9 @@ pub fn java_runtimes(query: &UseQuery<ListJavaRuntimesQuery>) -> Vec<JavaRuntime
|
|||
let reader = query.read();
|
||||
match &*reader.state() {
|
||||
QueryStateData::Settled { res: Ok(list), .. } => list.clone(),
|
||||
QueryStateData::Loading { res: Some(Ok(list)) } => list.clone(),
|
||||
QueryStateData::Loading {
|
||||
res: Some(Ok(list)),
|
||||
} => list.clone(),
|
||||
_ => Vec::new(),
|
||||
}
|
||||
}
|
||||
|
|
@ -68,14 +68,14 @@ pub fn use_provider_versions(vendor: JavaVendor) -> UseQuery<ProviderVersionsQue
|
|||
))
|
||||
}
|
||||
|
||||
pub fn provider_versions(
|
||||
query: &UseQuery<ProviderVersionsQuery>,
|
||||
) -> (Vec<AvailableJava>, bool) {
|
||||
pub fn provider_versions(query: &UseQuery<ProviderVersionsQuery>) -> (Vec<AvailableJava>, bool) {
|
||||
let reader = query.read();
|
||||
match &*reader.state() {
|
||||
QueryStateData::Settled { res: Ok(list), .. } => (list.clone(), false),
|
||||
QueryStateData::Settled { res: Err(_), .. } => (Vec::new(), false),
|
||||
QueryStateData::Loading { res: Some(Ok(list)) } => (list.clone(), true),
|
||||
QueryStateData::Loading {
|
||||
res: Some(Ok(list)),
|
||||
} => (list.clone(), true),
|
||||
_ => (Vec::new(), true),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -89,7 +89,9 @@ where
|
|||
{
|
||||
match &*query.read().state() {
|
||||
QueryStateData::Settled { res: Ok(value), .. } => Some(value.clone()),
|
||||
QueryStateData::Loading { res: Some(Ok(value)) } => Some(value.clone()),
|
||||
QueryStateData::Loading {
|
||||
res: Some(Ok(value)),
|
||||
} => Some(value.clone()),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ mod player_profile;
|
|||
mod screenshots;
|
||||
mod settings_profiles;
|
||||
mod skin;
|
||||
mod tos;
|
||||
mod version_metadata;
|
||||
mod versions;
|
||||
|
||||
|
|
@ -69,5 +70,6 @@ pub use settings_profiles::{
|
|||
use_named_profiles,
|
||||
};
|
||||
pub use skin::use_player_skin;
|
||||
pub use tos::{TermsQuery, terms_document, terms_error, terms_is_loading, use_terms};
|
||||
pub use version_metadata::{pick_version_metadata, use_version_metadata};
|
||||
pub use versions::{loader_versions, use_loader_versions, use_versions};
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
|
||||
use std::path::PathBuf;
|
||||
|
||||
use freya::query::{Mutation, MutationCapability, QueriesStorage, UseMutation, use_mutation};
|
||||
|
|
@ -37,9 +36,18 @@ pub struct ClusterMutation;
|
|||
|
||||
#[derive(Clone, PartialEq, Eq, Hash)]
|
||||
pub enum ClusterAction {
|
||||
ToggleArtifact { cluster_id: ClusterId, hash: String },
|
||||
RemoveArtifact { cluster_id: ClusterId, hash: String },
|
||||
RemoveBundlePackageFromDisk { cluster_id: ClusterId, hash: String },
|
||||
ToggleArtifact {
|
||||
cluster_id: ClusterId,
|
||||
hash: String,
|
||||
},
|
||||
RemoveArtifact {
|
||||
cluster_id: ClusterId,
|
||||
hash: String,
|
||||
},
|
||||
RemoveBundlePackageFromDisk {
|
||||
cluster_id: ClusterId,
|
||||
hash: String,
|
||||
},
|
||||
SetBundlePackageEnabled {
|
||||
cluster_id: ClusterId,
|
||||
bundle_name: String,
|
||||
|
|
@ -106,10 +114,14 @@ impl MutationCapability for ClusterMutation {
|
|||
ClusterAction::SetDedicatedDir {
|
||||
cluster_id,
|
||||
dedicated,
|
||||
} => oneclient_core::clusters::ClusterManager::set_dedicated_dir(
|
||||
&state, *cluster_id, *dedicated,
|
||||
)
|
||||
.await,
|
||||
} => {
|
||||
oneclient_core::clusters::ClusterManager::set_dedicated_dir(
|
||||
&state,
|
||||
*cluster_id,
|
||||
*dedicated,
|
||||
)
|
||||
.await
|
||||
}
|
||||
};
|
||||
result.map_err(|e| e.to_string())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
|
||||
use freya::query::{Query, QueryCapability, QueryStateData, UseQuery, use_query};
|
||||
use oneclient_core::packages::domain::GameLoader;
|
||||
use oneclient_core::packages::types::{
|
||||
|
|
@ -48,7 +47,8 @@ impl QueryCapability for PackageSearchQuery {
|
|||
&SearchFilters {
|
||||
query: (!keys.query.trim().is_empty()).then(|| keys.query.trim().to_string()),
|
||||
content_type: Some(keys.content_type),
|
||||
game_versions: (!keys.game_versions.is_empty()).then(|| keys.game_versions.clone()),
|
||||
game_versions: (!keys.game_versions.is_empty())
|
||||
.then(|| keys.game_versions.clone()),
|
||||
loaders: (!keys.loaders.is_empty()).then(|| keys.loaders.clone()),
|
||||
categories: (!keys.categories.is_empty()).then(|| keys.categories.clone()),
|
||||
sort: Some(keys.sort),
|
||||
|
|
@ -91,7 +91,9 @@ pub fn search_items(query: &UseQuery<PackageSearchQuery>) -> Vec<ProjectSummary>
|
|||
let reader = query.read();
|
||||
match &*reader.state() {
|
||||
QueryStateData::Settled { res: Ok(page), .. } => page.items.clone(),
|
||||
QueryStateData::Loading { res: Some(Ok(page)) } => page.items.clone(),
|
||||
QueryStateData::Loading {
|
||||
res: Some(Ok(page)),
|
||||
} => page.items.clone(),
|
||||
_ => Vec::new(),
|
||||
}
|
||||
}
|
||||
|
|
@ -100,13 +102,18 @@ pub fn search_total(query: &UseQuery<PackageSearchQuery>) -> usize {
|
|||
let reader = query.read();
|
||||
match &*reader.state() {
|
||||
QueryStateData::Settled { res: Ok(page), .. } => page.total,
|
||||
QueryStateData::Loading { res: Some(Ok(page)) } => page.total,
|
||||
QueryStateData::Loading {
|
||||
res: Some(Ok(page)),
|
||||
} => page.total,
|
||||
_ => 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn search_pending(query: &UseQuery<PackageSearchQuery>) -> bool {
|
||||
matches!(&*query.read().state(), QueryStateData::Pending | QueryStateData::Loading { .. })
|
||||
matches!(
|
||||
&*query.read().state(),
|
||||
QueryStateData::Pending | QueryStateData::Loading { .. }
|
||||
)
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||
|
|
@ -126,7 +133,9 @@ impl QueryCapability for PackageProjectQuery {
|
|||
async fn run(&self, keys: &Self::Keys) -> Result<Self::Ok, Self::Err> {
|
||||
let state = LauncherState::get()?;
|
||||
let provider = state.services.packages.get(keys.provider)?;
|
||||
provider.get_project(&keys.project_id, &state.services).await
|
||||
provider
|
||||
.get_project(&keys.project_id, &state.services)
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -146,8 +155,12 @@ pub fn use_package_project(
|
|||
pub fn project_detail(query: &UseQuery<PackageProjectQuery>) -> Option<ProjectDetail> {
|
||||
let reader = query.read();
|
||||
match &*reader.state() {
|
||||
QueryStateData::Settled { res: Ok(detail), .. } => Some(detail.clone()),
|
||||
QueryStateData::Loading { res: Some(Ok(detail)) } => Some(detail.clone()),
|
||||
QueryStateData::Settled {
|
||||
res: Ok(detail), ..
|
||||
} => Some(detail.clone()),
|
||||
QueryStateData::Loading {
|
||||
res: Some(Ok(detail)),
|
||||
} => Some(detail.clone()),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
|
@ -260,7 +273,9 @@ pub fn version_list(query: &UseQuery<PackageVersionsQuery>) -> Vec<VersionSummar
|
|||
let reader = query.read();
|
||||
match &*reader.state() {
|
||||
QueryStateData::Settled { res: Ok(page), .. } => page.items.clone(),
|
||||
QueryStateData::Loading { res: Some(Ok(page)) } => page.items.clone(),
|
||||
QueryStateData::Loading {
|
||||
res: Some(Ok(page)),
|
||||
} => page.items.clone(),
|
||||
_ => Vec::new(),
|
||||
}
|
||||
}
|
||||
|
|
@ -269,7 +284,9 @@ pub fn versions_total(query: &UseQuery<PackageVersionsQuery>) -> usize {
|
|||
let reader = query.read();
|
||||
match &*reader.state() {
|
||||
QueryStateData::Settled { res: Ok(page), .. } => page.total,
|
||||
QueryStateData::Loading { res: Some(Ok(page)) } => page.total,
|
||||
QueryStateData::Loading {
|
||||
res: Some(Ok(page)),
|
||||
} => page.total,
|
||||
_ => 0,
|
||||
}
|
||||
}
|
||||
|
|
@ -315,7 +332,9 @@ pub fn category_list(query: &UseQuery<PackageCategoriesQuery>) -> Vec<String> {
|
|||
let reader = query.read();
|
||||
match &*reader.state() {
|
||||
QueryStateData::Settled { res: Ok(list), .. } => list.clone(),
|
||||
QueryStateData::Loading { res: Some(Ok(list)) } => list.clone(),
|
||||
QueryStateData::Loading {
|
||||
res: Some(Ok(list)),
|
||||
} => list.clone(),
|
||||
_ => Vec::new(),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -39,7 +39,10 @@ impl QueryCapability for FetchPlayerProfileQuery {
|
|||
// }
|
||||
}
|
||||
|
||||
pub fn use_player_profile(uuid: impl Into<String>, access_token: Option<impl Into<String>>) -> UseQuery<FetchPlayerProfileQuery> {
|
||||
pub fn use_player_profile(
|
||||
uuid: impl Into<String>,
|
||||
access_token: Option<impl Into<String>>,
|
||||
) -> UseQuery<FetchPlayerProfileQuery> {
|
||||
use_query(
|
||||
Query::new(
|
||||
PlayerProfileQueryKeys {
|
||||
|
|
|
|||
|
|
@ -12,7 +12,6 @@ use tokio::sync::Semaphore;
|
|||
|
||||
static LOCAL_IMAGE_SEMAPHORE: OnceLock<Arc<Semaphore>> = OnceLock::new();
|
||||
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||
pub struct ClusterScreenshotsKeys {
|
||||
pub cluster_id: i64,
|
||||
|
|
@ -96,7 +95,6 @@ pub fn try_cluster_screenshots(
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
pub async fn invalidate_screenshots_queries() {
|
||||
QueriesStorage::<ClusterScreenshotsQuery>::try_invalidate_all().await;
|
||||
QueriesStorage::<LocalImageQuery>::try_invalidate_all().await;
|
||||
|
|
|
|||
|
|
@ -42,4 +42,4 @@ fn java_string_hash(s: &str) -> i32 {
|
|||
h = h.wrapping_mul(31).wrapping_add(c as i32);
|
||||
}
|
||||
h
|
||||
}
|
||||
}
|
||||
|
|
|
|||
61
packages/oneclient_app/src/hooks/queries/tos.rs
Normal file
61
packages/oneclient_app/src/hooks/queries/tos.rs
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
use freya::query::{Query, QueryCapability, QueryStateData, UseQuery, use_query};
|
||||
use oneclient_core::{LauncherError, LauncherState, TermsDocument, fetch_terms};
|
||||
|
||||
use crate::hooks::use_settings_snapshot;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||
pub struct TermsKeys {
|
||||
pub meta_url_base: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||
pub struct TermsQuery;
|
||||
|
||||
impl QueryCapability for TermsQuery {
|
||||
type Ok = TermsDocument;
|
||||
type Err = LauncherError;
|
||||
type Keys = TermsKeys;
|
||||
|
||||
async fn run(&self, _keys: &Self::Keys) -> Result<Self::Ok, Self::Err> {
|
||||
let state = LauncherState::get()?;
|
||||
fetch_terms(&state.services).await
|
||||
}
|
||||
}
|
||||
|
||||
pub fn use_terms() -> UseQuery<TermsQuery> {
|
||||
let settings = use_settings_snapshot().settings;
|
||||
let meta_url_base = settings
|
||||
.custom_meta_url_base
|
||||
.clone()
|
||||
.unwrap_or_default()
|
||||
.trim()
|
||||
.to_string();
|
||||
|
||||
use_query(Query::new(TermsKeys { meta_url_base }, TermsQuery))
|
||||
}
|
||||
|
||||
pub fn terms_document(query: &UseQuery<TermsQuery>) -> Option<TermsDocument> {
|
||||
match &*query.read().state() {
|
||||
QueryStateData::Settled {
|
||||
res: Ok(document), ..
|
||||
} => Some(document.clone()),
|
||||
QueryStateData::Loading {
|
||||
res: Some(Ok(document)),
|
||||
} => Some(document.clone()),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn terms_error(query: &UseQuery<TermsQuery>) -> Option<String> {
|
||||
match &*query.read().state() {
|
||||
QueryStateData::Settled { res: Err(err), .. } => Some(err.to_string()),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn terms_is_loading(query: &UseQuery<TermsQuery>) -> bool {
|
||||
matches!(
|
||||
&*query.read().state(),
|
||||
QueryStateData::Pending | QueryStateData::Loading { res: None }
|
||||
)
|
||||
}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
use freya::query::QueryStateData;
|
||||
use oneclient_core::packages::domain::GameLoader;
|
||||
use oneclient_core::VersionMetadata;
|
||||
use oneclient_core::packages::domain::GameLoader;
|
||||
|
||||
use super::use_versions;
|
||||
|
||||
|
|
@ -46,7 +46,9 @@ pub fn use_version_metadata(
|
|||
let state = reader.state();
|
||||
let list = match &*state {
|
||||
QueryStateData::Settled { res: Ok(list), .. } => list,
|
||||
QueryStateData::Loading { res: Some(Ok(list)) } => list,
|
||||
QueryStateData::Loading {
|
||||
res: Some(Ok(list)),
|
||||
} => list,
|
||||
_ => return None,
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -69,7 +69,9 @@ pub fn loader_versions(query: &UseQuery<LoaderVersionsQuery>) -> Vec<String> {
|
|||
let reader = query.read();
|
||||
match &*reader.state() {
|
||||
QueryStateData::Settled { res: Ok(list), .. } => list.clone(),
|
||||
QueryStateData::Loading { res: Some(Ok(list)) } => list.clone(),
|
||||
QueryStateData::Loading {
|
||||
res: Some(Ok(list)),
|
||||
} => list.clone(),
|
||||
_ => Vec::new(),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,38 +5,38 @@ use super::{use_dispatch, use_settings_snapshot};
|
|||
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct PersistedView {
|
||||
pub layout: State<ViewLayout>,
|
||||
pub sort: State<Option<String>>,
|
||||
pub layout: State<ViewLayout>,
|
||||
pub sort: State<Option<String>>,
|
||||
}
|
||||
|
||||
pub fn use_view_state(key: &str) -> PersistedView {
|
||||
let key = key.to_string();
|
||||
let settings = use_settings_snapshot().settings;
|
||||
let dispatch = use_dispatch();
|
||||
let key = key.to_string();
|
||||
let settings = use_settings_snapshot().settings;
|
||||
let dispatch = use_dispatch();
|
||||
|
||||
let initial = settings.view_state(&key);
|
||||
let init_layout = initial.layout;
|
||||
let init_sort = initial.sort;
|
||||
let layout = use_state(move || init_layout);
|
||||
let sort = use_state(move || init_sort.clone());
|
||||
let initial = settings.view_state(&key);
|
||||
let init_layout = initial.layout;
|
||||
let init_sort = initial.sort;
|
||||
let layout = use_state(move || init_layout);
|
||||
let sort = use_state(move || init_sort.clone());
|
||||
|
||||
let mut first = use_state(|| true);
|
||||
use_side_effect(move || {
|
||||
let next = ViewState {
|
||||
layout: *layout.read(),
|
||||
sort: sort.read().clone(),
|
||||
};
|
||||
if *first.peek() {
|
||||
first.set(false);
|
||||
return;
|
||||
}
|
||||
if settings.view_state(&key) == next {
|
||||
return;
|
||||
}
|
||||
let mut updated = settings.clone();
|
||||
updated.set_view_state(key.clone(), next);
|
||||
dispatch.set_settings(updated);
|
||||
});
|
||||
let mut first = use_state(|| true);
|
||||
use_side_effect(move || {
|
||||
let next = ViewState {
|
||||
layout: *layout.read(),
|
||||
sort: sort.read().clone(),
|
||||
};
|
||||
if *first.peek() {
|
||||
first.set(false);
|
||||
return;
|
||||
}
|
||||
if settings.view_state(&key) == next {
|
||||
return;
|
||||
}
|
||||
let mut updated = settings.clone();
|
||||
updated.set_view_state(key.clone(), next);
|
||||
dispatch.set_settings(updated);
|
||||
});
|
||||
|
||||
PersistedView { layout, sort }
|
||||
PersistedView { layout, sort }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,8 +9,8 @@ use skia_safe::sampling_options::CubicResampler;
|
|||
|
||||
use crate::Route;
|
||||
use crate::components::Button;
|
||||
use crate::layout::AnimatedAppOutlet;
|
||||
use crate::components::{AppNavbar, DynamicArt, Icon, IconType, OverlayPopup, ScrollArea};
|
||||
use crate::layout::AnimatedAppOutlet;
|
||||
use crate::theme;
|
||||
use crate::use_settings_snapshot;
|
||||
use oneclient_db::models::ClusterId;
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ impl ClusterViewShellTab {
|
|||
match self {
|
||||
Self::Overview => "Overview",
|
||||
Self::Logs => "Logs",
|
||||
Self::GameLog => "Game Log",
|
||||
Self::GameLog => "Live Log",
|
||||
Self::Screenshots => "Screenshots",
|
||||
Self::Mods => "Mods",
|
||||
Self::Shaders => "Shaders",
|
||||
|
|
|
|||
|
|
@ -6,8 +6,8 @@ mod root_layout;
|
|||
mod settings_shell;
|
||||
|
||||
pub use animated_outlet::AnimatedAppOutlet;
|
||||
pub use app_shell::{AppShell, HOME_BACKGROUND_ASSET};
|
||||
pub(crate) use app_shell::gradient_overlay_radial;
|
||||
pub use app_shell::{AppShell, HOME_BACKGROUND_ASSET};
|
||||
pub use cluster_shell::{ClusterShell, cluster_content};
|
||||
pub use onboarding_shell::OnboardingShell;
|
||||
pub use root_layout::RootLayout;
|
||||
|
|
|
|||
|
|
@ -198,7 +198,10 @@ const SEARCH_INDEX: &[SearchItem] = &[
|
|||
icon: IconType::Globe01,
|
||||
title: "Language",
|
||||
description: "Choose the launcher language.",
|
||||
keywords: &["language", "locale", "english", "spanish", "french", "german", "russian", "japanese", "chinese"],
|
||||
keywords: &[
|
||||
"language", "locale", "english", "spanish", "french", "german", "russian", "japanese",
|
||||
"chinese",
|
||||
],
|
||||
route: Route::SettingsLanguage {},
|
||||
},
|
||||
// Developer
|
||||
|
|
@ -332,10 +335,7 @@ const GROUPS: &[SettingsGroup] = &[
|
|||
},
|
||||
SettingsGroup {
|
||||
label: "ABOUT",
|
||||
tabs: &[
|
||||
SettingsTab::DeveloperOptions,
|
||||
SettingsTab::Changelog,
|
||||
],
|
||||
tabs: &[SettingsTab::DeveloperOptions, SettingsTab::Changelog],
|
||||
},
|
||||
];
|
||||
|
||||
|
|
@ -490,18 +490,20 @@ fn search_results(query: String, mut search: State<String>) -> Vec<Element> {
|
|||
matches.truncate(SEARCH_RESULTS_MAX);
|
||||
|
||||
if matches.is_empty() {
|
||||
return vec![rect()
|
||||
.width(Size::fill())
|
||||
.padding(Gaps::new_symmetric(10., 14.))
|
||||
.corner_radius(CornerRadius::new_all(12.))
|
||||
.background(colors::page_elevated())
|
||||
.child(
|
||||
label()
|
||||
.text("No settings matched your search.")
|
||||
.font_size(12.)
|
||||
.color(colors::fg_secondary()),
|
||||
)
|
||||
.into_element()];
|
||||
return vec![
|
||||
rect()
|
||||
.width(Size::fill())
|
||||
.padding(Gaps::new_symmetric(10., 14.))
|
||||
.corner_radius(CornerRadius::new_all(12.))
|
||||
.background(colors::page_elevated())
|
||||
.child(
|
||||
label()
|
||||
.text("No settings matched your search.")
|
||||
.font_size(12.)
|
||||
.color(colors::fg_secondary()),
|
||||
)
|
||||
.into_element(),
|
||||
];
|
||||
}
|
||||
|
||||
let mut out: Vec<Element> = Vec::with_capacity(matches.len() + 1);
|
||||
|
|
@ -696,11 +698,11 @@ impl Component for SidebarInfo {
|
|||
.spacing(4.)
|
||||
.font_size(12.)
|
||||
.color(colors::fg_secondary())
|
||||
.children(items.into_iter().map(|item| {
|
||||
label()
|
||||
.text(item)
|
||||
.into_element()
|
||||
}))
|
||||
.children(
|
||||
items
|
||||
.into_iter()
|
||||
.map(|item| label().text(item).into_element()),
|
||||
)
|
||||
.on_pointer_enter(|_| Cursor::set(CursorIcon::Pointer))
|
||||
.on_pointer_leave(|_| Cursor::set(CursorIcon::default()))
|
||||
.on_press(copy_to_clipboard)
|
||||
|
|
|
|||
|
|
@ -12,8 +12,8 @@ mod notifications;
|
|||
pub mod platform;
|
||||
mod routes;
|
||||
pub mod theme;
|
||||
pub mod updater;
|
||||
mod ui;
|
||||
pub mod updater;
|
||||
pub(crate) mod utils;
|
||||
mod view;
|
||||
|
||||
|
|
|
|||
|
|
@ -39,12 +39,15 @@ fn main() {
|
|||
let _tokio_guard = rt.enter();
|
||||
|
||||
let settings = rt.block_on(oneclient_core::settings::store::load_settings(None));
|
||||
|
||||
|
||||
if settings.log_debug {
|
||||
oneclient_core::logger::init_debug()
|
||||
} else {
|
||||
oneclient_core::logger::init()
|
||||
}.expect("Failed to initialize logger");
|
||||
}
|
||||
.expect("Failed to initialize logger");
|
||||
|
||||
let _sentry_guard = oneclient_core::reporting::init(settings.crash_reporting);
|
||||
|
||||
let (bridge, handle): (OneClientBridge, CoreBridgeHandle) = OneClientBridge::new();
|
||||
handle.spawn_runtime();
|
||||
|
|
@ -67,7 +70,7 @@ fn main() {
|
|||
std::env::var("ONECLIENT_GPU_CACHE_MB")
|
||||
.ok()
|
||||
.and_then(|v| v.parse::<usize>().ok())
|
||||
.unwrap_or(32)
|
||||
.unwrap_or(32),
|
||||
)
|
||||
.with_default_font(theme::DEFAULT_FONT);
|
||||
|
||||
|
|
|
|||
|
|
@ -17,13 +17,13 @@ use crate::view::{
|
|||
ClusterShaders, ClusterTextures, ProcessLogs,
|
||||
},
|
||||
settings::{
|
||||
SettingsApis, SettingsAppearance, SettingsChangelog, SettingsDeveloper,
|
||||
SettingsJava, SettingsLanguage, SettingsLauncher, SettingsMinecraft,
|
||||
SettingsApis, SettingsAppearance, SettingsChangelog, SettingsDeveloper, SettingsJava,
|
||||
SettingsLanguage, SettingsLauncher, SettingsMinecraft,
|
||||
},
|
||||
},
|
||||
onboarding::{
|
||||
OnboardingAccount, OnboardingBundles, OnboardingDownloading, OnboardingLanguage,
|
||||
OnboardingMigration, OnboardingPreferences, OnboardingWelcome,
|
||||
OnboardingMigration, OnboardingPreferences, OnboardingTerms, OnboardingWelcome,
|
||||
},
|
||||
};
|
||||
|
||||
|
|
@ -37,6 +37,8 @@ pub enum Route {
|
|||
#[layout(OnboardingShell)]
|
||||
#[route("/onboarding")]
|
||||
OnboardingWelcome {},
|
||||
#[route("/onboarding/terms")]
|
||||
OnboardingTerms {},
|
||||
#[route("/onboarding/migration")]
|
||||
OnboardingMigration {},
|
||||
#[route("/onboarding/language")]
|
||||
|
|
@ -120,6 +122,7 @@ impl Route {
|
|||
match self {
|
||||
Route::Startup { .. } => "Startup".to_string(),
|
||||
Route::OnboardingWelcome { .. } => "Welcome".to_string(),
|
||||
Route::OnboardingTerms { .. } => "Terms & Privacy".to_string(),
|
||||
Route::OnboardingMigration { .. } => "Migration".to_string(),
|
||||
Route::OnboardingLanguage { .. } => "Language".to_string(),
|
||||
Route::OnboardingAccount { .. } => "Account".to_string(),
|
||||
|
|
|
|||
|
|
@ -149,4 +149,4 @@ pub fn selection_bg() -> Color {
|
|||
|
||||
pub fn toast_action() -> Color {
|
||||
Color::from_rgb(155, 161, 166)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,14 +17,12 @@ pub fn border_all(width: f32) -> Border {
|
|||
}
|
||||
|
||||
pub fn border_all_color(width: f32, color: Color) -> Border {
|
||||
Border::new()
|
||||
.fill(color)
|
||||
.width(BorderWidth {
|
||||
top: width,
|
||||
right: width,
|
||||
bottom: width,
|
||||
left: width,
|
||||
})
|
||||
Border::new().fill(color).width(BorderWidth {
|
||||
top: width,
|
||||
right: width,
|
||||
bottom: width,
|
||||
left: width,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn border_left(width: f32) -> Border {
|
||||
|
|
@ -79,4 +77,4 @@ pub fn entrance_motion_layer(
|
|||
.child(child),
|
||||
)
|
||||
.into_element()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -55,10 +55,7 @@ fn check_for_update() -> anyhow::Result<Option<Update>> {
|
|||
Ok(check_update(current, config)?)
|
||||
}
|
||||
|
||||
async fn download_and_install(
|
||||
update: Update,
|
||||
notifier: NotificationService,
|
||||
) -> anyhow::Result<()> {
|
||||
async fn download_and_install(update: Update, notifier: NotificationService) -> anyhow::Result<()> {
|
||||
let progress_id = Uuid::new_v4();
|
||||
let version = update.version.clone();
|
||||
let label = format!("Downloading OneClient {version}");
|
||||
|
|
|
|||
|
|
@ -16,7 +16,10 @@ pub fn group_clusters_by_major(clusters: &[Cluster]) -> ClusterGroups {
|
|||
let Some(parsed) = parse_mc_version(&cluster.mc_version) else {
|
||||
continue;
|
||||
};
|
||||
groups.entry(parsed.major).or_default().push(cluster.clone());
|
||||
groups
|
||||
.entry(parsed.major)
|
||||
.or_default()
|
||||
.push(cluster.clone());
|
||||
}
|
||||
|
||||
for list in groups.values_mut() {
|
||||
|
|
@ -82,9 +85,7 @@ pub fn resolve_cluster(
|
|||
.iter()
|
||||
.find(|cluster| {
|
||||
let version_ok = minor.is_none_or(|minor| {
|
||||
parse_mc_version(&cluster.mc_version)
|
||||
.and_then(|parsed| parsed.minor)
|
||||
== Some(minor)
|
||||
parse_mc_version(&cluster.mc_version).and_then(|parsed| parsed.minor) == Some(minor)
|
||||
});
|
||||
let loader_ok = loader.is_none_or(|loader| cluster.mc_loader == loader);
|
||||
version_ok && loader_ok
|
||||
|
|
@ -241,21 +242,22 @@ fn version_sort_key(cluster: &Cluster) -> (u32, u32) {
|
|||
.unwrap_or((0, 0))
|
||||
}
|
||||
|
||||
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
pub fn is_wayland() -> bool {
|
||||
false
|
||||
false
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
pub fn is_wayland() -> bool {
|
||||
static IS_WAYLAND: OnceLock<bool> = OnceLock::new();
|
||||
static IS_WAYLAND: OnceLock<bool> = OnceLock::new();
|
||||
|
||||
*IS_WAYLAND.get_or_init(|| {
|
||||
if cfg!(target_os = "linux") {
|
||||
std::env::var("XDG_SESSION_TYPE").map(|v| v == "wayland").unwrap_or(false)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
})
|
||||
}
|
||||
*IS_WAYLAND.get_or_init(|| {
|
||||
if cfg!(target_os = "linux") {
|
||||
std::env::var("XDG_SESSION_TYPE")
|
||||
.map(|v| v == "wayland")
|
||||
.unwrap_or(false)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
use chrono::Utc;
|
||||
use chrono::{DateTime, Utc};
|
||||
use freya::animation::{AnimNum, Function, OnFinish, use_animation};
|
||||
use freya::prelude::*;
|
||||
use freya::query::{MutationCapability, MutationStateData, UseMutation};
|
||||
use oneclient_core::auth::{AccountKind, MinecraftAccount};
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::components::{
|
||||
Avatar, Button, Icon, IconType, OverlayPopup, PlayerModel, ScrollArea, TextInput,
|
||||
|
|
@ -74,13 +76,17 @@ impl Component for Accounts {
|
|||
let mut rows: Vec<Element> = accounts
|
||||
.iter()
|
||||
.map(|account| {
|
||||
account_row(
|
||||
account,
|
||||
Some(account.id) == default_id,
|
||||
AccountRow {
|
||||
id: account.id,
|
||||
username: account.username.clone(),
|
||||
kind: account.kind,
|
||||
expires: account.expires,
|
||||
is_default: Some(account.id) == default_id,
|
||||
set_default,
|
||||
remove,
|
||||
refresh,
|
||||
)
|
||||
}
|
||||
.into_element()
|
||||
})
|
||||
.collect();
|
||||
if rows.is_empty() {
|
||||
|
|
@ -339,113 +345,182 @@ fn hint_line(icon: IconType, text: String, color: Color) -> impl IntoElement {
|
|||
.into_element()
|
||||
}
|
||||
|
||||
fn account_row(
|
||||
account: &MinecraftAccount,
|
||||
const REFRESH_SPIN_TIME: u64 = 800;
|
||||
|
||||
const REFRESHING_OPACITY: f32 = 0.7;
|
||||
|
||||
struct AccountRow {
|
||||
id: Uuid,
|
||||
username: String,
|
||||
kind: AccountKind,
|
||||
expires: DateTime<Utc>,
|
||||
is_default: bool,
|
||||
set_default: crate::hooks::UseSetDefaultAccount,
|
||||
remove: crate::hooks::UseRemoveAccount,
|
||||
refresh: crate::hooks::UseRefreshAccount,
|
||||
) -> Element {
|
||||
let id = account.id;
|
||||
let is_microsoft = account.is_microsoft();
|
||||
let expired = is_microsoft && account.expires <= Utc::now();
|
||||
}
|
||||
|
||||
let border_color = if expired {
|
||||
colors::danger()
|
||||
} else if is_default {
|
||||
colors::brand()
|
||||
} else {
|
||||
colors::component_border()
|
||||
};
|
||||
impl PartialEq for AccountRow {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.id == other.id
|
||||
&& self.username == other.username
|
||||
&& self.kind == other.kind
|
||||
&& self.expires == other.expires
|
||||
&& self.is_default == other.is_default
|
||||
}
|
||||
}
|
||||
|
||||
rect()
|
||||
.horizontal()
|
||||
.width(Size::fill())
|
||||
.cross_align(Alignment::Center)
|
||||
.content(Content::Flex)
|
||||
.spacing(16.)
|
||||
.padding(Gaps::new_all(12.))
|
||||
.corner_radius(CornerRadius::new_all(12.))
|
||||
.background(colors::page_elevated())
|
||||
.border(border_all_color(1., border_color))
|
||||
.a11y_role(AccessibilityRole::Button)
|
||||
.maybe(!is_default, |el| {
|
||||
el.on_press(move |_| set_default.mutate(SetDefaultAccountKeys { id: Some(id) }))
|
||||
})
|
||||
.child(
|
||||
Avatar::new(id.to_string())
|
||||
.width(Size::px(40.))
|
||||
.height(Size::px(40.)),
|
||||
)
|
||||
.child(
|
||||
rect()
|
||||
.vertical()
|
||||
.width(Size::flex(1.0))
|
||||
.spacing(4.)
|
||||
.child(
|
||||
rect()
|
||||
.horizontal()
|
||||
.cross_align(Alignment::Center)
|
||||
.spacing(8.)
|
||||
.child(
|
||||
label()
|
||||
.text(account.username.clone())
|
||||
.font_size(16.)
|
||||
.font_weight(FontWeight::MEDIUM)
|
||||
.color(colors::fg_primary()),
|
||||
)
|
||||
.child(kind_badge(account.kind))
|
||||
.maybe_child(is_default.then(default_badge))
|
||||
.maybe_child(expired.then(expired_badge)),
|
||||
)
|
||||
.child(
|
||||
label()
|
||||
.text(id.to_string())
|
||||
.font_size(11.)
|
||||
.color(colors::fg_secondary()),
|
||||
),
|
||||
)
|
||||
.maybe_child(is_microsoft.then(|| {
|
||||
Button::new()
|
||||
.ghost()
|
||||
.icon()
|
||||
.on_press(move |e: Event<PressEventData>| {
|
||||
e.stop_propagation();
|
||||
refresh.mutate(RefreshAccountKeys { id });
|
||||
})
|
||||
.child(
|
||||
Icon::new(IconType::RefreshCw01)
|
||||
.size(18.)
|
||||
.color(if expired {
|
||||
colors::danger()
|
||||
} else {
|
||||
colors::fg_secondary()
|
||||
}),
|
||||
)
|
||||
.into_element()
|
||||
}))
|
||||
.child(
|
||||
Button::new().ghost().icon().enabled(false).child(
|
||||
Icon::new(IconType::Pencil01)
|
||||
.size(18.)
|
||||
.color(colors::fg_secondary()),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
Button::new()
|
||||
.ghost()
|
||||
.icon()
|
||||
.on_press(move |e: Event<PressEventData>| {
|
||||
e.stop_propagation();
|
||||
remove.mutate(RemoveAccountKeys { id });
|
||||
})
|
||||
.child(
|
||||
Icon::new(IconType::Trash01)
|
||||
impl Component for AccountRow {
|
||||
fn render_key(&self) -> DiffKey {
|
||||
DiffKey::from(&self.id)
|
||||
}
|
||||
|
||||
fn render(&self) -> impl IntoElement {
|
||||
let id = self.id;
|
||||
let is_default = self.is_default;
|
||||
let set_default = self.set_default;
|
||||
let remove = self.remove;
|
||||
let refresh = self.refresh;
|
||||
|
||||
let is_microsoft = self.kind == AccountKind::Microsoft;
|
||||
let expired = is_microsoft && self.expires <= Utc::now();
|
||||
|
||||
let mut refreshing = use_state(|| false);
|
||||
let is_refreshing = *refreshing.read();
|
||||
|
||||
let spin = use_animation(|conf| {
|
||||
conf.on_finish(OnFinish::restart());
|
||||
AnimNum::new(0., 360.)
|
||||
.time(REFRESH_SPIN_TIME)
|
||||
.function(Function::Linear)
|
||||
});
|
||||
|
||||
use_side_effect_with_deps(&is_refreshing, move |&is_refreshing| {
|
||||
let mut spin = spin;
|
||||
if is_refreshing {
|
||||
spin.start();
|
||||
} else {
|
||||
spin.reset();
|
||||
}
|
||||
});
|
||||
|
||||
let rotation = if is_refreshing {
|
||||
spin.get().value()
|
||||
} else {
|
||||
0.
|
||||
};
|
||||
|
||||
let border_color = if expired {
|
||||
colors::danger()
|
||||
} else if is_default {
|
||||
colors::brand()
|
||||
} else {
|
||||
colors::component_border()
|
||||
};
|
||||
|
||||
rect()
|
||||
.horizontal()
|
||||
.width(Size::fill())
|
||||
.cross_align(Alignment::Center)
|
||||
.content(Content::Flex)
|
||||
.spacing(16.)
|
||||
.padding(Gaps::new_all(12.))
|
||||
.corner_radius(CornerRadius::new_all(12.))
|
||||
.background(colors::page_elevated())
|
||||
.border(border_all_color(1., border_color))
|
||||
.opacity(if is_refreshing {
|
||||
REFRESHING_OPACITY
|
||||
} else {
|
||||
1.
|
||||
})
|
||||
.a11y_role(AccessibilityRole::Button)
|
||||
.maybe(!is_default, |el| {
|
||||
el.on_press(move |_| set_default.mutate(SetDefaultAccountKeys { id: Some(id) }))
|
||||
})
|
||||
.child(
|
||||
Avatar::new(id.to_string())
|
||||
.width(Size::px(40.))
|
||||
.height(Size::px(40.)),
|
||||
)
|
||||
.child(
|
||||
rect()
|
||||
.vertical()
|
||||
.width(Size::flex(1.0))
|
||||
.spacing(4.)
|
||||
.child(
|
||||
rect()
|
||||
.horizontal()
|
||||
.cross_align(Alignment::Center)
|
||||
.spacing(8.)
|
||||
.child(
|
||||
label()
|
||||
.text(self.username.clone())
|
||||
.font_size(16.)
|
||||
.font_weight(FontWeight::MEDIUM)
|
||||
.color(colors::fg_primary()),
|
||||
)
|
||||
.child(kind_badge(self.kind))
|
||||
.maybe_child(is_default.then(default_badge))
|
||||
.maybe_child(expired.then(expired_badge)),
|
||||
)
|
||||
.child(
|
||||
label()
|
||||
.text(id.to_string())
|
||||
.font_size(11.)
|
||||
.color(colors::fg_secondary()),
|
||||
),
|
||||
)
|
||||
.maybe_child(is_microsoft.then(|| {
|
||||
Button::new()
|
||||
.ghost()
|
||||
.icon()
|
||||
.on_press(move |e: Event<PressEventData>| {
|
||||
e.stop_propagation();
|
||||
if *refreshing.peek() {
|
||||
return;
|
||||
}
|
||||
refreshing.set(true);
|
||||
spawn(async move {
|
||||
refresh.mutate_async(RefreshAccountKeys { id }).await;
|
||||
refreshing.set(false);
|
||||
});
|
||||
})
|
||||
.child(
|
||||
rect().rotate(rotation).child(
|
||||
Icon::new(IconType::RefreshCw01)
|
||||
.size(18.)
|
||||
.color(if expired {
|
||||
colors::danger()
|
||||
} else {
|
||||
colors::fg_secondary()
|
||||
}),
|
||||
),
|
||||
)
|
||||
.into_element()
|
||||
}))
|
||||
.child(
|
||||
Button::new().ghost().icon().enabled(false).child(
|
||||
Icon::new(IconType::Pencil01)
|
||||
.size(18.)
|
||||
.color(colors::fg_secondary()),
|
||||
),
|
||||
)
|
||||
.into_element()
|
||||
)
|
||||
.child(
|
||||
Button::new()
|
||||
.ghost()
|
||||
.icon()
|
||||
.on_press(move |e: Event<PressEventData>| {
|
||||
e.stop_propagation();
|
||||
remove.mutate(RemoveAccountKeys { id });
|
||||
})
|
||||
.child(
|
||||
Icon::new(IconType::Trash01)
|
||||
.size(18.)
|
||||
.color(colors::fg_secondary()),
|
||||
),
|
||||
)
|
||||
.into_element()
|
||||
}
|
||||
}
|
||||
|
||||
fn kind_badge(kind: AccountKind) -> impl IntoElement {
|
||||
|
|
|
|||
|
|
@ -62,7 +62,10 @@ impl Component for WhenChart {
|
|||
)
|
||||
}
|
||||
WhenMode::Hour => {
|
||||
let hour = self.peak_hour.map(format_hour).unwrap_or_else(|| "—".to_string());
|
||||
let hour = self
|
||||
.peak_hour
|
||||
.map(format_hour)
|
||||
.unwrap_or_else(|| "—".to_string());
|
||||
(
|
||||
format!("Peak around {hour}"),
|
||||
BarChart::new(self.per_hour.clone(), hour_labels())
|
||||
|
|
@ -148,7 +151,12 @@ impl Component for DailyChart {
|
|||
let win_total: i64 = values.iter().sum();
|
||||
let subtitle = match (slice.first(), slice.last()) {
|
||||
(Some((a, _)), Some((b, _))) => {
|
||||
format!("{} · {} – {}", format_duration_hm(win_total), format_day(*a), format_day(*b))
|
||||
format!(
|
||||
"{} · {} – {}",
|
||||
format_duration_hm(win_total),
|
||||
format_day(*a),
|
||||
format_day(*b)
|
||||
)
|
||||
}
|
||||
_ => format_duration_hm(win_total),
|
||||
};
|
||||
|
|
@ -159,24 +167,16 @@ impl Component for DailyChart {
|
|||
.horizontal()
|
||||
.cross_align(Alignment::Center)
|
||||
.spacing(8.)
|
||||
.child(nav_button(
|
||||
IconType::ChevronsLeft,
|
||||
can_older,
|
||||
move |_| {
|
||||
if can_older {
|
||||
*offset.write() = off + 1;
|
||||
}
|
||||
},
|
||||
))
|
||||
.child(nav_button(
|
||||
IconType::ChevronsRight,
|
||||
can_newer,
|
||||
move |_| {
|
||||
if can_newer {
|
||||
*offset.write() = off - 1;
|
||||
}
|
||||
},
|
||||
))
|
||||
.child(nav_button(IconType::ChevronsLeft, can_older, move |_| {
|
||||
if can_older {
|
||||
*offset.write() = off + 1;
|
||||
}
|
||||
}))
|
||||
.child(nav_button(IconType::ChevronsRight, can_newer, move |_| {
|
||||
if can_newer {
|
||||
*offset.write() = off - 1;
|
||||
}
|
||||
}))
|
||||
.child(
|
||||
SegmentedControl::new(range)
|
||||
.height(30.)
|
||||
|
|
@ -272,6 +272,12 @@ fn weekday_labels() -> Vec<String> {
|
|||
|
||||
fn hour_labels() -> Vec<String> {
|
||||
(0..24)
|
||||
.map(|h| if h % 6 == 0 { format_hour(h) } else { String::new() })
|
||||
.map(|h| {
|
||||
if h % 6 == 0 {
|
||||
format_hour(h)
|
||||
} else {
|
||||
String::new()
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -45,11 +45,7 @@ fn charts_grid(cards: Vec<Element>) -> Element {
|
|||
let mut grid = rect().vertical().width(Size::fill()).spacing(16.);
|
||||
for pair in cards.chunks(2) {
|
||||
if pair.len() == 1 {
|
||||
grid = grid.child(
|
||||
rect()
|
||||
.width(Size::fill())
|
||||
.child(pair[0].clone()),
|
||||
);
|
||||
grid = grid.child(rect().width(Size::fill()).child(pair[0].clone()));
|
||||
continue;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -12,7 +12,12 @@ use super::{card, card_header};
|
|||
|
||||
pub(super) fn servers_section(servers: &[ServerStat]) -> Element {
|
||||
let total_joins: i64 = servers.iter().map(|s| s.joins).sum();
|
||||
let max_secs = servers.iter().map(|s| s.total_secs).max().unwrap_or(0).max(1);
|
||||
let max_secs = servers
|
||||
.iter()
|
||||
.map(|s| s.total_secs)
|
||||
.max()
|
||||
.unwrap_or(0)
|
||||
.max(1);
|
||||
|
||||
let mut list = rect().vertical().width(Size::fill()).spacing(4.);
|
||||
for server in servers.iter().take(10) {
|
||||
|
|
@ -155,9 +160,12 @@ impl Component for ServerIcon {
|
|||
let reader = query.read();
|
||||
let loaded = match (&self.url, &*reader.state()) {
|
||||
(Some(url), QueryStateData::Settled { res: Ok(bytes), .. })
|
||||
| (Some(url), QueryStateData::Loading { res: Some(Ok(bytes)) }) => {
|
||||
Some((url.clone(), bytes.clone()))
|
||||
}
|
||||
| (
|
||||
Some(url),
|
||||
QueryStateData::Loading {
|
||||
res: Some(Ok(bytes)),
|
||||
},
|
||||
) => Some((url.clone(), bytes.clone())),
|
||||
_ => None,
|
||||
};
|
||||
|
||||
|
|
@ -185,7 +193,11 @@ fn server_icon_placeholder(is_ip: bool, size: f32) -> Element {
|
|||
.height(Size::px(size))
|
||||
.corner_radius(CornerRadius::new_all(6.))
|
||||
.background(colors::component_bg())
|
||||
.child(Icon::new(icon).size(size * 0.5).color(colors::fg_secondary()))
|
||||
.child(
|
||||
Icon::new(icon)
|
||||
.size(size * 0.5)
|
||||
.color(colors::fg_secondary()),
|
||||
)
|
||||
.into_element()
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3,9 +3,9 @@ use freya::prelude::*;
|
|||
use oneclient_core::game::{Analytics, Persona};
|
||||
|
||||
use crate::components::{Icon, IconType};
|
||||
use crate::utils::format_duration_hm;
|
||||
use crate::theme::colors;
|
||||
use crate::ui::border_all_color;
|
||||
use crate::utils::format_duration_hm;
|
||||
|
||||
use super::card;
|
||||
|
||||
|
|
@ -19,10 +19,22 @@ pub(super) fn tiles_row(analytics: &Analytics) -> Element {
|
|||
let total_joins: i64 = analytics.servers.iter().map(|s| s.joins).sum();
|
||||
|
||||
let mut tiles = vec![
|
||||
stat_tile(IconType::ClockRewind, "Total playtime", format_duration_hm(stats.total_secs)),
|
||||
stat_tile(
|
||||
IconType::ClockRewind,
|
||||
"Total playtime",
|
||||
format_duration_hm(stats.total_secs),
|
||||
),
|
||||
stat_tile(IconType::Play, "Sessions", stats.session_count.to_string()),
|
||||
stat_tile(IconType::Calendar, "Avg / session", format_duration_hm(avg_session)),
|
||||
stat_tile(IconType::Rocket02, "Active days", stats.active_days.to_string()),
|
||||
stat_tile(
|
||||
IconType::Calendar,
|
||||
"Avg / session",
|
||||
format_duration_hm(avg_session),
|
||||
),
|
||||
stat_tile(
|
||||
IconType::Rocket02,
|
||||
"Active days",
|
||||
stats.active_days.to_string(),
|
||||
),
|
||||
];
|
||||
if !analytics.servers.is_empty() {
|
||||
tiles.push(stat_tile(
|
||||
|
|
@ -97,7 +109,11 @@ fn persona_card(persona: Persona) -> Element {
|
|||
.corner_radius(CornerRadius::new_all(22.))
|
||||
.background(colors::brand().with_a(40))
|
||||
.center()
|
||||
.child(Icon::new(persona_icon(persona)).size(22.).color(colors::brand())),
|
||||
.child(
|
||||
Icon::new(persona_icon(persona))
|
||||
.size(22.)
|
||||
.color(colors::brand()),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
rect()
|
||||
|
|
|
|||
|
|
@ -1,17 +1,19 @@
|
|||
use super::*;
|
||||
|
||||
use freya::router::RouterContext;
|
||||
use oneclient_core::packages::types::ProjectSummary;
|
||||
use oneclient_core::packages::ProviderId;
|
||||
use oneclient_core::packages::types::ProjectSummary;
|
||||
|
||||
use crate::components::{
|
||||
Icon, IconType,
|
||||
};
|
||||
use crate::components::{Icon, IconType};
|
||||
use crate::routes::Route;
|
||||
use crate::theme::colors;
|
||||
use crate::ui::border_all_color;
|
||||
|
||||
pub(super) fn grid_row(row: Vec<ProjectSummary>, cluster_id: i64, package_type: &str) -> impl IntoElement {
|
||||
pub(super) fn grid_row(
|
||||
row: Vec<ProjectSummary>,
|
||||
cluster_id: i64,
|
||||
package_type: &str,
|
||||
) -> impl IntoElement {
|
||||
let package_type = package_type.to_string();
|
||||
let fill = GRID_COLUMNS - row.len();
|
||||
|
||||
|
|
@ -154,7 +156,11 @@ impl Component for PackageCard {
|
|||
}
|
||||
}
|
||||
|
||||
pub(super) fn list_row(item: ProjectSummary, cluster_id: i64, package_type: &str) -> impl IntoElement {
|
||||
pub(super) fn list_row(
|
||||
item: ProjectSummary,
|
||||
cluster_id: i64,
|
||||
package_type: &str,
|
||||
) -> impl IntoElement {
|
||||
ListRow {
|
||||
item,
|
||||
cluster_id,
|
||||
|
|
@ -185,69 +191,69 @@ impl Component for ListRow {
|
|||
Cursor::set(CursorIcon::default());
|
||||
});
|
||||
|
||||
rect()
|
||||
.horizontal()
|
||||
.width(Size::fill())
|
||||
.height(Size::px(LIST_ROW_H))
|
||||
.cross_align(Alignment::Center)
|
||||
.spacing(12.)
|
||||
.padding(Gaps::new_all(16.))
|
||||
.corner_radius(CornerRadius::new_all(10.))
|
||||
.background(CARD_BG)
|
||||
.border(border_all_color(
|
||||
1.,
|
||||
if focused {
|
||||
colors::brand()
|
||||
} else {
|
||||
colors::component_border()
|
||||
},
|
||||
))
|
||||
.overflow(Overflow::Clip)
|
||||
.content(Content::Flex)
|
||||
.a11y_id(a11y_id)
|
||||
.a11y_focusable(true)
|
||||
.a11y_role(AccessibilityRole::Button)
|
||||
.on_pointer_enter(|_| Cursor::set(CursorIcon::Pointer))
|
||||
.on_pointer_leave(|_| Cursor::set(CursorIcon::default()))
|
||||
.on_all_press(move |_| open_package(cluster_id, &package_type, provider, &id))
|
||||
.child(Thumbnail::new(item.icon_url.clone(), 48.).radius(8.))
|
||||
.child(
|
||||
rect()
|
||||
.vertical()
|
||||
.width(Size::flex(1.0))
|
||||
.spacing(3.)
|
||||
.child(
|
||||
rect()
|
||||
.horizontal()
|
||||
.cross_align(Alignment::Center)
|
||||
.spacing(6.)
|
||||
.child(
|
||||
label()
|
||||
.text(item.name.clone())
|
||||
.font_size(15.)
|
||||
.font_weight(FontWeight::MEDIUM)
|
||||
.max_lines(1)
|
||||
.color(CARD_NAME),
|
||||
)
|
||||
.child(
|
||||
label()
|
||||
.text(format!("by {}", item.author))
|
||||
.font_size(10.)
|
||||
.max_lines(1)
|
||||
.color(colors::fg_secondary()),
|
||||
)
|
||||
.child(Icon::new(item.provider).size(12.)),
|
||||
)
|
||||
.child(
|
||||
label()
|
||||
.text(item.summary.clone())
|
||||
.font_size(11.)
|
||||
.max_lines(2)
|
||||
.width(Size::fill())
|
||||
.color(colors::fg_secondary()),
|
||||
),
|
||||
)
|
||||
.child(downloads_row(item.downloads))
|
||||
rect()
|
||||
.horizontal()
|
||||
.width(Size::fill())
|
||||
.height(Size::px(LIST_ROW_H))
|
||||
.cross_align(Alignment::Center)
|
||||
.spacing(12.)
|
||||
.padding(Gaps::new_all(16.))
|
||||
.corner_radius(CornerRadius::new_all(10.))
|
||||
.background(CARD_BG)
|
||||
.border(border_all_color(
|
||||
1.,
|
||||
if focused {
|
||||
colors::brand()
|
||||
} else {
|
||||
colors::component_border()
|
||||
},
|
||||
))
|
||||
.overflow(Overflow::Clip)
|
||||
.content(Content::Flex)
|
||||
.a11y_id(a11y_id)
|
||||
.a11y_focusable(true)
|
||||
.a11y_role(AccessibilityRole::Button)
|
||||
.on_pointer_enter(|_| Cursor::set(CursorIcon::Pointer))
|
||||
.on_pointer_leave(|_| Cursor::set(CursorIcon::default()))
|
||||
.on_all_press(move |_| open_package(cluster_id, &package_type, provider, &id))
|
||||
.child(Thumbnail::new(item.icon_url.clone(), 48.).radius(8.))
|
||||
.child(
|
||||
rect()
|
||||
.vertical()
|
||||
.width(Size::flex(1.0))
|
||||
.spacing(3.)
|
||||
.child(
|
||||
rect()
|
||||
.horizontal()
|
||||
.cross_align(Alignment::Center)
|
||||
.spacing(6.)
|
||||
.child(
|
||||
label()
|
||||
.text(item.name.clone())
|
||||
.font_size(15.)
|
||||
.font_weight(FontWeight::MEDIUM)
|
||||
.max_lines(1)
|
||||
.color(CARD_NAME),
|
||||
)
|
||||
.child(
|
||||
label()
|
||||
.text(format!("by {}", item.author))
|
||||
.font_size(10.)
|
||||
.max_lines(1)
|
||||
.color(colors::fg_secondary()),
|
||||
)
|
||||
.child(Icon::new(item.provider).size(12.)),
|
||||
)
|
||||
.child(
|
||||
label()
|
||||
.text(item.summary.clone())
|
||||
.font_size(11.)
|
||||
.max_lines(2)
|
||||
.width(Size::fill())
|
||||
.color(colors::fg_secondary()),
|
||||
),
|
||||
)
|
||||
.child(downloads_row(item.downloads))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
use freya::animation::{
|
||||
AnimNum, Ease, Function, OnChange, OnCreation,
|
||||
use_animation_with_dependencies,
|
||||
AnimNum, Ease, Function, OnChange, OnCreation, use_animation_with_dependencies,
|
||||
};
|
||||
use freya::prelude::*;
|
||||
use oneclient_core::packages::types::ProjectSummary;
|
||||
|
|
@ -174,7 +173,9 @@ impl Component for Browser {
|
|||
match mode {
|
||||
ViewLayout::Grid => {
|
||||
let rows = BROWSE_PAGE_SIZE.div_ceil(GRID_COLUMNS);
|
||||
sa.lazy(rows, CARD_H, GRID_SPACING, |_| skeleton_grid_row().into_element())
|
||||
sa.lazy(rows, CARD_H, GRID_SPACING, |_| {
|
||||
skeleton_grid_row().into_element()
|
||||
})
|
||||
}
|
||||
ViewLayout::List => sa.lazy(BROWSE_PAGE_SIZE, LIST_ROW_H, LIST_SPACING, |_| {
|
||||
SkeletonListRow.into_element()
|
||||
|
|
@ -289,4 +290,3 @@ fn controls(
|
|||
),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -120,4 +120,3 @@ impl Component for CategoryRow {
|
|||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,6 @@
|
|||
use super::*;
|
||||
|
||||
use freya::animation::{
|
||||
AnimNum, Ease, Function, OnCreation, OnFinish, use_animation,
|
||||
};
|
||||
use freya::animation::{AnimNum, Ease, Function, OnCreation, OnFinish, use_animation};
|
||||
|
||||
use crate::theme::colors;
|
||||
use crate::ui::border_all_color;
|
||||
|
|
|
|||
|
|
@ -3,8 +3,8 @@ use oneclient_core::packages::{ContentType, ProviderId};
|
|||
|
||||
use crate::components::ScrollArea;
|
||||
use crate::hooks::{
|
||||
content_type_for_slug, project_detail, use_browser_compat, use_dispatch,
|
||||
use_link_confirm, use_package_project, use_package_versions, version_list, versions_total,
|
||||
content_type_for_slug, project_detail, use_browser_compat, use_dispatch, use_link_confirm,
|
||||
use_package_project, use_package_versions, version_list, versions_total,
|
||||
};
|
||||
use crate::theme::colors;
|
||||
use crate::ui::border_all_color;
|
||||
|
|
@ -205,4 +205,3 @@ impl Component for BrowserPackage {
|
|||
.into_element()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,14 +1,14 @@
|
|||
use super::*;
|
||||
|
||||
use freya::query::QueryStateData;
|
||||
use oneclient_core::packages::ProviderId;
|
||||
use oneclient_core::packages::types::{
|
||||
GalleryImage, PackageBody, ProjectDetail, ReleaseType, VersionSummary,
|
||||
};
|
||||
use oneclient_core::packages::ProviderId;
|
||||
|
||||
use crate::BridgeDispatch;
|
||||
use crate::components::{Button, Icon, IconType, Segment, SegmentedControl};
|
||||
use crate::hooks::{use_cached_image, VERSIONS_PAGE_SIZE};
|
||||
use crate::hooks::{VERSIONS_PAGE_SIZE, use_cached_image};
|
||||
use crate::theme::colors;
|
||||
use crate::ui::border_all_color;
|
||||
use crate::utils::format_size;
|
||||
|
|
@ -16,7 +16,6 @@ use crate::utils::format_size;
|
|||
const GALLERY_IMAGE_H: f32 = 360.;
|
||||
const GALLERY_EDGE: u32 = 1024;
|
||||
|
||||
|
||||
pub(super) fn loading_body() -> impl IntoElement {
|
||||
rect()
|
||||
.width(Size::fill())
|
||||
|
|
@ -356,4 +355,3 @@ fn release_badge(release_type: ReleaseType) -> impl IntoElement {
|
|||
.color(color),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,16 +1,13 @@
|
|||
use super::*;
|
||||
|
||||
use oneclient_core::packages::types::{
|
||||
ProjectDetail, ProjectMember,
|
||||
};
|
||||
use oneclient_core::packages::ProviderId;
|
||||
use oneclient_core::packages::types::{ProjectDetail, ProjectMember};
|
||||
|
||||
use crate::BridgeDispatch;
|
||||
use crate::components::{Button, Icon, IconType};
|
||||
use crate::theme::colors;
|
||||
use crate::ui::border_all_color;
|
||||
|
||||
|
||||
pub(super) fn sidebar(
|
||||
project: Option<ProjectDetail>,
|
||||
latest_version: Option<String>,
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
|
||||
use freya::prelude::*;
|
||||
use oneclient_core::Patch;
|
||||
use oneclient_core::java::JavaRuntime;
|
||||
|
|
@ -6,7 +5,8 @@ use oneclient_core::packages::domain::GameLoader;
|
|||
use oneclient_core::settings::{GameSettingsProfile, ProfileUpdate, Resolution};
|
||||
|
||||
use crate::components::{
|
||||
Button, Dropdown, Icon, IconType, ScrollArea, TextInput, toggle, toggle_controlled, validate_number
|
||||
Button, Dropdown, Icon, IconType, ScrollArea, TextInput, toggle, toggle_controlled,
|
||||
validate_number,
|
||||
};
|
||||
use crate::hooks::{
|
||||
ClusterAction, java_runtimes, loader_versions, try_game_profile, use_cluster_mutation,
|
||||
|
|
@ -37,7 +37,10 @@ impl Component for ClusterSettings {
|
|||
.as_ref()
|
||||
.map(|c| c.mc_version.clone())
|
||||
.unwrap_or_default();
|
||||
let loader = cluster.as_ref().map(|c| c.mc_loader).unwrap_or(GameLoader::Fabric);
|
||||
let loader = cluster
|
||||
.as_ref()
|
||||
.map(|c| c.mc_loader)
|
||||
.unwrap_or(GameLoader::Fabric);
|
||||
let versions_query = use_loader_versions(mc_version, loader);
|
||||
let runtimes_query = use_java_runtimes();
|
||||
|
||||
|
|
@ -149,13 +152,11 @@ fn reset_button(overridden: bool, on_reset: EventHandler<()>) -> impl IntoElemen
|
|||
|
||||
Button::new()
|
||||
.small()
|
||||
.ghost()
|
||||
.icon()
|
||||
.ghost()
|
||||
.icon()
|
||||
.corner_radius(CornerRadius::new_all(7.))
|
||||
.maybe(overridden, |el| {
|
||||
el.on_press(move |_| on_reset.call(()))
|
||||
})
|
||||
.enabled(overridden)
|
||||
.maybe(overridden, |el| el.on_press(move |_| on_reset.call(())))
|
||||
.enabled(overridden)
|
||||
.child(Icon::new(IconType::RefreshCcw02).size(15.).color(color))
|
||||
.into_element()
|
||||
}
|
||||
|
|
@ -394,7 +395,11 @@ impl Component for ResolutionRow {
|
|||
.text_align(TextAlign::Center)
|
||||
.width(Size::px(70.)),
|
||||
)
|
||||
.child(Icon::new(IconType::X).size(14.).color(colors::fg_secondary()))
|
||||
.child(
|
||||
Icon::new(IconType::X)
|
||||
.size(14.)
|
||||
.color(colors::fg_secondary()),
|
||||
)
|
||||
.child(
|
||||
TextInput::new(height)
|
||||
.placeholder("480")
|
||||
|
|
@ -527,7 +532,12 @@ impl Component for TextRow {
|
|||
.placeholder(placeholder)
|
||||
.width(Size::px(220.));
|
||||
|
||||
settings_row(icon, title, description, override_cell(control, overridden, on_reset))
|
||||
settings_row(
|
||||
icon,
|
||||
title,
|
||||
description,
|
||||
override_cell(control, overridden, on_reset),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -564,8 +574,7 @@ impl Component for JavaRow {
|
|||
.map(runtime_label)
|
||||
.unwrap_or_else(|| "Automatic".into());
|
||||
|
||||
let paths: Vec<String> =
|
||||
runtimes.iter().map(|r| r.absolute_path.clone()).collect();
|
||||
let paths: Vec<String> = runtimes.iter().map(|r| r.absolute_path.clone()).collect();
|
||||
|
||||
let dispatch = dispatch.clone();
|
||||
Dropdown::new(selected, options)
|
||||
|
|
@ -641,7 +650,10 @@ impl Component for LoaderRow {
|
|||
settings_row(
|
||||
IconType::Rocket02,
|
||||
"Loader Version",
|
||||
format!("The {} loader version used to launch this cluster.", self.loader),
|
||||
format!(
|
||||
"The {} loader version used to launch this cluster.",
|
||||
self.loader
|
||||
),
|
||||
control,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -65,7 +65,8 @@ enum Confirm {
|
|||
|
||||
fn kind_badge(kind: LogKind) -> &'static str {
|
||||
match kind {
|
||||
LogKind::Game { .. } => "Launcher",
|
||||
LogKind::Game { .. } => "Game output",
|
||||
LogKind::Launcher => "Launcher logs",
|
||||
LogKind::Minecraft => "Minecraft",
|
||||
LogKind::CrashReport => "Crash report",
|
||||
LogKind::Other => "Other",
|
||||
|
|
@ -151,7 +152,7 @@ impl Component for ClusterLogs {
|
|||
let value = search.read().clone();
|
||||
(!value.trim().is_empty()).then_some(value)
|
||||
};
|
||||
|
||||
|
||||
let content_query = use_log_content(
|
||||
selected_path.clone(),
|
||||
level.read().to_level(),
|
||||
|
|
@ -162,7 +163,7 @@ impl Component for ClusterLogs {
|
|||
let Some(cluster) = load_cluster(cluster_id) else {
|
||||
return cluster_not_found();
|
||||
};
|
||||
let folder = cluster.game_dir().ok().map(|d| d.join("logs"));
|
||||
let folder = cluster.dir().ok().map(|d| d.join("logs"));
|
||||
|
||||
let files = try_cluster_logs(&logs_query).unwrap_or_default();
|
||||
let has_log = !files.is_empty();
|
||||
|
|
|
|||
|
|
@ -1,23 +1,23 @@
|
|||
mod overview;
|
||||
mod logs;
|
||||
mod overview;
|
||||
mod package_manager;
|
||||
|
||||
mod cluster_settings;
|
||||
mod mods;
|
||||
mod process_logs;
|
||||
mod screenshots;
|
||||
mod shaders;
|
||||
mod textures;
|
||||
mod process_logs;
|
||||
|
||||
pub use overview::ClusterOverview;
|
||||
pub use logs::ClusterLogs;
|
||||
pub use overview::ClusterOverview;
|
||||
|
||||
pub use cluster_settings::ClusterSettings;
|
||||
pub use mods::ClusterMods;
|
||||
pub use process_logs::ProcessLogs;
|
||||
pub use screenshots::ClusterScreenshots;
|
||||
pub use shaders::ClusterShaders;
|
||||
pub use textures::ClusterTextures;
|
||||
pub use process_logs::ProcessLogs;
|
||||
|
||||
use freya::prelude::*;
|
||||
use freya::query::QueryStateData;
|
||||
|
|
|
|||
|
|
@ -1,14 +1,14 @@
|
|||
use freya::prelude::*;
|
||||
use oneclient_core::packages::{ContentType, ProviderId};
|
||||
use oneclient_core::packages::ContentType;
|
||||
|
||||
use crate::hooks::{
|
||||
bundle_overrides_map, bundles_with_status_items, cluster_content_items, package_meta_batch,
|
||||
use_bundle_overrides, use_bundles_with_status, use_cluster_content, use_package_meta_batch,
|
||||
bundle_overrides_map, bundles_with_status_items, cluster_content_items, use_bundle_overrides,
|
||||
use_bundles_with_status, use_cluster_content,
|
||||
};
|
||||
use crate::layout::cluster_content;
|
||||
|
||||
use super::package_manager::{
|
||||
PackageManager, bundle_categories, bundle_packages, managed_project_ids,
|
||||
PackageManager, bundle_categories, bundle_packages, use_content_meta,
|
||||
};
|
||||
use super::{cluster_not_found, load_cluster};
|
||||
|
||||
|
|
@ -23,8 +23,8 @@ impl Component for ClusterMods {
|
|||
let bundles = use_bundles_with_status(self.cluster_id);
|
||||
let overrides = use_bundle_overrides(self.cluster_id);
|
||||
let bundle_items = bundles_with_status_items(&bundles);
|
||||
let project_ids = managed_project_ids(&bundle_items, ContentType::Mod);
|
||||
let meta = use_package_meta_batch(ProviderId::Modrinth, project_ids);
|
||||
let content_items = cluster_content_items(&content);
|
||||
let meta = use_content_meta(&content_items, &bundle_items, ContentType::Mod);
|
||||
|
||||
let Some(_cluster) = load_cluster(self.cluster_id) else {
|
||||
return cluster_not_found();
|
||||
|
|
@ -32,10 +32,10 @@ impl Component for ClusterMods {
|
|||
|
||||
let all_categories = bundle_categories(&bundle_items);
|
||||
let items = bundle_packages(
|
||||
cluster_content_items(&content),
|
||||
content_items,
|
||||
&bundle_items,
|
||||
&bundle_overrides_map(&overrides),
|
||||
&package_meta_batch(&meta),
|
||||
&meta,
|
||||
ContentType::Mod,
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
|
||||
use std::collections::{HashMap, HashSet};
|
||||
|
||||
use freya::prelude::*;
|
||||
|
|
@ -6,7 +5,7 @@ use oneclient_core::packages::{CachedPackageMeta, ContentType, ProviderId};
|
|||
use oneclient_core::{BundleFileKind, BundleWithUpdateStatus, LinkedArtifactInfo};
|
||||
|
||||
use crate::components::{CardLayout, PackageEntry};
|
||||
use crate::hooks::use_view_state;
|
||||
use crate::hooks::{package_meta_batch, use_package_meta_batch, use_view_state};
|
||||
|
||||
mod views;
|
||||
use views::{ContentBox, ContentKind, EnabledFilter, SortMode, toolbar_bar};
|
||||
|
|
@ -18,9 +17,13 @@ const GRID_GAP: f32 = 10.;
|
|||
const GRID_MIN_W: f32 = 260.;
|
||||
const LAZY_OVERSCAN: i64 = 2;
|
||||
|
||||
pub fn managed_project_ids(
|
||||
pub type PackageMetaMap = HashMap<(ProviderId, String), CachedPackageMeta>;
|
||||
|
||||
fn provider_project_ids(
|
||||
content: &[LinkedArtifactInfo],
|
||||
bundles: &[BundleWithUpdateStatus],
|
||||
content_type: ContentType,
|
||||
provider: ProviderId,
|
||||
) -> Vec<String> {
|
||||
let mut ids = Vec::new();
|
||||
for bundle in bundles {
|
||||
|
|
@ -28,19 +31,49 @@ pub fn managed_project_ids(
|
|||
if file.content_type() != content_type {
|
||||
continue;
|
||||
}
|
||||
if let BundleFileKind::Managed { project_id, .. } = &file.kind {
|
||||
if let BundleFileKind::Managed {
|
||||
project_id,
|
||||
provider: file_provider,
|
||||
..
|
||||
} = &file.kind
|
||||
&& *file_provider == provider
|
||||
{
|
||||
ids.push(project_id.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
for info in content {
|
||||
if info.content_type != content_type || info.provider != Some(provider) {
|
||||
continue;
|
||||
}
|
||||
if let Some(project_id) = &info.project_id {
|
||||
ids.push(project_id.clone());
|
||||
}
|
||||
}
|
||||
ids
|
||||
}
|
||||
|
||||
pub fn use_content_meta(
|
||||
content: &[LinkedArtifactInfo],
|
||||
bundles: &[BundleWithUpdateStatus],
|
||||
content_type: ContentType,
|
||||
) -> PackageMetaMap {
|
||||
let mut out = PackageMetaMap::new();
|
||||
for provider in ProviderId::REMOTE_PROVIDERS.iter().copied() {
|
||||
let ids = provider_project_ids(content, bundles, content_type, provider);
|
||||
let query = use_package_meta_batch(provider, ids);
|
||||
for (project_id, meta) in package_meta_batch(&query) {
|
||||
out.insert((provider, project_id), meta);
|
||||
}
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
pub fn bundle_packages(
|
||||
content: Vec<LinkedArtifactInfo>,
|
||||
bundles: &[BundleWithUpdateStatus],
|
||||
overrides: &HashMap<(String, String), String>,
|
||||
meta: &HashMap<String, CachedPackageMeta>,
|
||||
meta: &PackageMetaMap,
|
||||
content_type: ContentType,
|
||||
) -> Vec<PackageEntry> {
|
||||
let mut by_project: HashMap<&str, &LinkedArtifactInfo> = HashMap::new();
|
||||
|
|
@ -104,10 +137,7 @@ pub fn bundle_packages(
|
|||
}
|
||||
|
||||
for info in &content {
|
||||
let in_bundle = info
|
||||
.project_id
|
||||
.as_deref()
|
||||
.is_some_and(|p| seen.contains(p))
|
||||
let in_bundle = info.project_id.as_deref().is_some_and(|p| seen.contains(p))
|
||||
|| seen.contains(&info.hash);
|
||||
if in_bundle {
|
||||
continue;
|
||||
|
|
@ -141,10 +171,10 @@ fn make_row(
|
|||
categories: Vec<String>,
|
||||
enabled: bool,
|
||||
installed_info: Option<&LinkedArtifactInfo>,
|
||||
meta: &HashMap<String, CachedPackageMeta>,
|
||||
meta: &PackageMetaMap,
|
||||
fallback_name: String,
|
||||
) -> PackageEntry {
|
||||
let m = meta.get(&package_id);
|
||||
let m = meta.get(&(provider, package_id.clone()));
|
||||
let name = m
|
||||
.map(|p| p.name.clone())
|
||||
.filter(|s| !s.is_empty())
|
||||
|
|
|
|||
|
|
@ -264,7 +264,13 @@ impl Component for FilterPopover {
|
|||
.background(colors::page_elevated().with_a(230))
|
||||
.blur(12.)
|
||||
.border(crate::ui::border_all_color(1., colors::component_border()))
|
||||
.shadow(Shadow::from((0., 8., 32., 0., Color::from_argb(120, 0, 0, 0))))
|
||||
.shadow(Shadow::from((
|
||||
0.,
|
||||
8.,
|
||||
32.,
|
||||
0.,
|
||||
Color::from_argb(120, 0, 0, 0),
|
||||
)))
|
||||
.a11y_id(a11y_id)
|
||||
.a11y_role(AccessibilityRole::Dialog)
|
||||
.on_global_key_down(move |e: Event<KeyboardEventData>| {
|
||||
|
|
@ -527,12 +533,16 @@ impl Component for ContentBox {
|
|||
|
||||
let header = (count > 0)
|
||||
.then(|| match kind {
|
||||
ContentKind::External => Some(
|
||||
action_header(browse_button(cluster_id, package_type)).into_element(),
|
||||
),
|
||||
ContentKind::External => {
|
||||
Some(action_header(browse_button(cluster_id, package_type)).into_element())
|
||||
}
|
||||
ContentKind::Local => Some(
|
||||
action_header(add_from_file_button(cluster_id, content_type, dispatch.clone()))
|
||||
.into_element(),
|
||||
action_header(add_from_file_button(
|
||||
cluster_id,
|
||||
content_type,
|
||||
dispatch.clone(),
|
||||
))
|
||||
.into_element(),
|
||||
),
|
||||
ContentKind::Other => None,
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
|
||||
use freya::prelude::*;
|
||||
|
||||
use crate::components::LogViewer;
|
||||
|
|
@ -30,9 +29,7 @@ impl Component for ProcessLogs {
|
|||
not_running()
|
||||
};
|
||||
|
||||
cluster_content()
|
||||
.child(body)
|
||||
.into_element()
|
||||
cluster_content().child(body).into_element()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -8,8 +8,7 @@ use oneclient_core::settings::ViewLayout;
|
|||
|
||||
use crate::components::{
|
||||
Button, ContextMenu, Icon, IconType, LocalImage, OverlayPopup, ScreenshotViewer, ScrollArea,
|
||||
open_folder_button,
|
||||
Segment, SegmentedControl,
|
||||
Segment, SegmentedControl, open_folder_button,
|
||||
};
|
||||
use crate::hooks::{
|
||||
ScreenshotAction, try_cluster_screenshots, use_cluster_screenshots, use_dispatch,
|
||||
|
|
@ -57,7 +56,14 @@ impl Component for ClusterScreenshots {
|
|||
let grid_width = use_state(|| 0f32);
|
||||
let mut menu = use_state(|| None::<(f32, f32, PathBuf)>);
|
||||
|
||||
let toolbar = toolbar_row(&shots, folder, view_mode, edit_mode, selected, confirm_delete);
|
||||
let toolbar = toolbar_row(
|
||||
&shots,
|
||||
folder,
|
||||
view_mode,
|
||||
edit_mode,
|
||||
selected,
|
||||
confirm_delete,
|
||||
);
|
||||
|
||||
let content: Element = if shots.is_empty() {
|
||||
empty_state(matches!(
|
||||
|
|
@ -210,7 +216,12 @@ fn toolbar_row(
|
|||
.horizontal()
|
||||
.cross_align(Alignment::Center)
|
||||
.spacing(10.)
|
||||
.maybe_child((!editing).then_some(folder).flatten().map(open_folder_button));
|
||||
.maybe_child(
|
||||
(!editing)
|
||||
.then_some(folder)
|
||||
.flatten()
|
||||
.map(open_folder_button),
|
||||
);
|
||||
|
||||
if editing {
|
||||
let select_all_paths = all_paths.clone();
|
||||
|
|
@ -422,8 +433,6 @@ fn confirm_panel(
|
|||
.into_element()
|
||||
}
|
||||
|
||||
|
||||
|
||||
fn res_badge(res: Option<(u32, u32)>) -> Option<Element> {
|
||||
res.map(format_res).map(|text| {
|
||||
rect()
|
||||
|
|
|
|||
|
|
@ -1,14 +1,14 @@
|
|||
use freya::prelude::*;
|
||||
use oneclient_core::packages::{ContentType, ProviderId};
|
||||
use oneclient_core::packages::ContentType;
|
||||
|
||||
use crate::hooks::{
|
||||
bundle_overrides_map, bundles_with_status_items, cluster_content_items, package_meta_batch,
|
||||
use_bundle_overrides, use_bundles_with_status, use_cluster_content, use_package_meta_batch,
|
||||
bundle_overrides_map, bundles_with_status_items, cluster_content_items, use_bundle_overrides,
|
||||
use_bundles_with_status, use_cluster_content,
|
||||
};
|
||||
use crate::layout::cluster_content;
|
||||
|
||||
use super::package_manager::{
|
||||
PackageManager, bundle_categories, bundle_packages, managed_project_ids,
|
||||
PackageManager, bundle_categories, bundle_packages, use_content_meta,
|
||||
};
|
||||
use super::{cluster_not_found, load_cluster};
|
||||
|
||||
|
|
@ -23,8 +23,8 @@ impl Component for ClusterShaders {
|
|||
let bundles = use_bundles_with_status(self.cluster_id);
|
||||
let overrides = use_bundle_overrides(self.cluster_id);
|
||||
let bundle_items = bundles_with_status_items(&bundles);
|
||||
let project_ids = managed_project_ids(&bundle_items, ContentType::Shader);
|
||||
let meta = use_package_meta_batch(ProviderId::Modrinth, project_ids);
|
||||
let content_items = cluster_content_items(&content);
|
||||
let meta = use_content_meta(&content_items, &bundle_items, ContentType::Shader);
|
||||
|
||||
let Some(_cluster) = load_cluster(self.cluster_id) else {
|
||||
return cluster_not_found();
|
||||
|
|
@ -32,10 +32,10 @@ impl Component for ClusterShaders {
|
|||
|
||||
let all_categories = bundle_categories(&bundle_items);
|
||||
let items = bundle_packages(
|
||||
cluster_content_items(&content),
|
||||
content_items,
|
||||
&bundle_items,
|
||||
&bundle_overrides_map(&overrides),
|
||||
&package_meta_batch(&meta),
|
||||
&meta,
|
||||
ContentType::Shader,
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,14 +1,14 @@
|
|||
use freya::prelude::*;
|
||||
use oneclient_core::packages::{ContentType, ProviderId};
|
||||
use oneclient_core::packages::ContentType;
|
||||
|
||||
use crate::hooks::{
|
||||
bundle_overrides_map, bundles_with_status_items, cluster_content_items, package_meta_batch,
|
||||
use_bundle_overrides, use_bundles_with_status, use_cluster_content, use_package_meta_batch,
|
||||
bundle_overrides_map, bundles_with_status_items, cluster_content_items, use_bundle_overrides,
|
||||
use_bundles_with_status, use_cluster_content,
|
||||
};
|
||||
use crate::layout::cluster_content;
|
||||
|
||||
use super::package_manager::{
|
||||
PackageManager, bundle_categories, bundle_packages, managed_project_ids,
|
||||
PackageManager, bundle_categories, bundle_packages, use_content_meta,
|
||||
};
|
||||
use super::{cluster_not_found, load_cluster};
|
||||
|
||||
|
|
@ -23,8 +23,8 @@ impl Component for ClusterTextures {
|
|||
let bundles = use_bundles_with_status(self.cluster_id);
|
||||
let overrides = use_bundle_overrides(self.cluster_id);
|
||||
let bundle_items = bundles_with_status_items(&bundles);
|
||||
let project_ids = managed_project_ids(&bundle_items, ContentType::ResourcePack);
|
||||
let meta = use_package_meta_batch(ProviderId::Modrinth, project_ids);
|
||||
let content_items = cluster_content_items(&content);
|
||||
let meta = use_content_meta(&content_items, &bundle_items, ContentType::ResourcePack);
|
||||
|
||||
let Some(_cluster) = load_cluster(self.cluster_id) else {
|
||||
return cluster_not_found();
|
||||
|
|
@ -32,10 +32,10 @@ impl Component for ClusterTextures {
|
|||
|
||||
let all_categories = bundle_categories(&bundle_items);
|
||||
let items = bundle_packages(
|
||||
cluster_content_items(&content),
|
||||
content_items,
|
||||
&bundle_items,
|
||||
&bundle_overrides_map(&overrides),
|
||||
&package_meta_batch(&meta),
|
||||
&meta,
|
||||
ContentType::ResourcePack,
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ use crate::components::{Button, ClusterLandscapeArt, Dropdown, Icon, IconType, M
|
|||
use crate::hooks::{
|
||||
use_active_cluster_id, use_clusters, use_dispatch, use_game_snapshot, use_version_metadata,
|
||||
};
|
||||
use crate::view::app::launch_button_state;
|
||||
use crate::routes::Route;
|
||||
use crate::theme::colors;
|
||||
use crate::ui::border_all_color;
|
||||
|
|
@ -16,6 +15,7 @@ use crate::utils::{
|
|||
default_loader, default_major, default_minor, group_clusters_by_major, loaders_for_major,
|
||||
major_pretty_name, minor_label, minor_versions, resolve_cluster,
|
||||
};
|
||||
use crate::view::app::launch_button_state;
|
||||
|
||||
const GRID_GAP_PX: f32 = 12.;
|
||||
const MIN_CARD_WIDTH_PX: f32 = 280.;
|
||||
|
|
@ -212,7 +212,10 @@ fn detail_sidebar(
|
|||
.as_ref()
|
||||
.and_then(|m| m.long_description.clone())
|
||||
.unwrap_or_else(|| PLACEHOLDER_VERSION_INFO.to_string());
|
||||
let tags = metadata.as_ref().map(|m| m.tags.clone()).unwrap_or_default();
|
||||
let tags = metadata
|
||||
.as_ref()
|
||||
.map(|m| m.tags.clone())
|
||||
.unwrap_or_default();
|
||||
|
||||
rect()
|
||||
.width(Size::px(SIDEBAR_WIDTH_PX))
|
||||
|
|
@ -225,17 +228,9 @@ fn detail_sidebar(
|
|||
.background(colors::page_elevated())
|
||||
.border(border_all_color(1., colors::component_border()))
|
||||
.overflow(Overflow::Clip)
|
||||
.child(
|
||||
rect()
|
||||
.width(Size::fill())
|
||||
.max_height(Size::px(140.))
|
||||
.child(ClusterLandscapeArt::for_version(
|
||||
major,
|
||||
minor_value,
|
||||
loader_value,
|
||||
false,
|
||||
)),
|
||||
)
|
||||
.child(rect().width(Size::fill()).max_height(Size::px(140.)).child(
|
||||
ClusterLandscapeArt::for_version(major, minor_value, loader_value, false),
|
||||
))
|
||||
.child(
|
||||
rect()
|
||||
.vertical()
|
||||
|
|
@ -274,7 +269,11 @@ fn detail_sidebar(
|
|||
.content(Content::Flex)
|
||||
.main_align(Alignment::Center)
|
||||
.spacing(8.)
|
||||
.child(play_button(cluster_id, dispatch, launch_button_state(&game, cluster_id)))
|
||||
.child(play_button(
|
||||
cluster_id,
|
||||
dispatch,
|
||||
launch_button_state(&game, cluster_id),
|
||||
))
|
||||
.child(view_button(cluster_id, active_id)),
|
||||
),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,14 +1,14 @@
|
|||
mod analytics;
|
||||
mod home;
|
||||
mod accounts;
|
||||
mod analytics;
|
||||
mod clusters;
|
||||
mod debug;
|
||||
mod home;
|
||||
mod skins;
|
||||
mod stats;
|
||||
|
||||
pub mod browser;
|
||||
pub mod settings;
|
||||
pub mod cluster;
|
||||
pub mod settings;
|
||||
|
||||
use crate::bridge::GameSnapshot;
|
||||
use oneclient_core::notification::LaunchStage;
|
||||
|
|
@ -23,10 +23,10 @@ pub fn launch_button_state(game: &GameSnapshot, cluster_id: i64) -> (&'static st
|
|||
}
|
||||
}
|
||||
|
||||
pub(crate) use analytics::analytics_body;
|
||||
pub use accounts::Accounts;
|
||||
pub use home::Home;
|
||||
pub(crate) use analytics::analytics_body;
|
||||
pub use clusters::Clusters;
|
||||
pub use debug::Debug;
|
||||
pub use home::Home;
|
||||
pub use skins::AccountSkins;
|
||||
pub use stats::Stats;
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
use freya::prelude::*;
|
||||
|
||||
use super::settings_page;
|
||||
use crate::components::{IconType, TextInput};
|
||||
use crate::hooks::{use_dispatch, use_settings_snapshot};
|
||||
use super::settings_page;
|
||||
use crate::view::app::settings::{section_header, settings_row};
|
||||
|
||||
fn normalize(value: &str) -> Option<String> {
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
use freya::prelude::*;
|
||||
|
||||
use super::settings_page;
|
||||
use crate::components::{Icon, IconType, toggle};
|
||||
use crate::hooks::{use_dispatch, use_settings_snapshot};
|
||||
use super::settings_page;
|
||||
use crate::theme::colors;
|
||||
use crate::ui::border_all_color;
|
||||
use crate::view::app::settings::settings_row;
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
use freya::prelude::*;
|
||||
|
||||
use super::settings_page;
|
||||
use crate::components::{Icon, IconType};
|
||||
use crate::hooks::{changelog_error, changelog_groups, changelog_is_loading, use_changelog};
|
||||
use super::settings_page;
|
||||
use crate::theme::colors;
|
||||
|
||||
#[derive(PartialEq)]
|
||||
|
|
@ -119,14 +119,16 @@ impl Component for ReleaseCard {
|
|||
.spacing(4.)
|
||||
.padding(Gaps::new(0., 0., 0., 6.))
|
||||
.children(if changes.is_empty() {
|
||||
vec![rect()
|
||||
.child(
|
||||
label()
|
||||
.text("No changes recorded for this version.")
|
||||
.font_size(12.)
|
||||
.color(colors::fg_secondary()),
|
||||
)
|
||||
.into_element()]
|
||||
vec![
|
||||
rect()
|
||||
.child(
|
||||
label()
|
||||
.text("No changes recorded for this version.")
|
||||
.font_size(12.)
|
||||
.color(colors::fg_secondary()),
|
||||
)
|
||||
.into_element(),
|
||||
]
|
||||
} else {
|
||||
changes
|
||||
.into_iter()
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
use freya::prelude::*;
|
||||
use freya::router::RouterContext;
|
||||
|
||||
use super::settings_page;
|
||||
use crate::Route;
|
||||
use crate::components::{Button, IconType, link_button, toggle};
|
||||
use crate::hooks::{use_browser_compat, use_dispatch, use_settings_snapshot};
|
||||
use super::settings_page;
|
||||
use crate::view::app::settings::{section_header, settings_row};
|
||||
|
||||
#[derive(PartialEq)]
|
||||
|
|
@ -67,7 +67,7 @@ impl Component for SettingsDeveloper {
|
|||
"Debug Page",
|
||||
"View the debug page.",
|
||||
link_button().on_press(move |_| {
|
||||
let _ = RouterContext::get().push(Route::Debug { });
|
||||
let _ = RouterContext::get().push(Route::Debug {});
|
||||
}),
|
||||
))
|
||||
.into_element()
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
use freya::prelude::*;
|
||||
use oneclient_core::java::{AvailableJava, JavaRuntime, JavaVendor};
|
||||
|
||||
use super::settings_page;
|
||||
use crate::components::{Button, Dropdown, Icon, IconType, OverlayPopup, ScrollArea};
|
||||
use crate::hooks::{
|
||||
java_runtimes, provider_versions, use_dispatch, use_java_runtimes, use_provider_versions,
|
||||
BridgeDispatch,
|
||||
BridgeDispatch, java_runtimes, provider_versions, use_dispatch, use_java_runtimes,
|
||||
use_provider_versions,
|
||||
};
|
||||
use super::settings_page;
|
||||
use crate::theme::colors;
|
||||
use crate::ui::border_all_color;
|
||||
use crate::view::app::settings::section_header;
|
||||
|
|
@ -151,15 +151,13 @@ fn table_header() -> impl IntoElement {
|
|||
.child(head("VENDOR", Size::px(130.)))
|
||||
.child(head("VERSION", Size::px(90.)))
|
||||
.child(
|
||||
rect()
|
||||
.width(Size::flex(1.0))
|
||||
.child(
|
||||
label()
|
||||
.text("PATH")
|
||||
.font_size(11.)
|
||||
.font_weight(FontWeight::MEDIUM)
|
||||
.color(colors::fg_secondary()),
|
||||
),
|
||||
rect().width(Size::flex(1.0)).child(
|
||||
label()
|
||||
.text("PATH")
|
||||
.font_size(11.)
|
||||
.font_weight(FontWeight::MEDIUM)
|
||||
.color(colors::fg_secondary()),
|
||||
),
|
||||
)
|
||||
.child(rect().width(Size::px(34.)))
|
||||
.into_element()
|
||||
|
|
@ -206,7 +204,10 @@ impl Component for RuntimeRow {
|
|||
.fill(colors::component_border()),
|
||||
)
|
||||
})
|
||||
.child(cell(format!("{} {}", runtime.vendor, runtime.major), Size::px(130.)))
|
||||
.child(cell(
|
||||
format!("{} {}", runtime.vendor, runtime.major),
|
||||
Size::px(130.),
|
||||
))
|
||||
.child(cell(runtime.version.clone(), Size::px(90.)))
|
||||
.child(
|
||||
rect()
|
||||
|
|
@ -241,7 +242,11 @@ fn remove_button(dispatch: BridgeDispatch, path: String) -> impl IntoElement {
|
|||
.ghost()
|
||||
.small()
|
||||
.on_press(move |_| dispatch.remove_java_runtime(path.clone()))
|
||||
.child(Icon::new(IconType::Trash01).size(14.).color(colors::danger()))
|
||||
.child(
|
||||
Icon::new(IconType::Trash01)
|
||||
.size(14.)
|
||||
.color(colors::danger()),
|
||||
)
|
||||
.into_element()
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,13 +1,11 @@
|
|||
use freya::prelude::*;
|
||||
|
||||
use crate::components::{IconType, Icon};
|
||||
use super::settings_page;
|
||||
use crate::components::{Icon, IconType};
|
||||
use crate::theme::colors;
|
||||
use crate::view::app::settings::section_header;
|
||||
|
||||
const LANGUAGES: &[(&str, &str)] = &[
|
||||
("English", "English (US)"),
|
||||
];
|
||||
const LANGUAGES: &[(&str, &str)] = &[("English", "English (US)")];
|
||||
|
||||
#[derive(PartialEq)]
|
||||
pub struct SettingsLanguage;
|
||||
|
|
@ -63,8 +61,11 @@ fn language_row(
|
|||
.color(colors::fg_secondary()),
|
||||
),
|
||||
)
|
||||
.maybe_child(
|
||||
is_selected.then(|| Icon::new(IconType::Check).size(20.).color(colors::brand()).into_element()),
|
||||
)
|
||||
.maybe_child(is_selected.then(|| {
|
||||
Icon::new(IconType::Check)
|
||||
.size(20.)
|
||||
.color(colors::brand())
|
||||
.into_element()
|
||||
}))
|
||||
.into_element()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
use freya::prelude::*;
|
||||
|
||||
use super::settings_page;
|
||||
use crate::components::{IconType, link_button, toggle};
|
||||
use crate::hooks::{use_dispatch, use_launcher, use_settings_snapshot};
|
||||
use super::settings_page;
|
||||
use crate::platform;
|
||||
use crate::view::app::settings::{section_header, settings_row};
|
||||
|
||||
|
|
@ -20,17 +20,24 @@ impl Component for SettingsLauncher {
|
|||
move || v
|
||||
});
|
||||
|
||||
let crash_reporting = use_state({
|
||||
let v = settings.crash_reporting;
|
||||
move || v
|
||||
});
|
||||
|
||||
let mut first = use_state(|| true);
|
||||
{
|
||||
let settings = settings.clone();
|
||||
use_side_effect(move || {
|
||||
let discord = *discord_rpc.read();
|
||||
let crash = *crash_reporting.read();
|
||||
if *first.peek() {
|
||||
first.set(false);
|
||||
return;
|
||||
}
|
||||
let mut next = settings.clone();
|
||||
next.discord_enabled = discord;
|
||||
next.crash_reporting = crash;
|
||||
dispatch.set_settings(next);
|
||||
});
|
||||
}
|
||||
|
|
@ -46,6 +53,12 @@ impl Component for SettingsLauncher {
|
|||
"Enable Discord Rich Presence.",
|
||||
toggle(discord_rpc),
|
||||
))
|
||||
.child(settings_row(
|
||||
IconType::AlertTriangle,
|
||||
"Crash Reporting",
|
||||
"Send anonymous crash and error reports to help fix bugs. Applies on restart.",
|
||||
toggle(crash_reporting),
|
||||
))
|
||||
.child(section_header("FOLDERS AND FILES"))
|
||||
.child(settings_row(
|
||||
IconType::Folder,
|
||||
|
|
|
|||
|
|
@ -2,9 +2,9 @@ use freya::prelude::*;
|
|||
use oneclient_core::Patch;
|
||||
use oneclient_core::settings::{ProfileUpdate, Resolution};
|
||||
|
||||
use super::settings_page;
|
||||
use crate::components::{Icon, IconType, TextInput, toggle, validate_number};
|
||||
use crate::hooks::{use_dispatch, use_settings_snapshot};
|
||||
use super::settings_page;
|
||||
use crate::theme::colors;
|
||||
use crate::view::app::settings::{section_header, settings_row};
|
||||
|
||||
|
|
@ -21,11 +21,17 @@ impl Component for SettingsMinecraft {
|
|||
move || v
|
||||
});
|
||||
let width = use_state({
|
||||
let v = profile.resolution.map(|r| r.width.to_string()).unwrap_or_default();
|
||||
let v = profile
|
||||
.resolution
|
||||
.map(|r| r.width.to_string())
|
||||
.unwrap_or_default();
|
||||
move || v
|
||||
});
|
||||
let height = use_state({
|
||||
let v = profile.resolution.map(|r| r.height.to_string()).unwrap_or_default();
|
||||
let v = profile
|
||||
.resolution
|
||||
.map(|r| r.height.to_string())
|
||||
.unwrap_or_default();
|
||||
move || v
|
||||
});
|
||||
let memory = use_state({
|
||||
|
|
@ -165,7 +171,11 @@ fn resolution_field(width: State<String>, height: State<String>) -> impl IntoEle
|
|||
.width(Size::px(70.))
|
||||
.text_align(TextAlign::Center),
|
||||
)
|
||||
.child(Icon::new(IconType::X).size(14.).color(colors::fg_secondary()))
|
||||
.child(
|
||||
Icon::new(IconType::X)
|
||||
.size(14.)
|
||||
.color(colors::fg_secondary()),
|
||||
)
|
||||
.child(
|
||||
TextInput::new(height)
|
||||
.placeholder("480")
|
||||
|
|
|
|||
|
|
@ -15,14 +15,14 @@ pub use apis::SettingsApis;
|
|||
pub use changelog::SettingsChangelog;
|
||||
pub use developer::SettingsDeveloper;
|
||||
// pub use feedback::SettingsFeedback;
|
||||
pub use java::SettingsJava;
|
||||
pub use appearance::SettingsAppearance;
|
||||
pub use java::SettingsJava;
|
||||
pub use language::SettingsLanguage;
|
||||
pub use launcher::SettingsLauncher;
|
||||
pub use minecraft::SettingsMinecraft;
|
||||
|
||||
use crate::{
|
||||
components::{IconType, Icon},
|
||||
components::{Icon, IconType},
|
||||
theme::colors,
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
|
||||
use freya::prelude::*;
|
||||
|
||||
use crate::components::{Button, ButtonVariant, Icon, IconType, PlayerModel};
|
||||
|
|
@ -120,7 +119,11 @@ fn side_panel() -> impl IntoElement {
|
|||
|
||||
fn action_button(text: &'static str, icon: IconType, primary: bool) -> impl IntoElement {
|
||||
Button::new()
|
||||
.variant(if primary { ButtonVariant::Primary } else { ButtonVariant::Secondary })
|
||||
.variant(if primary {
|
||||
ButtonVariant::Primary
|
||||
} else {
|
||||
ButtonVariant::Secondary
|
||||
})
|
||||
.child(Icon::new(icon).size(16.).color(colors::fg_primary()))
|
||||
.text(text)
|
||||
.into_element()
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
mod not_found;
|
||||
mod startup;
|
||||
|
||||
pub mod onboarding;
|
||||
pub mod app;
|
||||
pub mod onboarding;
|
||||
|
||||
pub use not_found::NotFound;
|
||||
pub use startup::Startup;
|
||||
pub use startup::Startup;
|
||||
|
|
|
|||
|
|
@ -32,4 +32,4 @@ impl Component for NotFound {
|
|||
.color(theme::colors::fg_secondary()),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -125,12 +125,7 @@ fn sign_in_card(
|
|||
.size(13.)
|
||||
.color(colors::danger()),
|
||||
)
|
||||
.child(
|
||||
label()
|
||||
.text(message)
|
||||
.font_size(12.)
|
||||
.color(colors::danger()),
|
||||
)
|
||||
.child(label().text(message).font_size(12.).color(colors::danger()))
|
||||
.into_element()
|
||||
}))
|
||||
.into_element()
|
||||
|
|
|
|||
|
|
@ -2,9 +2,7 @@ use super::*;
|
|||
|
||||
use oneclient_core::clusters::Cluster;
|
||||
|
||||
use crate::components::{
|
||||
Button, DynamicArt, Icon, IconType,
|
||||
};
|
||||
use crate::components::{Button, DynamicArt, Icon, IconType};
|
||||
use crate::theme::colors;
|
||||
use crate::ui::border_all_color;
|
||||
|
||||
|
|
|
|||
|
|
@ -8,8 +8,7 @@ use oneclient_core::{BundleArchive, BundleFile, BundleFileKind};
|
|||
|
||||
use crate::components::ScrollArea;
|
||||
use crate::hooks::{
|
||||
ClusterBundles, onboarding_bundles_items,
|
||||
use_onboarding_bundles, use_onboarding_selection,
|
||||
ClusterBundles, onboarding_bundles_items, use_onboarding_bundles, use_onboarding_selection,
|
||||
};
|
||||
use crate::routes::Route;
|
||||
use crate::theme::colors;
|
||||
|
|
@ -321,4 +320,3 @@ impl Component for OnboardingBundles {
|
|||
.maybe_child(popup)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -6,12 +6,8 @@ use freya::query::QueryStateData;
|
|||
use oneclient_core::packages::{CachedPackageMeta, ProviderId};
|
||||
use oneclient_core::{BundleArchive, BundleFileKind};
|
||||
|
||||
use crate::components::{
|
||||
Button, Icon, OverlayPopup, ScrollArea, TabBar, TabItem,
|
||||
};
|
||||
use crate::hooks::{
|
||||
package_meta_batch, use_cached_image, use_package_meta_batch,
|
||||
};
|
||||
use crate::components::{Button, Icon, OverlayPopup, ScrollArea, TabBar, TabItem};
|
||||
use crate::hooks::{package_meta_batch, use_cached_image, use_package_meta_batch};
|
||||
use crate::theme::colors;
|
||||
use crate::ui::border_all_color;
|
||||
use crate::utils::format_size;
|
||||
|
|
|
|||
|
|
@ -46,7 +46,10 @@ pub(super) fn summary_view(
|
|||
|
||||
let migration_section: Element = match migration {
|
||||
Some((source, folder, target)) => {
|
||||
let mut rows = vec![summary_line("From", &source), summary_line("Files", &folder)];
|
||||
let mut rows = vec![
|
||||
summary_line("From", &source),
|
||||
summary_line("Files", &folder),
|
||||
];
|
||||
if !target.is_empty() {
|
||||
rows.push(summary_line("Destination", &target));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -50,7 +50,14 @@ pub(super) fn use_onboarding_tip() -> String {
|
|||
let Ok(state) = oneclient_core::LauncherState::get() else {
|
||||
return;
|
||||
};
|
||||
match state.services.requester.http().get(FUNFACTS_URL).send().await {
|
||||
match state
|
||||
.services
|
||||
.requester
|
||||
.http()
|
||||
.get(FUNFACTS_URL)
|
||||
.send()
|
||||
.await
|
||||
{
|
||||
Ok(res) if res.status().is_success() => match res.text().await {
|
||||
Ok(text) => {
|
||||
let parsed = parse_funfacts(&text);
|
||||
|
|
|
|||
|
|
@ -14,9 +14,10 @@ struct Language {
|
|||
percentage: u32,
|
||||
}
|
||||
|
||||
const LANGUAGES: [Language; 1] = [
|
||||
Language { name: "English", percentage: 100 },
|
||||
];
|
||||
const LANGUAGES: [Language; 1] = [Language {
|
||||
name: "English",
|
||||
percentage: 100,
|
||||
}];
|
||||
|
||||
#[derive(PartialEq)]
|
||||
pub struct OnboardingLanguage;
|
||||
|
|
@ -29,15 +30,13 @@ impl Component for OnboardingLanguage {
|
|||
let back = if has_migration_data(&migration_query) {
|
||||
Route::OnboardingMigration {}
|
||||
} else {
|
||||
Route::OnboardingWelcome {}
|
||||
Route::OnboardingTerms {}
|
||||
};
|
||||
|
||||
let rows: Vec<Element> = LANGUAGES
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(index, lang)| {
|
||||
language_row(lang, index == *selected.read(), selected, language)
|
||||
})
|
||||
.map(|(index, lang)| language_row(lang, index == *selected.read(), selected, language))
|
||||
.collect();
|
||||
|
||||
let content = rect()
|
||||
|
|
@ -55,7 +54,13 @@ impl Component for OnboardingLanguage {
|
|||
.font_weight(FontWeight::MEDIUM)
|
||||
.color(colors::fg_primary()),
|
||||
)
|
||||
.child(rect().vertical().width(Size::fill()).spacing(4.).children(rows))
|
||||
.child(
|
||||
rect()
|
||||
.vertical()
|
||||
.width(Size::fill())
|
||||
.spacing(4.)
|
||||
.children(rows),
|
||||
)
|
||||
.into_element();
|
||||
|
||||
onboarding_page(
|
||||
|
|
@ -92,7 +97,9 @@ fn language_row(
|
|||
.padding(Gaps::new_symmetric(14., 20.))
|
||||
.corner_radius(CornerRadius::new_all(10.))
|
||||
.background(background)
|
||||
.maybe(active, |el| el.border(border_all_color(1., colors::brand())))
|
||||
.maybe(active, |el| {
|
||||
el.border(border_all_color(1., colors::brand()))
|
||||
})
|
||||
.a11y_role(AccessibilityRole::Button)
|
||||
.on_pointer_enter(|_| Cursor::set(CursorIcon::Pointer))
|
||||
.on_pointer_leave(|_| Cursor::set(CursorIcon::default()))
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue