chore(lint): cargo fmt

This commit is contained in:
LynithDev 2026-09-18 23:05:50 +02:00
parent e0947848f9
commit daaa209b30
273 changed files with 13173 additions and 12596 deletions

View file

@ -47,8 +47,14 @@ mod tests {
#[test] #[test]
fn both_spellings_carry_the_folder() { fn both_spellings_carry_the_folder() {
assert_eq!(parse(&["--launch", "fabric-1-20"]).launch.as_deref(), Some("fabric-1-20")); assert_eq!(
assert_eq!(parse(&["--launch=fabric-1-20"]).launch.as_deref(), Some("fabric-1-20")); parse(&["--launch", "fabric-1-20"]).launch.as_deref(),
Some("fabric-1-20")
);
assert_eq!(
parse(&["--launch=fabric-1-20"]).launch.as_deref(),
Some("fabric-1-20")
);
} }
#[test] #[test]
@ -68,7 +74,12 @@ mod tests {
#[test] #[test]
fn unknown_flags_are_ignored_not_fatal() { fn unknown_flags_are_ignored_not_fatal() {
assert_eq!(parse(&["--verbose", "--launch", "x", "leftover"]).launch.as_deref(), Some("x")); assert_eq!(
parse(&["--verbose", "--launch", "x", "leftover"])
.launch
.as_deref(),
Some("x")
);
} }
#[test] #[test]

View file

@ -6,7 +6,6 @@ use freya::{
use oneclient_auth::MinecraftAccount; use oneclient_auth::MinecraftAccount;
use crate::{ use crate::{
ui::{divider},
Route, Route,
components::{Avatar, Button, Icon, IconType, OverlayPopup}, components::{Avatar, Button, Icon, IconType, OverlayPopup},
hooks::{ hooks::{
@ -14,6 +13,7 @@ use crate::{
use_accounts, use_current_account, use_dispatch, use_set_default_account, use_accounts, use_current_account, use_dispatch, use_set_default_account,
}, },
theme::colors, theme::colors,
ui::divider,
}; };
#[derive(PartialEq)] #[derive(PartialEq)]

View file

@ -5,7 +5,10 @@ use freya::router::RouterContext;
use crate::components::{Button, Icon, IconType}; use crate::components::{Button, Icon, IconType};
use oneclient_common::parse_mc_version; use oneclient_common::parse_mc_version;
use crate::hooks::{settled_or_loading, use_active_cluster_id, use_clusters, use_dispatch, use_game_snapshot, use_launcher, use_version_metadata}; use crate::hooks::{
settled_or_loading, use_active_cluster_id, use_clusters, use_dispatch, use_game_snapshot,
use_launcher, use_version_metadata,
};
use crate::routes::Route; use crate::routes::Route;
use crate::theme::colors; use crate::theme::colors;
use crate::utils::sort_clusters_for_home; use crate::utils::sort_clusters_for_home;

View file

@ -56,7 +56,7 @@ pub struct Button {
tooltip: Option<Box<str>>, tooltip: Option<Box<str>>,
tooltip_placement: TooltipPlacement, tooltip_placement: TooltipPlacement,
on_press: Option<EventHandler<Event<PressEventData>>>, on_press: Option<EventHandler<Event<PressEventData>>>,
overflow: Overflow, overflow: Overflow,
elements: Vec<Element>, elements: Vec<Element>,
key: DiffKey, key: DiffKey,
@ -118,7 +118,7 @@ impl Button {
tooltip_placement: TooltipPlacement::default(), tooltip_placement: TooltipPlacement::default(),
on_press: None, on_press: None,
elements: Vec::new(), elements: Vec::new(),
overflow: Overflow::Clip, overflow: Overflow::Clip,
key: DiffKey::None, key: DiffKey::None,
cursor_icon: CursorIcon::Pointer, cursor_icon: CursorIcon::Pointer,
} }
@ -158,10 +158,10 @@ impl Button {
self self
} }
pub fn overflow(mut self, overflow: Overflow) -> Self { pub fn overflow(mut self, overflow: Overflow) -> Self {
self.overflow = overflow; self.overflow = overflow;
self self
} }
pub fn font_size(mut self, font_size: impl Into<f32>) -> Self { pub fn font_size(mut self, font_size: impl Into<f32>) -> Self {
self.text_style.font_size = Some(font_size.into().into()); self.text_style.font_size = Some(font_size.into().into());

View file

@ -368,7 +368,7 @@ impl Component for BarChart {
rect() rect()
.width(Size::fill()) .width(Size::fill())
.height(Size::px(h)) .height(Size::px(h))
.corner_radius(CornerRadius::new(4., 4., 0., 0.)) .corner_radius(CornerRadius::new(4., 4., 0., 0.))
.background(color), .background(color),
), ),
); );

View file

@ -4,7 +4,9 @@ use freya::{prelude::*, router::RouterContext};
use oneclient_content::packages::{CachedPackageMeta, ProviderId}; use oneclient_content::packages::{CachedPackageMeta, ProviderId};
use crate::components::{Button, Icon, IconType, OverlayPopup, ScrollArea, TabBar, TabItem}; use crate::components::{Button, Icon, IconType, OverlayPopup, ScrollArea, TabBar, TabItem};
use crate::hooks::{package_meta_batch, use_dispatch, use_notifications_snapshot, use_package_meta_batch}; use crate::hooks::{
package_meta_batch, use_dispatch, use_notifications_snapshot, use_package_meta_batch,
};
use crate::notifications::{ClusterUpdateItem, ClusterUpdateSummary}; use crate::notifications::{ClusterUpdateItem, ClusterUpdateSummary};
use crate::routes::Route; use crate::routes::Route;
use crate::theme::colors; use crate::theme::colors;
@ -355,11 +357,7 @@ fn change_list(
} }
/// Multi-cluster stand-in for the footer's "Open cluster" shortcut /// Multi-cluster stand-in for the footer's "Open cluster" shortcut
fn cluster_header( fn cluster_header(group: &ClusterGroup, first: bool, dispatch: crate::Actions) -> impl IntoElement {
group: &ClusterGroup,
first: bool,
dispatch: crate::Actions,
) -> impl IntoElement {
let cluster_id = group.cluster_id; let cluster_id = group.cluster_id;
let count = group.names.len(); let count = group.names.len();

View file

@ -120,7 +120,12 @@ impl Component for AccountHeader {
let unusable = query_error(&current).is_some(); let unusable = query_error(&current).is_some();
let account = try_default_account(&current).or_else(|| { let account = try_default_account(&current).or_else(|| {
unusable unusable
.then(|| try_accounts(&accounts_query).unwrap_or_default().into_iter().next()) .then(|| {
try_accounts(&accounts_query)
.unwrap_or_default()
.into_iter()
.next()
})
.flatten() .flatten()
}); });
@ -164,11 +169,7 @@ impl Component for AccountHeader {
.cross_align(Alignment::Center) .cross_align(Alignment::Center)
.spacing(10.) .spacing(10.)
.padding(Gaps::new_all(4.)) .padding(Gaps::new_all(4.))
.child( .child(Avatar::new(uuid).width(Size::px(40.)).height(Size::px(40.)))
Avatar::new(uuid)
.width(Size::px(40.))
.height(Size::px(40.)),
)
.child( .child(
rect() rect()
.vertical() .vertical()
@ -257,11 +258,7 @@ impl Component for NavRow {
.on_pointer_enter(move |_| hovered.set(true)) .on_pointer_enter(move |_| hovered.set(true))
.on_pointer_leave(move |_| hovered.set(false)) .on_pointer_leave(move |_| hovered.set(false))
.on_press(go) .on_press(go)
.child( .child(Icon::new(self.icon).size(20.).color(colors::fg_secondary()))
Icon::new(self.icon)
.size(20.)
.color(colors::fg_secondary()),
)
.child( .child(
label() label()
.text(self.row_label) .text(self.row_label)
@ -619,9 +616,7 @@ impl Component for FooterAction {
.a11y_role(AccessibilityRole::Button) .a11y_role(AccessibilityRole::Button)
.on_pointer_enter(move |_| hovered.set(true)) .on_pointer_enter(move |_| hovered.set(true))
.on_pointer_leave(move |_| hovered.set(false)) .on_pointer_leave(move |_| hovered.set(false))
.map(handler, |el, handler| { .map(handler, |el, handler| el.on_press(move |e| handler.call(e)))
el.on_press(move |e| handler.call(e))
})
.child(Icon::new(self.icon).size(16.).color(self.color)) .child(Icon::new(self.icon).size(16.).color(self.color))
.child( .child(
label() label()

View file

@ -270,8 +270,7 @@ fn month_grid(
outside: day.month0() != shown.month0(), outside: day.month0() != shown.month0(),
enabled: (min..=max).contains(&day), enabled: (min..=max).contains(&day),
in_range: selected.is_some_and(|(a, b)| (a..=b).contains(&day)), in_range: selected.is_some_and(|(a, b)| (a..=b).contains(&day)),
edge: selected.is_some_and(|(a, b)| day == a || day == b) edge: selected.is_some_and(|(a, b)| day == a || day == b) || pending == Some(day),
|| pending == Some(day),
pick: pick.clone(), pick: pick.clone(),
}); });
} }
@ -349,7 +348,11 @@ impl<F: FnMut(NaiveDate) + Clone + 'static> Component for DayCell<F> {
} }
} }
fn footer(has_range: bool, pending: Option<NaiveDate>, mut clear: impl FnMut() + 'static) -> Element { fn footer(
has_range: bool,
pending: Option<NaiveDate>,
mut clear: impl FnMut() + 'static,
) -> Element {
let hint = match pending { let hint = match pending {
Some(day) => format!("From {} — pick an end date", format_day(day)), Some(day) => format!("From {} — pick an end date", format_day(day)),
None => "Click a start date, then an end date".to_string(), None => "Click a start date, then an end date".to_string(),

View file

@ -1,9 +1,9 @@
use bytes::Bytes; use bytes::Bytes;
use freya::prelude::*; use freya::prelude::*;
use oneclient_core::clusters::Cluster;
use oneclient_core::images::DEFAULT_IMAGE_EDGE;
use oneclient_common::domain::GameLoader; use oneclient_common::domain::GameLoader;
use oneclient_common::{VersionKey, parse_mc_version}; use oneclient_common::{VersionKey, parse_mc_version};
use oneclient_core::clusters::Cluster;
use oneclient_core::images::DEFAULT_IMAGE_EDGE;
use crate::AppAssets; use crate::AppAssets;
use crate::hooks::{loaded_image, use_cached_image, use_version_metadata}; use crate::hooks::{loaded_image, use_cached_image, use_version_metadata};

View file

@ -7,8 +7,8 @@ use std::path::{Path, PathBuf};
use freya::animation::{AnimNum, Ease, Function, OnCreation, use_animation}; use freya::animation::{AnimNum, Ease, Function, OnCreation, use_animation};
use freya::prelude::*; use freya::prelude::*;
use freya::router::use_route; use freya::router::use_route;
use oneclient_core::clusters::Cluster;
use oneclient_content::packages::ContentType; use oneclient_content::packages::ContentType;
use oneclient_core::clusters::Cluster;
use oneclient_db::models::ClusterId; use oneclient_db::models::ClusterId;
use crate::Route; use crate::Route;

View file

@ -79,9 +79,7 @@ impl Component for GenericPromptOverlay {
.pick_folder() .pick_folder()
.await .await
{ {
dispatch.answer_prompt( dispatch.answer_prompt(Answer::new(id).with_folder(handle.path()));
Answer::new(id).with_folder(handle.path()),
);
} }
}); });
} }

View file

@ -1,6 +1,6 @@
use freya::prelude::*; use freya::prelude::*;
use oneclient_java::{JAVA_CHOICE_DOWNLOAD, JAVA_CHOICE_FOLDER, JavaVendor};
use oneclient_events::Answer; use oneclient_events::Answer;
use oneclient_java::{JAVA_CHOICE_DOWNLOAD, JAVA_CHOICE_FOLDER, JavaVendor};
use crate::components::{Button, Icon, IconType, JavaInstallManager, OverlayPopup}; use crate::components::{Button, Icon, IconType, JavaInstallManager, OverlayPopup};
use crate::hooks::{use_dispatch, use_notifications_snapshot}; use crate::hooks::{use_dispatch, use_notifications_snapshot};
@ -65,9 +65,8 @@ impl Component for JavaPromptOverlay {
.pick_folder() .pick_folder()
.await .await
{ {
dispatch.answer_prompt( dispatch
Answer::new(JAVA_CHOICE_FOLDER).with_folder(handle.path()), .answer_prompt(Answer::new(JAVA_CHOICE_FOLDER).with_folder(handle.path()));
);
} }
}); });
}; };
@ -124,9 +123,7 @@ impl Component for JavaPromptOverlay {
.child( .child(
Button::new() Button::new()
.secondary() .secondary()
.on_press(move |_| { .on_press(move |_| cancel.dismiss_prompt())
cancel.dismiss_prompt()
})
.text("Cancel"), .text("Cancel"),
) )
.child( .child(

View file

@ -195,8 +195,7 @@ impl ElementExt for ScaledImageElement {
let image = &self.image_handle.image; let image = &self.image_handle.image;
let natural = Size2D::new(image.width() as f32, image.height() as f32); let natural = Size2D::new(image.width() as f32, image.height() as f32);
let available = let available = (*context.area_size - context.torin_node.margin.into()).max(Size2D::zero());
(*context.area_size - context.torin_node.margin.into()).max(Size2D::zero());
let ratio = (available.width / natural.width).min(1.); let ratio = (available.width / natural.width).min(1.);
let ratio = if ratio.is_finite() && ratio > 0. { let ratio = if ratio.is_finite() && ratio > 0. {
@ -256,8 +255,8 @@ impl ElementExt for ScaledImageElement {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use freya::prelude::Bytes;
use freya::engine::prelude::AlphaType; use freya::engine::prelude::AlphaType;
use freya::prelude::Bytes;
use freya_testing::prelude::*; use freya_testing::prelude::*;
use super::*; use super::*;

View file

@ -115,4 +115,3 @@ impl Component for Markdown {
self.key.clone().or(self.default_key()) self.key.clone().or(self.default_key())
} }
} }

View file

@ -274,7 +274,10 @@ impl Walker {
} else { } else {
std::mem::take(&mut self.image_alt) std::mem::take(&mut self.image_alt)
}; };
self.place(Inline::Image { url, alt }, |url, alt| Block::Image { url, alt }); self.place(Inline::Image { url, alt }, |url, alt| Block::Image {
url,
alt,
});
} }
TagEnd::Link => { TagEnd::Link => {
self.in_link = false; self.in_link = false;
@ -488,18 +491,27 @@ mod tests {
#[test] #[test]
fn inline_html_keeps_the_text_it_wrapped() { fn inline_html_keeps_the_text_it_wrapped() {
assert_eq!(paragraph_text(&blocks("a <b>bold</b> word")[0]), "a bold word"); assert_eq!(
paragraph_text(&blocks("a <b>bold</b> word")[0]),
"a bold word"
);
} }
#[test] #[test]
fn block_html_keeps_its_text() { fn block_html_keeps_its_text() {
assert_eq!(paragraph_text(&blocks("<div>\nhello\n</div>")[0]).trim(), "hello"); assert_eq!(
paragraph_text(&blocks("<div>\nhello\n</div>")[0]).trim(),
"hello"
);
} }
#[test] #[test]
fn images_arrive_as_paragraph_content() { fn images_arrive_as_paragraph_content() {
assert_eq!(paragraph_text(&blocks("![alt](a.png)")[0]), "[img:alt]"); assert_eq!(paragraph_text(&blocks("![alt](a.png)")[0]), "[img:alt]");
assert_eq!(paragraph_text(&blocks("see ![alt](a.png) here")[0]), "see [img:alt] here"); assert_eq!(
paragraph_text(&blocks("see ![alt](a.png) here")[0]),
"see [img:alt] here"
);
} }
#[test] #[test]
@ -530,7 +542,8 @@ mod tests {
#[test] #[test]
fn tables_split_head_from_body() { fn tables_split_head_from_body() {
let Block::Table { headers, rows } = &blocks("| a | b |\n| - | - |\n| 1 | 2 |\n| 3 | 4 |")[0] let Block::Table { headers, rows } =
&blocks("| a | b |\n| - | - |\n| 1 | 2 |\n| 3 | 4 |")[0]
else { else {
panic!("expected a table"); panic!("expected a table");
}; };

View file

@ -7,12 +7,15 @@ use crate::hooks::LinkConfirmState;
pub fn render_block(block: &Block, key: usize, style: &MarkdownStyle) -> Element { pub fn render_block(block: &Block, key: usize, style: &MarkdownStyle) -> Element {
match block { match block {
Block::Heading { level, spans } => { Block::Heading { level, spans } => render_spans(
render_spans(spans, style.heading_size(*level), style.color, style.color_code) spans,
.font_weight(FontWeight::BOLD) style.heading_size(*level),
.key(key) style.color,
.into() style.color_code,
} )
.font_weight(FontWeight::BOLD)
.key(key)
.into(),
Block::Paragraph { content } => render_content(content, style.paragraph_size, style) Block::Paragraph { content } => render_content(content, style.paragraph_size, style)
.key(key) .key(key)
.into(), .into(),
@ -53,7 +56,9 @@ pub fn render_block(block: &Block, key: usize, style: &MarkdownStyle) -> Element
.alignment(BorderAlignment::Inner), .alignment(BorderAlignment::Inner),
) )
.background(style.background_blockquote) .background(style.background_blockquote)
.child(render_content(content, style.paragraph_size, style).font_slant(FontSlant::Italic)) .child(
render_content(content, style.paragraph_size, style).font_slant(FontSlant::Italic),
)
.into(), .into(),
Block::Rule => rect() Block::Rule => rect()
.key(key) .key(key)
@ -215,12 +220,7 @@ impl KeyExt for MarkdownLink {
} }
impl MarkdownLink { impl MarkdownLink {
fn new( fn new(url: String, title: Option<String>, content: Vec<Inline>, style: MarkdownStyle) -> Self {
url: String,
title: Option<String>,
content: Vec<Inline>,
style: MarkdownStyle,
) -> Self {
Self { Self {
url, url,
title, title,
@ -242,18 +242,24 @@ impl Component for MarkdownLink {
let mut text = paragraph().font_size(self.style.paragraph_size); let mut text = paragraph().font_size(self.style.paragraph_size);
for item in &self.content { for item in &self.content {
text = match item { text = match item {
Inline::Span(span) => { Inline::Span(span) => text.span(styled_span(
text.span(styled_span(span, self.style.color_link, self.style.color_code)) span,
} self.style.color_link,
self.style.color_code,
)),
Inline::Image { url, alt } => { Inline::Image { url, alt } => {
text.child(MarkdownImage::new(url.clone(), alt.clone())) text.child(MarkdownImage::new(url.clone(), alt.clone()))
} }
Inline::Link { content, .. } => content.iter().fold(text, |text, item| match item { Inline::Link { content, .. } => {
Inline::Span(span) => { content.iter().fold(text, |text, item| match item {
text.span(styled_span(span, self.style.color_link, self.style.color_code)) Inline::Span(span) => text.span(styled_span(
} span,
_ => text, self.style.color_link,
}), self.style.color_code,
)),
_ => text,
})
}
}; };
} }

View file

@ -1,9 +1,9 @@
use crate::state::LoginProgress;
use freya::prelude::*; use freya::prelude::*;
use freya::query::{MutationCapability, MutationStateData, UseMutation}; use freya::query::{MutationCapability, MutationStateData, UseMutation};
use freya::text_edit::Clipboard; use freya::text_edit::Clipboard;
use oneclient_core::LauncherError;
use oneclient_auth::{AuthErrorGuidance, MicrosoftLoginSession}; use oneclient_auth::{AuthErrorGuidance, MicrosoftLoginSession};
use crate::state::LoginProgress; use oneclient_core::LauncherError;
use crate::components::{Button, Icon, IconType, OverlayPopup}; use crate::components::{Button, Icon, IconType, OverlayPopup};
use crate::hooks::{ use crate::hooks::{
@ -225,10 +225,7 @@ pub(crate) fn login_dialog(
.spacing(18.) .spacing(18.)
.child(browser_dialog_body(auth_url)) .child(browser_dialog_body(auth_url))
.child(dialog_divider()) .child(dialog_divider())
.child(device_code_dialog_body( .child(device_code_dialog_body(user_code, verification_uri))
user_code,
verification_uri,
))
.child(status_row(status, error)) .child(status_row(status, error))
.maybe_child(guidance.map(guidance_block)), .maybe_child(guidance.map(guidance_block)),
), ),
@ -314,17 +311,13 @@ fn status_row(status: Option<LoginProgress>, error: Option<String>) -> impl Into
} }
fn guidance_block(guidance: AuthErrorGuidance) -> impl IntoElement { fn guidance_block(guidance: AuthErrorGuidance) -> impl IntoElement {
let mut steps = rect() let mut steps = rect().vertical().width(Size::fill()).spacing(6.).child(
.vertical() label()
.width(Size::fill()) .text("What you can do:")
.spacing(6.) .font_size(12.)
.child( .font_weight(FontWeight::SEMI_BOLD)
label() .color(colors::fg_primary()),
.text("What you can do:") );
.font_size(12.)
.font_weight(FontWeight::SEMI_BOLD)
.color(colors::fg_primary()),
);
for (index, step) in guidance.steps_to_fix.into_iter().enumerate() { for (index, step) in guidance.steps_to_fix.into_iter().enumerate() {
steps = steps.child( steps = steps.child(

View file

@ -1,5 +1,4 @@
mod account_switcher; mod account_switcher;
mod control_center;
mod active_cluster_panel; mod active_cluster_panel;
mod avatar; mod avatar;
mod button; mod button;
@ -8,24 +7,25 @@ mod checkbox;
mod cluster_landscape_art; mod cluster_landscape_art;
mod cluster_update_popup; mod cluster_update_popup;
mod context_menu; mod context_menu;
mod control_center;
mod date_range_picker; mod date_range_picker;
mod optional_mods_popup;
mod dropdown; mod dropdown;
mod dynamic_art; mod dynamic_art;
mod file_drop; mod file_drop;
mod generic_prompt;
mod icons; mod icons;
mod java_install_manager; mod java_install_manager;
mod generic_prompt;
mod java_prompt; mod java_prompt;
mod microsoft_java_prompt;
mod link_confirm; mod link_confirm;
mod local_image; mod local_image;
mod log_viewer; mod log_viewer;
mod markdown; mod markdown;
mod memory_field; mod memory_field;
mod microsoft_java_prompt;
mod microsoft_login; mod microsoft_login;
mod navbar; mod navbar;
mod notifications; mod notifications;
mod optional_mods_popup;
mod overlay_popup; mod overlay_popup;
mod package_row; mod package_row;
mod package_update_popup; mod package_update_popup;
@ -44,11 +44,10 @@ mod toasts;
mod toggle; mod toggle;
mod tooltip; mod tooltip;
mod update_prompt; mod update_prompt;
mod version_card;
pub mod upload_mclogs; pub mod upload_mclogs;
mod version_card;
pub use account_switcher::AccountSwitcher; pub use account_switcher::AccountSwitcher;
pub use control_center::ControlCenter;
pub use active_cluster_panel::ActiveClusterPanel; pub use active_cluster_panel::ActiveClusterPanel;
pub use avatar::Avatar; pub use avatar::Avatar;
pub use button::{Button, ButtonSize, ButtonVariant, link_button, open_folder_button}; pub use button::{Button, ButtonSize, ButtonVariant, link_button, open_folder_button};
@ -57,29 +56,30 @@ pub use checkbox::checkbox_labeled;
pub use cluster_landscape_art::ClusterLandscapeArt; pub use cluster_landscape_art::ClusterLandscapeArt;
pub use cluster_update_popup::ClusterUpdatePopup; pub use cluster_update_popup::ClusterUpdatePopup;
pub use context_menu::ContextMenu; pub use context_menu::ContextMenu;
pub use control_center::ControlCenter;
pub use date_range_picker::{DateRange, DateRangePicker}; pub use date_range_picker::{DateRange, DateRangePicker};
pub use optional_mods_popup::OptionalModsPopup;
pub use dropdown::Dropdown; pub use dropdown::Dropdown;
pub use dynamic_art::{ART_PREVIEW_EDGE, DynamicArt}; pub use dynamic_art::{ART_PREVIEW_EDGE, DynamicArt};
pub use file_drop::FileDropOverlay; pub use file_drop::FileDropOverlay;
pub use generic_prompt::GenericPromptOverlay;
pub use icons::{Icon, IconTint, IconType}; pub use icons::{Icon, IconTint, IconType};
pub use java_install_manager::JavaInstallManager; pub use java_install_manager::JavaInstallManager;
pub use generic_prompt::GenericPromptOverlay;
pub use java_prompt::JavaPromptOverlay; pub use java_prompt::JavaPromptOverlay;
pub use microsoft_java_prompt::MicrosoftJavaPromptOverlay;
pub use link_confirm::ConfirmLinkOverlay; pub use link_confirm::ConfirmLinkOverlay;
pub use local_image::LocalImage; pub use local_image::LocalImage;
pub use markdown::{Markdown, MarkdownStyle};
pub use log_viewer::LogViewer; pub use log_viewer::LogViewer;
pub use markdown::{Markdown, MarkdownStyle};
pub use memory_field::memory_field; pub use memory_field::memory_field;
pub use microsoft_login::use_microsoft_login; pub use microsoft_java_prompt::MicrosoftJavaPromptOverlay;
pub(crate) use microsoft_login::login_dialog; pub(crate) use microsoft_login::login_dialog;
pub use microsoft_login::use_microsoft_login;
pub(crate) use navbar::window_controls; pub(crate) use navbar::window_controls;
pub use navbar::{AppNavbar, OnboardingNavbar}; pub use navbar::{AppNavbar, OnboardingNavbar};
pub use notifications::NotificationCenter; pub use notifications::NotificationCenter;
pub use optional_mods_popup::OptionalModsPopup;
pub use overlay_popup::{OVERLAY_BASE_LEVEL, OVERLAY_MAX_LEVEL, OverlayPopup, overlay_is_topmost}; pub use overlay_popup::{OVERLAY_BASE_LEVEL, OVERLAY_MAX_LEVEL, OverlayPopup, overlay_is_topmost};
pub use package_row::{CardLayout, PackageEntry, PackageRow, provider_badge};
pub(crate) use package_row::{CARD_GRID_H, GRID_GAP, GRID_MIN_W, grid_card, package_icon}; pub(crate) use package_row::{CARD_GRID_H, GRID_GAP, GRID_MIN_W, grid_card, package_icon};
pub use package_row::{CardLayout, PackageEntry, PackageRow, provider_badge};
pub use package_update_popup::PackageUpdatePopup; pub use package_update_popup::PackageUpdatePopup;
pub use pagination::Pagination; pub use pagination::Pagination;
pub use player_model::PlayerModel; pub use player_model::PlayerModel;

View file

@ -125,10 +125,10 @@ fn navbar_center(is_small: bool) -> impl IntoElement {
}) })
.cross_align(Alignment::Center) .cross_align(Alignment::Center)
.spacing(if is_small { .spacing(if is_small {
NAV_LINK_SPACING_PX NAV_LINK_SPACING_PX
} else { } else {
NAV_LINK_SPACING_PX / 2. NAV_LINK_SPACING_PX / 2.
}) })
.child(NavLink { .child(NavLink {
active: route == Route::Home {}, active: route == Route::Home {},
target: NavTarget::Route(Route::Home {}), target: NavTarget::Route(Route::Home {}),
@ -308,7 +308,7 @@ impl Component for NavbarRight {
) )
.child( .child(
super::navbar_button() super::navbar_button()
.overflow(Overflow::None) .overflow(Overflow::None)
.tooltip("Control Center") .tooltip("Control Center")
.padding(0.0) .padding(0.0)
.on_press(open_control_center) .on_press(open_control_center)
@ -323,11 +323,7 @@ fn avatar_with_gear(uuid: String) -> impl IntoElement {
.width(Size::px(28.)) .width(Size::px(28.))
.height(Size::px(28.)) .height(Size::px(28.))
.center() .center()
.child( .child(Avatar::new(uuid).width(Size::px(28.)).height(Size::px(28.)))
Avatar::new(uuid)
.width(Size::px(28.))
.height(Size::px(28.)),
)
.child( .child(
rect() rect()
.position(Position::new_absolute().bottom(-7.).right(-7.)) .position(Position::new_absolute().bottom(-7.).right(-7.))

View file

@ -5,12 +5,12 @@ use freya::{
use oneclient_events::Level; use oneclient_events::Level;
use crate::{ use crate::{
ui::{divider, relative_time},
components::{Button, ButtonVariant, Icon, IconType, OverlayPopup, ScrollArea, progress_track}, components::{Button, ButtonVariant, Icon, IconType, OverlayPopup, ScrollArea, progress_track},
hooks::{use_dispatch, use_notifications_snapshot}, hooks::{use_dispatch, use_notifications_snapshot},
notifications::{InboxEntry, NotificationActionKind}, notifications::{InboxEntry, NotificationActionKind},
theme::colors, theme::colors,
transfer::TransferStats, transfer::TransferStats,
ui::{divider, relative_time},
utils::{format_durations, format_size}, utils::{format_durations, format_size},
}; };
@ -212,7 +212,9 @@ fn tasks_section(entry: &InboxEntry, mut expanded: State<bool>) -> impl IntoElem
let now = !*expanded.peek(); let now = !*expanded.peek();
expanded.set(now); expanded.set(now);
}) })
.child(ChevronToggle { expanded: is_expanded }) .child(ChevronToggle {
expanded: is_expanded,
})
.child( .child(
label() label()
.text(if is_expanded { .text(if is_expanded {
@ -525,4 +527,3 @@ fn level_color(level: &Level) -> Color {
Level::Error => colors::danger(), Level::Error => colors::danger(),
} }
} }

View file

@ -8,7 +8,9 @@ use oneclient_content::packages::{CachedPackageMeta, ProviderId};
use oneclient_core::{BrowserPackageUpdate, ProfileUpdate}; use oneclient_core::{BrowserPackageUpdate, ProfileUpdate};
use oneclient_db::models::ClusterId; use oneclient_db::models::ClusterId;
use crate::components::{Button, Dropdown, Icon, IconType, OverlayPopup, ScrollArea, checkbox_labeled}; use crate::components::{
Button, Dropdown, Icon, IconType, OverlayPopup, ScrollArea, checkbox_labeled,
};
use crate::hooks::{ use crate::hooks::{
package_meta_batch, use_dispatch, use_notifications_snapshot, use_package_meta_batch, package_meta_batch, use_dispatch, use_notifications_snapshot, use_package_meta_batch,
}; };
@ -210,9 +212,7 @@ fn content(
.main_align(Alignment::Start) .main_align(Alignment::Start)
.cross_align(Alignment::Center) .cross_align(Alignment::Center)
.spacing(6.) .spacing(6.)
.child( .child(checkbox_labeled(dont_show_again, "Don't show again?")),
checkbox_labeled(dont_show_again, "Don't show again?")
)
) )
.child( .child(
rect() rect()
@ -267,7 +267,7 @@ fn content(
}) })
.child(Icon::new(IconType::DownloadCloud02).size(15.)) .child(Icon::new(IconType::DownloadCloud02).size(15.))
.text("Proceed"), .text("Proceed"),
) ),
), ),
) )
} }

View file

@ -52,26 +52,24 @@ impl Component for Pagination {
let current = *page.read(); let current = *page.read();
let last = total_pages.saturating_sub(1); let last = total_pages.saturating_sub(1);
let icon_btn = move |target: usize, let icon_btn =
available: bool, move |target: usize, available: bool, icon: IconType, tooltip: &'static str| {
icon: IconType, let mut page = page;
tooltip: &'static str| { let enabled = available && interactive;
let mut page = page; Button::new()
let enabled = available && interactive; .secondary()
Button::new() .icon()
.secondary() .enabled(enabled)
.icon() .tooltip(tooltip)
.enabled(enabled) .tooltip_placement(TooltipPlacement::Top)
.tooltip(tooltip) .on_press(move |_| page.set(target))
.tooltip_placement(TooltipPlacement::Top) .child(Icon::new(icon).size(14.).color(if enabled {
.on_press(move |_| page.set(target)) colors::fg_primary()
.child(Icon::new(icon).size(14.).color(if enabled { } else {
colors::fg_primary() colors::fg_secondary().with_a(90)
} else { }))
colors::fg_secondary().with_a(90) .into_element()
})) };
.into_element()
};
let num_btn = move |target: usize| { let num_btn = move |target: usize| {
let mut page = page; let mut page = page;

View file

@ -35,10 +35,8 @@ impl Component for RecentsRow {
let sorted: Vec<Cluster> = sort_clusters_for_home(clusters); let sorted: Vec<Cluster> = sort_clusters_for_home(clusters);
let slots = recent_card_slots_for_window( let slots =
root_size.read().width, recent_card_slots_for_window(root_size.read().width, *scale_factor.read() as f32);
*scale_factor.read() as f32,
);
// Budget off cluster count not slot count an offset-only change makes the layout // Budget off cluster count not slot count an offset-only change makes the layout
// engine reuse a card's cached area freezing survivors at pre-resize widths // engine reuse a card's cached area freezing survivors at pre-resize widths
@ -255,7 +253,10 @@ impl Component for ClusterCard {
.height(Size::fill()) .height(Size::fill())
.position(Position::new_absolute()) .position(Position::new_absolute())
.layer(Layer::Relative(1)) .layer(Layer::Relative(1))
.child(DynamicArt::for_cluster(&self.cluster).max_edge(ART_PREVIEW_EDGE)), .child(
DynamicArt::for_cluster(&self.cluster)
.max_edge(ART_PREVIEW_EDGE),
),
) )
.child( .child(
rect() rect()

View file

@ -11,11 +11,11 @@ pub(crate) fn normalize_wheel_delta(delta: f64, scale_factor: f64) -> f32 {
return 0.0; return 0.0;
} }
const FREYA_LINE_SPEED: f64 = 53.0; const FREYA_LINE_SPEED: f64 = 53.0;
const FREYA_PIXEL_SPEED: f64 = 2.0; const FREYA_PIXEL_SPEED: f64 = 2.0;
const LINE_DELTA_TOLERANCE: f64 = 1e-6; const LINE_DELTA_TOLERANCE: f64 = 1e-6;
const LINE_SCROLL: f64 = 53.0; const LINE_SCROLL: f64 = 53.0;
const PIXEL_SCROLL: f64 = 1.0; const PIXEL_SCROLL: f64 = 1.0;
let lines = (delta / FREYA_LINE_SPEED).round(); let lines = (delta / FREYA_LINE_SPEED).round();
if lines != 0.0 && (delta - lines * FREYA_LINE_SPEED).abs() <= LINE_DELTA_TOLERANCE { if lines != 0.0 && (delta - lines * FREYA_LINE_SPEED).abs() <= LINE_DELTA_TOLERANCE {

View file

@ -10,12 +10,12 @@ use freya::{
use oneclient_events::Level; use oneclient_events::Level;
use crate::{ use crate::{
ui::{divider},
components::{Button, ButtonSize, Icon, IconType, TooltipPlacement, progress_track}, components::{Button, ButtonSize, Icon, IconType, TooltipPlacement, progress_track},
hooks::{use_dispatch, use_notifications_snapshot}, hooks::{use_dispatch, use_notifications_snapshot},
motion::use_animations_enabled, motion::use_animations_enabled,
notifications::{InboxEntry, NotificationActionKind}, notifications::{InboxEntry, NotificationActionKind},
theme::colors, theme::colors,
ui::divider,
}; };
const TOAST_W: f32 = 300.; const TOAST_W: f32 = 300.;
@ -339,12 +339,7 @@ fn close_button(dispatch: crate::Actions, id: u64) -> impl IntoElement {
) )
} }
fn message_card( fn message_card(entry: &InboxEntry, dispatch: crate::Actions, id: u64, ttl_pct: f32) -> Rect {
entry: &InboxEntry,
dispatch: crate::Actions,
id: u64,
ttl_pct: f32,
) -> Rect {
toast_shell(entry) toast_shell(entry)
.child( .child(
rect() rect()

View file

@ -287,11 +287,19 @@ fn place(anchor: AnchorRect, placement: TooltipPlacement, size: Size2D) -> (f32,
let (x, y) = match placement { let (x, y) = match placement {
TooltipPlacement::Top => ( TooltipPlacement::Top => (
centered_x, centered_x,
if fits_above || !fits_below { above } else { below }, if fits_above || !fits_below {
above
} else {
below
},
), ),
TooltipPlacement::Bottom => ( TooltipPlacement::Bottom => (
centered_x, centered_x,
if fits_below || !fits_above { below } else { above }, if fits_below || !fits_above {
below
} else {
above
},
), ),
TooltipPlacement::Left => ( TooltipPlacement::Left => (
if fits_before || !fits_after { if fits_before || !fits_after {

View file

@ -14,10 +14,7 @@ pub struct UploadToMclogs {
impl UploadToMclogs { impl UploadToMclogs {
pub fn new(has_log: bool, confirm: State<Option<Confirm>>) -> Self { pub fn new(has_log: bool, confirm: State<Option<Confirm>>) -> Self {
Self { Self { has_log, confirm }
has_log,
confirm
}
} }
} }

View file

@ -2,5 +2,6 @@ pub const WINDOW_APP_ID: &str = "oneclient_app";
pub const WINDOW_TITLE: &str = "OneClient"; pub const WINDOW_TITLE: &str = "OneClient";
pub const UPDATER_PUBKEY: &str = "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IDFGODk3MkMyMjg0MjFDMDUKUldRRkhFSW93bktKSHpkWjNEMXNzaDVINVpCTU8xSnhuK2RnV0dTZ2FkcFJWbG1zUkhGYTNjaUkK"; pub const UPDATER_PUBKEY: &str = "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IDFGODk3MkMyMjg0MjFDMDUKUldRRkhFSW93bktKSHpkWjNEMXNzaDVINVpCTU8xSnhuK2RnV0dTZ2FkcFJWbG1zUkhGYTNjaUkK";
pub const UPDATER_ENDPOINT: &str = "https://github.com/Polyfrost/OneLauncher/releases/latest/download/latest.json"; pub const UPDATER_ENDPOINT: &str =
pub const RELEASES_URL: &str = "https://github.com/Polyfrost/OneLauncher/releases/latest"; "https://github.com/Polyfrost/OneLauncher/releases/latest/download/latest.json";
pub const RELEASES_URL: &str = "https://github.com/Polyfrost/OneLauncher/releases/latest";

View file

@ -183,7 +183,10 @@ impl EventPump {
guard.game.stages.insert(cluster_id, stage); guard.game.stages.insert(cluster_id, stage);
if stage == LaunchStage::Checking { if stage == LaunchStage::Checking {
guard.game.error = None; guard.game.error = None;
guard.game.logs.insert(cluster_id, std::sync::Arc::new(Vec::new())); guard
.game
.logs
.insert(cluster_id, std::sync::Arc::new(Vec::new()));
} }
} }
for (cluster_id, line) in logs { for (cluster_id, line) in logs {
@ -264,7 +267,9 @@ fn reconcile(
.collect(); .collect();
for id in &want { for id in &want {
armed.entry(*id).or_insert_with(|| ToastTimer::armed(paused)); armed
.entry(*id)
.or_insert_with(|| ToastTimer::armed(paused));
} }
armed.retain(|id, _| want.contains(id)); armed.retain(|id, _| want.contains(id));
} }
@ -358,10 +363,7 @@ pub async fn start_launcher(
Ok(()) Ok(())
} }
pub fn report_startup_failure( pub fn report_startup_failure(station: &RadioStation<AppState, AppChannel>, err: &anyhow::Error) {
station: &RadioStation<AppState, AppChannel>,
err: &anyhow::Error,
) {
let message = err.to_string(); let message = err.to_string();
tracing::error!("launcher init failed: {err:#}"); tracing::error!("launcher init failed: {err:#}");
@ -379,7 +381,9 @@ pub fn report_startup_failure(
let mut guard = station.write_channel(AppChannel::Notifications); let mut guard = station.write_channel(AppChannel::Notifications);
let AppState { let AppState {
notifications, inbox, .. notifications,
inbox,
..
} = &mut **guard; } = &mut **guard;
notifications.dispatch( notifications.dispatch(
inbox, inbox,

View file

@ -104,7 +104,10 @@ pub fn bundle_slug(folder: &str) -> String {
pub fn info_plist(name: &str, executable: &str, folder: &str, icon: Option<&str>) -> String { pub fn info_plist(name: &str, executable: &str, folder: &str, icon: Option<&str>) -> String {
let display = xml_escape(&single_line(name)); let display = xml_escape(&single_line(name));
let icon_entry = icon.map_or_else(String::new, |icon| { let icon_entry = icon.map_or_else(String::new, |icon| {
format!("\t<key>CFBundleIconFile</key>\n\t<string>{}</string>\n", xml_escape(icon)) format!(
"\t<key>CFBundleIconFile</key>\n\t<string>{}</string>\n",
xml_escape(icon)
)
}); });
format!( format!(

View file

@ -24,12 +24,12 @@ use oneclient_events::{Answer, Level};
use tokio::sync::mpsc; use tokio::sync::mpsc;
use crate::components::IconType; use crate::components::IconType;
use crate::{invalidate_java_queries, launcher};
use crate::notifications::{ use crate::notifications::{
ClusterUpdateSummary, NotificationAction, NotificationSpec, OptionalModRef, OptionalModsGroup, ClusterUpdateSummary, NotificationAction, NotificationSpec, OptionalModRef, OptionalModsGroup,
PackageUpdateGroup, PendingPrompt, PackageUpdateGroup, PendingPrompt,
}; };
use crate::state::{AppChannel, AppState, AsyncStatus, RelocationState}; use crate::state::{AppChannel, AppState, AsyncStatus, RelocationState};
use crate::{invalidate_java_queries, launcher};
/// Over-disabling is the cheaper mistake a dead button beats a second game /// Over-disabling is the cheaper mistake a dead button beats a second game
const LAUNCH_HOLD: Duration = Duration::from_secs(2); const LAUNCH_HOLD: Duration = Duration::from_secs(2);
@ -172,7 +172,10 @@ impl Actions {
fn with_engine(&self, mutate: impl FnOnce(&mut AppState)) { fn with_engine(&self, mutate: impl FnOnce(&mut AppState)) {
{ {
let mut guard = self.station.clone().write_channel(AppChannel::Notifications); let mut guard = self
.station
.clone()
.write_channel(AppChannel::Notifications);
mutate(&mut guard); mutate(&mut guard);
} }
self.nudge(PumpSignal::Reconcile); self.nudge(PumpSignal::Reconcile);
@ -196,10 +199,8 @@ impl Actions {
guard.settings.error = None; guard.settings.error = None;
} }
let loaded = oneclient_core::settings::store::load_settings(Some( let loaded =
&state.services.events, oneclient_core::settings::store::load_settings(Some(&state.services.events)).await;
))
.await;
let discord_enabled = loaded.discord_enabled; let discord_enabled = loaded.discord_enabled;
*state.settings.write() = loaded.clone(); *state.settings.write() = loaded.clone();
state.discord.set_enabled(discord_enabled); state.discord.set_enabled(discord_enabled);
@ -231,7 +232,10 @@ impl Actions {
}); });
} }
fn mutate_settings(&self, mutate: impl FnOnce(&mut LauncherSettings)) -> Option<LauncherSettings> { fn mutate_settings(
&self,
mutate: impl FnOnce(&mut LauncherSettings),
) -> Option<LauncherSettings> {
let state = launcher::state().ok()?; let state = launcher::state().ok()?;
let updated = { let updated = {
let mut lock = state.settings.write(); let mut lock = state.settings.write();
@ -312,7 +316,7 @@ impl Actions {
self.persist(updated); self.persist(updated);
} }
} }
pub fn reset_onboarding(&self) { pub fn reset_onboarding(&self) {
if let Some(updated) = self.mutate_settings(|settings| { if let Some(updated) = self.mutate_settings(|settings| {
settings.seen_onboarding = false; settings.seen_onboarding = false;
@ -561,14 +565,12 @@ impl Actions {
Ok(_) => { Ok(_) => {
events.signal(oneclient_events::Signal::JavaChanged); events.signal(oneclient_events::Signal::JavaChanged);
invalidate_java_queries().await invalidate_java_queries().await
}, }
Err(err) => { Err(err) => events
events
.notify("Java install failed") .notify("Java install failed")
.body(err.to_string()) .body(err.to_string())
.error() .error()
.send() .send(),
}
} }
}); });
} }
@ -692,7 +694,11 @@ impl Actions {
} }
pub fn dismiss_toast(&self, entry_id: u64) { pub fn dismiss_toast(&self, entry_id: u64) {
self.with_engine(|state| state.notifications.dismiss_toast(&mut state.inbox, entry_id)); self.with_engine(|state| {
state
.notifications
.dismiss_toast(&mut state.inbox, entry_id)
});
} }
pub fn mark_notification_read(&self, entry_id: u64) { pub fn mark_notification_read(&self, entry_id: u64) {
@ -779,10 +785,8 @@ impl Actions {
}; };
let content = state.services.content(); let content = state.services.content();
let events = state.services.events.clone(); let events = state.services.events.clone();
let session = oneclient_events::GroupedProgressSession::start( let session =
&events, oneclient_events::GroupedProgressSession::start(&events, "Adding optional mods");
"Adding optional mods",
);
let opt_in = session.child( let opt_in = session.child(
"Enabling mods", "Enabling mods",
mods.len() as u64, mods.len() as u64,
@ -887,9 +891,7 @@ impl Actions {
} }
pub fn proceed_package_updates(&self, chosen: Vec<oneclient_core::BrowserPackageUpdate>) { pub fn proceed_package_updates(&self, chosen: Vec<oneclient_core::BrowserPackageUpdate>) {
self.with_engine(move |state| { self.with_engine(move |state| state.notifications.proceed_package_updates(chosen));
state.notifications.proceed_package_updates(chosen)
});
} }
pub fn close_package_updates(&self) { pub fn close_package_updates(&self) {
@ -1005,15 +1007,16 @@ impl Actions {
.error = None; .error = None;
} }
pub fn import_local_file(&self, cluster_id: ClusterId, content_type: ContentType, path: PathBuf) { pub fn import_local_file(
&self,
cluster_id: ClusterId,
content_type: ContentType,
path: PathBuf,
) {
self.import_local_files(cluster_id, vec![(path, content_type)]); self.import_local_files(cluster_id, vec![(path, content_type)]);
} }
pub fn import_local_files( pub fn import_local_files(&self, cluster_id: ClusterId, files: Vec<(PathBuf, ContentType)>) {
&self,
cluster_id: ClusterId,
files: Vec<(PathBuf, ContentType)>,
) {
if files.is_empty() { if files.is_empty() {
return; return;
} }
@ -1042,7 +1045,11 @@ impl Actions {
deferred |= live == LiveSync::Deferred; deferred |= live == LiveSync::Deferred;
} }
notify_import(&events, &report, deferred && state.games.is_active(cluster_id)); notify_import(
&events,
&report,
deferred && state.games.is_active(cluster_id),
);
super::invalidate_cluster_queries().await; super::invalidate_cluster_queries().await;
} }
Err(err) => events Err(err) => events
@ -1353,7 +1360,7 @@ impl Actions {
.relocation = relocation; .relocation = relocation;
} }
/// `syncing_bundles` gates every launch button so the readiness check must /// `syncing_bundles` gates every launch button so the readiness check must
/// happen before the flag is raised not inside the task /// happen before the flag is raised not inside the task
pub fn sync_bundles(&self) { pub fn sync_bundles(&self) {
let state = match launcher::state() { let state = match launcher::state() {
@ -1372,7 +1379,6 @@ impl Actions {
.syncing_bundles = true; .syncing_bundles = true;
spawn_forever(async move { spawn_forever(async move {
if let Err(err) = state.bundles.sync(&state.services.content()).await { if let Err(err) = state.bundles.sync(&state.services.content()).await {
tracing::error!("bundle catalog sync failed: {err:#}"); tracing::error!("bundle catalog sync failed: {err:#}");
} }
@ -1416,26 +1422,22 @@ impl Actions {
.launcher .launcher
.syncing_bundles = true; .syncing_bundles = true;
let synced = match tokio::time::timeout(BUNDLE_SYNC_BUDGET, state.bundles.sync(&content)) let synced =
.await match tokio::time::timeout(BUNDLE_SYNC_BUDGET, state.bundles.sync(&content)).await {
{ Ok(Ok(_)) => true,
Ok(Ok(_)) => true, Ok(Err(err)) => {
Ok(Err(err)) => { tracing::warn!(
tracing::warn!( cluster_id,
cluster_id, error = %err,
error = %err, "bundle catalog sync failed, launching against the cached catalog"
"bundle catalog sync failed, launching against the cached catalog" );
); false
false }
} Err(_elapsed) => {
Err(_elapsed) => { tracing::debug!(cluster_id, "bundle catalog sync exceeded its launch budget");
tracing::debug!( false
cluster_id, }
"bundle catalog sync exceeded its launch budget" };
);
false
}
};
self.station self.station
.clone() .clone()
@ -1664,7 +1666,9 @@ impl Actions {
let (done, wait) = tokio::sync::oneshot::channel(); let (done, wait) = tokio::sync::oneshot::channel();
self.with_engine(move |state| { self.with_engine(move |state| {
state.notifications.open_optional_mods(vec![group], Some(done)); state
.notifications
.open_optional_mods(vec![group], Some(done));
state.center_open = false; state.center_open = false;
}); });
@ -1678,7 +1682,9 @@ impl Actions {
let (done, wait) = tokio::sync::oneshot::channel(); let (done, wait) = tokio::sync::oneshot::channel();
self.with_engine(move |state| { self.with_engine(move |state| {
state.notifications.open_package_updates(vec![group], Some(done)); state
.notifications
.open_package_updates(vec![group], Some(done));
state.center_open = false; state.center_open = false;
}); });
@ -1825,10 +1831,10 @@ fn notify_import(events: &oneclient_events::EventBus, report: &LocalImportReport
[only] => Some(format!("Added {}", only.file_name)), [only] => Some(format!("Added {}", only.file_name)),
rows => Some(format!("Added {} files", rows.len())), rows => Some(format!("Added {} files", rows.len())),
}) else { }) else {
let reason = report let reason = report.failed.first().map_or_else(
.failed || "Nothing could be read".to_string(),
.first() |(_, err)| err.to_string(),
.map_or_else(|| "Nothing could be read".to_string(), |(_, err)| err.to_string()); );
events.notify("Import failed").body(reason).error().send(); events.notify("Import failed").body(reason).error().send();
return; return;
@ -2056,7 +2062,10 @@ mod tests {
#[test] #[test]
fn every_cluster_holding_the_runtime_is_named() { fn every_cluster_holding_the_runtime_is_named() {
let profiles = vec![profile("a", Some(JDK_25)), profile("b", Some(JDK_25))]; let profiles = vec![profile("a", Some(JDK_25)), profile("b", Some(JDK_25))];
let clusters = vec![cluster(1, "Alpha", Some("a")), cluster(2, "Beta", Some("b"))]; let clusters = vec![
cluster(1, "Alpha", Some("a")),
cluster(2, "Beta", Some("b")),
];
assert_eq!( assert_eq!(
clusters_pinned_to_java(&profiles, &clusters, JDK_25), clusters_pinned_to_java(&profiles, &clusters, JDK_25),

View file

@ -1,6 +1,6 @@
mod actions;
mod active_cluster; mod active_cluster;
mod debounce; mod debounce;
mod actions;
mod overlay_claims; mod overlay_claims;
mod queries; mod queries;
mod shortcut_actions; mod shortcut_actions;
@ -27,43 +27,39 @@ pub use actions::{Actions, NotificationBuilder, PumpSignal};
pub use queries::{ pub use queries::{
AddOfflineAccountKeys, BROWSE_PAGE_SIZE, BeginMicrosoftLoginMutation, CachedImageQuery, AddOfflineAccountKeys, BROWSE_PAGE_SIZE, BeginMicrosoftLoginMutation, CachedImageQuery,
CancelMicrosoftLoginKeys, CancelMicrosoftLoginMutation, ClusterAction, ClusterBundles, CancelMicrosoftLoginKeys, CancelMicrosoftLoginMutation, ClusterAction, ClusterBundles,
ClusterLogsQuery, FinishMicrosoftLoginMutation, LogAction, LogContentQuery, MigrationQuery, ClusterLogsQuery, DiscardLeftoversKeys, DiscardLeftoversMutation, FinishMicrosoftLoginMutation,
OnboardingBundlesQuery, RefreshAccountKeys, RemoveAccountKeys, ScreenshotAction, LeftoversQuery, LogAction, LogContentQuery, MigrationQuery, OnboardingBundlesQuery,
SetDefaultAccountKeys, StorageAction, StorageActionMutation, StorageReportQuery, TermsQuery, RefreshAccountKeys, RemoveAccountKeys, ScreenshotAction, SetDefaultAccountKeys, StorageAction,
UploadLogKeys, UploadLogMutation, UseLogAction, StorageActionMutation, StorageReportQuery, TermsQuery, UploadLogKeys, UploadLogMutation,
UseRefreshAccount, UseRemoveAccount, UseScreenshotAction, UseSetDefaultAccount, UseDiscardLeftovers, UseLogAction, UseRefreshAccount, UseRemoveAccount, UseScreenshotAction,
UseStorageAction, UseUploadLog, UseSetDefaultAccount, UseStorageAction, UseUploadLog, VERSIONS_PAGE_SIZE,
VERSIONS_PAGE_SIZE, accounts_have_microsoft, bundle_overrides_map, bundles_with_status_items, accounts_have_microsoft, bundle_overrides_map, bundles_with_status_items, category_list,
category_list, changelog_error, changelog_groups, changelog_is_loading, cluster_content_items, changelog_error, changelog_groups, changelog_is_loading, cluster_content_items,
content_type_for_slug, has_migration_data, invalidate_cluster_content_queries, content_type_for_slug, has_migration_data, invalidate_cluster_content_queries,
invalidate_cluster_queries, invalidate_java_queries, invalidate_cluster_queries, invalidate_java_queries, invalidate_leftovers_queries,
invalidate_logs_queries, invalidate_profile_queries, invalidate_screenshots_queries, invalidate_logs_queries, invalidate_profile_queries, invalidate_screenshots_queries,
invalidate_storage_queries, try_storage_report, use_storage_action, use_storage_report, invalidate_storage_queries, java_runtimes, latest_changelog_version, loaded_image,
DiscardLeftoversKeys, DiscardLeftoversMutation, LeftoversQuery, loader_versions, login_code_already_handled, migration_detection, mutation_error,
UseDiscardLeftovers, invalidate_leftovers_queries, mutation_ok, try_leftovers, mutation_is_pending, mutation_is_running, mutation_ok, onboarding_bundles_items,
use_discard_leftovers, use_leftovers, package_meta_batch, package_updates, pick_version_metadata, project_detail, provider_versions,
java_runtimes, latest_changelog_version, loaded_image, loader_versions, query_error, query_is_busy, query_is_loading, reset_login_code_dedup, search_items,
login_code_already_handled, migration_detection, search_pending, search_total, settled_or_loading, stale_hashes, terms_document, terms_error,
mutation_error, mutation_is_pending, mutation_is_running, onboarding_bundles_items, package_meta_batch, terms_is_loading, try_account, try_accounts, try_cluster_analytics, try_cluster_logs,
package_updates, pick_version_metadata, project_detail, provider_versions, query_error, try_cluster_screenshots, try_default_account, try_game_profile, try_global_analytics,
query_is_busy, stale_hashes, use_package_updates, try_leftovers, try_log_content, try_storage_report, use_account, use_accounts,
query_is_loading, reset_login_code_dedup, search_items, search_pending, search_total, use_add_microsoft_account, use_add_offline_account, use_begin_microsoft_login,
settled_or_loading, 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_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_cancel_microsoft_login, use_changelog, use_cluster, use_cluster_analytics,
use_cluster_logs, use_cluster_mutation, use_cluster_profile, use_cluster_screenshots, use_cluster_content, use_cluster_logs, use_cluster_mutation, use_cluster_profile,
use_cluster, use_cluster_settings, use_clusters, use_current_account, use_default_account, use_cluster_screenshots, use_cluster_settings, use_clusters, use_current_account,
use_finish_microsoft_login, use_game_profile, use_global_analytics, use_java_runtimes, use_default_account, use_discard_leftovers, use_finish_microsoft_login, use_game_profile,
use_loader_versions, use_local_image, use_log_action, use_log_content, use_migration, use_global_analytics, use_java_runtimes, use_leftovers, use_loader_versions, use_local_image,
use_named_profiles, use_onboarding_bundles, use_package_categories, use_package_meta_batch, use_log_action, use_log_content, use_migration, use_named_profiles, use_onboarding_bundles,
use_package_project, use_package_search, use_package_versions, use_package_versions_when, use_package_categories, use_package_meta_batch, use_package_project, use_package_search,
use_player_profile, use_package_updates, use_package_versions, use_package_versions_when, use_player_profile,
use_player_skin, use_provider_versions, use_refresh_account, use_refresh_all_accounts, use_player_skin, use_provider_versions, use_refresh_account, use_refresh_all_accounts,
use_remove_account, use_screenshot_action, use_screenshot_folder_watch, use_set_default_account, use_remove_account, use_screenshot_action, use_screenshot_folder_watch,
use_terms, use_upload_log, use_set_default_account, use_storage_action, use_storage_report, use_terms, use_upload_log,
use_version_metadata, use_versions, version_list, versions_metadata, versions_total, use_version_metadata, use_versions, version_list, versions_metadata, versions_total,
}; };
@ -117,7 +113,9 @@ pub fn use_account_switcher_open() -> bool {
} }
pub fn use_control_center_open() -> bool { pub fn use_control_center_open() -> bool {
use_radio(AppChannel::ControlCenter).read().control_center_open use_radio(AppChannel::ControlCenter)
.read()
.control_center_open
} }
pub fn use_game_snapshot() -> GameState { pub fn use_game_snapshot() -> GameState {
@ -148,4 +146,3 @@ pub fn use_microsoft_login_status() -> Option<LoginProgress> {
.microsoft_login .microsoft_login
.clone() .clone()
} }

View file

@ -18,7 +18,10 @@ impl OverlayClaims {
fn release(&self) { fn release(&self) {
let held = self.0.get(); let held = self.0.get();
debug_assert!(held > 0, "overlay claim released without a matching acquire"); debug_assert!(
held > 0,
"overlay claim released without a matching acquire"
);
self.0.set(held.saturating_sub(1)); self.0.set(held.saturating_sub(1));
} }
} }

View file

@ -1,10 +1,11 @@
use std::sync::Mutex; use std::sync::Mutex;
use freya::query::{ use freya::query::{
Mutation, MutationCapability, MutationStateData, QueriesStorage, Query, QueryCapability, UseMutation, UseQuery, use_mutation, use_query, Mutation, MutationCapability, MutationStateData, QueriesStorage, Query, QueryCapability,
UseMutation, UseQuery, use_mutation, use_query,
}; };
use oneclient_core::LauncherError;
use oneclient_auth::{AccountKind, MicrosoftLoginSession, MinecraftAccount}; use oneclient_auth::{AccountKind, MicrosoftLoginSession, MinecraftAccount};
use oneclient_core::LauncherError;
use uuid::Uuid; use uuid::Uuid;
static HANDLED_LOGIN_CODE: Mutex<Option<String>> = Mutex::new(None); static HANDLED_LOGIN_CODE: Mutex<Option<String>> = Mutex::new(None);
@ -132,7 +133,10 @@ impl MutationCapability for BeginMicrosoftLoginMutation {
type Keys = (); type Keys = ();
async fn run(&self, _keys: &Self::Keys) -> Result<Self::Ok, Self::Err> { async fn run(&self, _keys: &Self::Keys) -> Result<Self::Ok, Self::Err> {
Ok(crate::launcher::state()?.auth.begin_microsoft_login().await?) Ok(crate::launcher::state()?
.auth
.begin_microsoft_login()
.await?)
} }
} }
@ -145,7 +149,10 @@ impl MutationCapability for FinishMicrosoftLoginMutation {
type Keys = MicrosoftLoginSession; type Keys = MicrosoftLoginSession;
async fn run(&self, flow: &Self::Keys) -> Result<Self::Ok, Self::Err> { async fn run(&self, flow: &Self::Keys) -> Result<Self::Ok, Self::Err> {
Ok(crate::launcher::state()?.auth.finish_microsoft_login(flow.clone()).await?) Ok(crate::launcher::state()?
.auth
.finish_microsoft_login(flow.clone())
.await?)
} }
async fn on_settled(&self, _keys: &Self::Keys, result: &Result<Self::Ok, Self::Err>) { async fn on_settled(&self, _keys: &Self::Keys, result: &Result<Self::Ok, Self::Err>) {
@ -170,7 +177,10 @@ impl MutationCapability for CancelMicrosoftLoginMutation {
type Keys = CancelMicrosoftLoginKeys; type Keys = CancelMicrosoftLoginKeys;
async fn run(&self, keys: &Self::Keys) -> Result<Self::Ok, Self::Err> { async fn run(&self, keys: &Self::Keys) -> Result<Self::Ok, Self::Err> {
crate::launcher::state()?.auth.cancel_microsoft_login(&keys.state_token).await; crate::launcher::state()?
.auth
.cancel_microsoft_login(&keys.state_token)
.await;
Ok(()) Ok(())
} }
} }
@ -210,7 +220,10 @@ impl MutationCapability for AddOfflineAccountMutation {
type Keys = AddOfflineAccountKeys; type Keys = AddOfflineAccountKeys;
async fn run(&self, keys: &Self::Keys) -> Result<Self::Ok, Self::Err> { async fn run(&self, keys: &Self::Keys) -> Result<Self::Ok, Self::Err> {
Ok(crate::launcher::state()?.auth.add_offline_account(keys.username.clone()).await?) Ok(crate::launcher::state()?
.auth
.add_offline_account(keys.username.clone())
.await?)
} }
async fn on_settled(&self, _keys: &Self::Keys, result: &Result<Self::Ok, Self::Err>) { async fn on_settled(&self, _keys: &Self::Keys, result: &Result<Self::Ok, Self::Err>) {
@ -234,7 +247,10 @@ impl MutationCapability for RemoveAccountMutation {
type Keys = RemoveAccountKeys; type Keys = RemoveAccountKeys;
async fn run(&self, keys: &Self::Keys) -> Result<Self::Ok, Self::Err> { async fn run(&self, keys: &Self::Keys) -> Result<Self::Ok, Self::Err> {
Ok(crate::launcher::state()?.auth.remove_account(keys.id).await?) Ok(crate::launcher::state()?
.auth
.remove_account(keys.id)
.await?)
} }
async fn on_settled(&self, keys: &Self::Keys, result: &Result<Self::Ok, Self::Err>) { async fn on_settled(&self, keys: &Self::Keys, result: &Result<Self::Ok, Self::Err>) {
@ -258,7 +274,10 @@ impl MutationCapability for SetDefaultAccountMutation {
type Keys = SetDefaultAccountKeys; type Keys = SetDefaultAccountKeys;
async fn run(&self, keys: &Self::Keys) -> Result<Self::Ok, Self::Err> { async fn run(&self, keys: &Self::Keys) -> Result<Self::Ok, Self::Err> {
Ok(crate::launcher::state()?.auth.set_default_account(keys.id).await?) Ok(crate::launcher::state()?
.auth
.set_default_account(keys.id)
.await?)
} }
async fn on_settled(&self, keys: &Self::Keys, result: &Result<Self::Ok, Self::Err>) { async fn on_settled(&self, keys: &Self::Keys, result: &Result<Self::Ok, Self::Err>) {
@ -282,7 +301,10 @@ impl MutationCapability for RefreshAccountMutation {
type Keys = RefreshAccountKeys; type Keys = RefreshAccountKeys;
async fn run(&self, keys: &Self::Keys) -> Result<Self::Ok, Self::Err> { async fn run(&self, keys: &Self::Keys) -> Result<Self::Ok, Self::Err> {
Ok(crate::launcher::state()?.auth.refresh_account(keys.id).await?) Ok(crate::launcher::state()?
.auth
.refresh_account(keys.id)
.await?)
} }
async fn on_settled(&self, keys: &Self::Keys, result: &Result<Self::Ok, Self::Err>) { async fn on_settled(&self, keys: &Self::Keys, result: &Result<Self::Ok, Self::Err>) {
@ -301,7 +323,10 @@ impl MutationCapability for RefreshAllAccountsMutation {
type Keys = (); type Keys = ();
async fn run(&self, _keys: &Self::Keys) -> Result<Self::Ok, Self::Err> { async fn run(&self, _keys: &Self::Keys) -> Result<Self::Ok, Self::Err> {
Ok(crate::launcher::state()?.auth.refresh_all_accounts().await?) Ok(crate::launcher::state()?
.auth
.refresh_all_accounts()
.await?)
} }
async fn on_settled(&self, _keys: &Self::Keys, result: &Result<Self::Ok, Self::Err>) { async fn on_settled(&self, _keys: &Self::Keys, result: &Result<Self::Ok, Self::Err>) {

View file

@ -33,7 +33,11 @@ impl QueryCapability for OnboardingBundlesQuery {
for cluster in clusters { for cluster in clusters {
let archives = state let archives = state
.bundles .bundles
.archives_for(&state.services.content(), &cluster.mc_version, cluster.mc_loader) .archives_for(
&state.services.content(),
&cluster.mc_version,
cluster.mc_loader,
)
.await .await
.unwrap_or_default(); .unwrap_or_default();
out.push(ClusterBundles { cluster, archives }); out.push(ClusterBundles { cluster, archives });
@ -70,14 +74,12 @@ impl QueryCapability for BundlesWithStatusQuery {
async fn run(&self, keys: &Self::Keys) -> Result<Self::Ok, Self::Err> { async fn run(&self, keys: &Self::Keys) -> Result<Self::Ok, Self::Err> {
let _ = keys; let _ = keys;
let state = crate::launcher::state()?; let state = crate::launcher::state()?;
Ok( Ok(get_bundles_with_update_status(
get_bundles_with_update_status( self.cluster_id,
self.cluster_id, state.bundles.as_ref(),
state.bundles.as_ref(), &state.services.content(),
&state.services.content(),
)
.await?,
) )
.await?)
} }
} }
@ -112,7 +114,8 @@ impl QueryCapability for BundleOverridesQuery {
async fn run(&self, keys: &Self::Keys) -> Result<Self::Ok, Self::Err> { async fn run(&self, keys: &Self::Keys) -> Result<Self::Ok, Self::Err> {
let _ = keys; let _ = keys;
let state = crate::launcher::state()?; let state = crate::launcher::state()?;
let rows = list_cluster_bundle_overrides(self.cluster_id, &state.services.content()).await?; let rows =
list_cluster_bundle_overrides(self.cluster_id, &state.services.content()).await?;
Ok(rows Ok(rows
.into_iter() .into_iter()
.map(|(bundle, pkg, ty)| ((bundle, pkg), ty)) .map(|(bundle, pkg, ty)| ((bundle, pkg), ty))

View file

@ -1,7 +1,5 @@
use freya::query::{Query, QueryCapability, UseQuery, use_query}; use freya::query::{Query, QueryCapability, UseQuery, use_query};
use oneclient_core::{ use oneclient_core::{ChangelogGroup, LauncherError, fetch_changelog, parse_changelog};
ChangelogGroup, LauncherError, fetch_changelog, parse_changelog,
};
#[derive(Clone, Debug, PartialEq, Eq, Hash)] #[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct ChangelogKeys { pub struct ChangelogKeys {

View file

@ -22,7 +22,8 @@ impl QueryCapability for ClusterContentQuery {
async fn run(&self, _keys: &Self::Keys) -> Result<Self::Ok, Self::Err> { async fn run(&self, _keys: &Self::Keys) -> Result<Self::Ok, Self::Err> {
let state = crate::launcher::state()?; let state = crate::launcher::state()?;
let all = PackageStore::list_linked_artifacts(self.cluster_id, &state.services.content()).await?; let all =
PackageStore::list_linked_artifacts(self.cluster_id, &state.services.content()).await?;
Ok(all Ok(all
.into_iter() .into_iter()
.filter(|item| item.content_type == self.content_type) .filter(|item| item.content_type == self.content_type)

View file

@ -37,7 +37,10 @@ impl QueryCapability for CachedImageQuery {
/// Returns the url alongside the bytes callers key their image handle on it /// Returns the url alongside the bytes callers key their image handle on it
/// and a stale url with fresh bytes renders the wrong picture for a frame /// and a stale url with fresh bytes renders the wrong picture for a frame
pub fn loaded_image(url: Option<&str>, query: &UseQuery<CachedImageQuery>) -> Option<(String, Bytes)> { pub fn loaded_image(
url: Option<&str>,
query: &UseQuery<CachedImageQuery>,
) -> Option<(String, Bytes)> {
let url = url?; let url = url?;
let bytes = super::state::settled_or_loading(query)?; let bytes = super::state::settled_or_loading(query)?;
Some((url.to_string(), bytes)) Some((url.to_string(), bytes))

View file

@ -14,9 +14,7 @@ impl QueryCapability for ListJavaRuntimesQuery {
async fn run(&self, _keys: &Self::Keys) -> Result<Self::Ok, Self::Err> { async fn run(&self, _keys: &Self::Keys) -> Result<Self::Ok, Self::Err> {
let state = crate::launcher::state().map_err(|e| e.to_string())?; let state = crate::launcher::state().map_err(|e| e.to_string())?;
state.java.list_runtimes() state.java.list_runtimes().await.map_err(|e| e.to_string())
.await
.map_err(|e| e.to_string())
} }
} }
@ -47,7 +45,9 @@ impl QueryCapability for ProviderVersionsQuery {
async fn run(&self, keys: &Self::Keys) -> Result<Self::Ok, Self::Err> { async fn run(&self, keys: &Self::Keys) -> Result<Self::Ok, Self::Err> {
let state = crate::launcher::state().map_err(|e| e.to_string())?; let state = crate::launcher::state().map_err(|e| e.to_string())?;
state.java.available_versions(&keys.vendor) state
.java
.available_versions(&keys.vendor)
.await .await
.map_err(|e| e.to_string()) .map_err(|e| e.to_string())
} }

View file

@ -1,8 +1,8 @@
use std::path::PathBuf; use std::path::PathBuf;
use freya::query::{ use freya::query::{
Mutation, MutationCapability, QueriesStorage, Query, QueryCapability, Mutation, MutationCapability, QueriesStorage, Query, QueryCapability, UseMutation, UseQuery,
UseMutation, UseQuery, use_mutation, use_query, use_mutation, use_query,
}; };
use oneclient_core::{ use oneclient_core::{
LauncherError, LogFileInfo, LogLevel, LogLine, MclogsUploadResponse, ReadOptions, LauncherError, LogFileInfo, LogLevel, LogLine, MclogsUploadResponse, ReadOptions,

View file

@ -40,11 +40,11 @@ pub use auth::{
CancelMicrosoftLoginMutation, FinishMicrosoftLoginMutation, RefreshAccountKeys, CancelMicrosoftLoginMutation, FinishMicrosoftLoginMutation, RefreshAccountKeys,
RemoveAccountKeys, SetDefaultAccountKeys, UseRefreshAccount, UseRemoveAccount, RemoveAccountKeys, SetDefaultAccountKeys, UseRefreshAccount, UseRemoveAccount,
UseSetDefaultAccount, accounts_have_microsoft, login_code_already_handled, mutation_error, UseSetDefaultAccount, accounts_have_microsoft, login_code_already_handled, mutation_error,
mutation_is_pending, mutation_is_running, mutation_ok, reset_login_code_dedup, try_account, try_accounts, try_default_account, mutation_is_pending, mutation_is_running, mutation_ok, reset_login_code_dedup, try_account,
use_account, use_accounts, use_add_microsoft_account, use_add_offline_account, try_accounts, try_default_account, use_account, use_accounts, use_add_microsoft_account,
use_begin_microsoft_login, use_cancel_microsoft_login, use_current_account, use_add_offline_account, use_begin_microsoft_login, use_cancel_microsoft_login,
use_default_account, use_finish_microsoft_login, use_refresh_account, use_refresh_all_accounts, use_current_account, use_default_account, use_finish_microsoft_login, use_refresh_account,
use_remove_account, use_set_default_account, use_refresh_all_accounts, use_remove_account, use_set_default_account,
}; };
pub use bundles::{ pub use bundles::{
ClusterBundles, OnboardingBundlesQuery, bundle_overrides_map, bundles_with_status_items, ClusterBundles, OnboardingBundlesQuery, bundle_overrides_map, bundles_with_status_items,
@ -52,7 +52,8 @@ pub use bundles::{
use_onboarding_bundles, use_onboarding_bundles,
}; };
pub use changelog::{ pub use changelog::{
changelog_error, changelog_groups, changelog_is_loading, latest_changelog_version, use_changelog, changelog_error, changelog_groups, changelog_is_loading, latest_changelog_version,
use_changelog,
}; };
pub use cluster_content::{cluster_content_items, use_cluster_content}; pub use cluster_content::{cluster_content_items, use_cluster_content};
pub use clusters::{use_cluster, use_clusters}; pub use clusters::{use_cluster, use_clusters};
@ -67,13 +68,6 @@ pub use logs::{
use_log_action, use_log_content, use_upload_log, use_log_action, use_log_content, use_upload_log,
}; };
pub use migration::{MigrationQuery, has_migration_data, migration_detection, use_migration}; pub use migration::{MigrationQuery, has_migration_data, migration_detection, use_migration};
pub use storage::{
DiscardLeftoversKeys, DiscardLeftoversMutation, LeftoversQuery,
StorageAction, StorageActionMutation, StorageReportQuery, UseDiscardLeftovers,
UseStorageAction, invalidate_leftovers_queries, invalidate_storage_queries, try_leftovers,
try_storage_report, use_discard_leftovers, use_leftovers, use_storage_action,
use_storage_report,
};
pub use mutations::{ pub use mutations::{
ClusterAction, invalidate_cluster_content_queries, invalidate_cluster_queries, ClusterAction, invalidate_cluster_content_queries, invalidate_cluster_queries,
invalidate_profile_queries, use_cluster_mutation, invalidate_profile_queries, use_cluster_mutation,
@ -83,8 +77,7 @@ pub use packages::{
BROWSE_PAGE_SIZE, VERSIONS_PAGE_SIZE, category_list, content_type_for_slug, package_meta_batch, BROWSE_PAGE_SIZE, VERSIONS_PAGE_SIZE, category_list, content_type_for_slug, package_meta_batch,
project_detail, search_items, search_pending, search_total, use_package_categories, project_detail, search_items, search_pending, search_total, use_package_categories,
use_package_meta_batch, use_package_project, use_package_search, use_package_versions, use_package_meta_batch, use_package_project, use_package_search, use_package_versions,
use_package_versions_when, use_package_versions_when, version_list, versions_total,
version_list, versions_total,
}; };
pub use player_profile::use_player_profile; pub use player_profile::use_player_profile;
pub use screenshots::{ pub use screenshots::{
@ -97,6 +90,12 @@ pub use settings_profiles::{
}; };
pub use skin::use_player_skin; pub use skin::use_player_skin;
pub use state::{query_error, query_is_busy, query_is_loading, settled_or_loading}; pub use state::{query_error, query_is_busy, query_is_loading, settled_or_loading};
pub use storage::{
DiscardLeftoversKeys, DiscardLeftoversMutation, LeftoversQuery, StorageAction,
StorageActionMutation, StorageReportQuery, UseDiscardLeftovers, UseStorageAction,
invalidate_leftovers_queries, invalidate_storage_queries, try_leftovers, try_storage_report,
use_discard_leftovers, use_leftovers, use_storage_action, use_storage_report,
};
pub use tos::{TermsQuery, terms_document, terms_error, terms_is_loading, use_terms}; 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 version_metadata::{pick_version_metadata, use_version_metadata};
pub use versions::{loader_versions, use_loader_versions, use_versions, versions_metadata}; pub use versions::{loader_versions, use_loader_versions, use_versions, versions_metadata};

View file

@ -23,12 +23,36 @@ async fn timed(step: &'static str, fut: impl std::future::Future<Output = ()>) {
pub async fn invalidate_cluster_queries() { pub async fn invalidate_cluster_queries() {
let started = std::time::Instant::now(); let started = std::time::Instant::now();
timed("cluster_content", QueriesStorage::<ClusterContentQuery>::invalidate_all()).await; timed(
timed("bundle_overrides", QueriesStorage::<BundleOverridesQuery>::invalidate_all()).await; "cluster_content",
timed("bundles_with_status", QueriesStorage::<BundlesWithStatusQuery>::invalidate_all()).await; QueriesStorage::<ClusterContentQuery>::invalidate_all(),
timed("clusters", QueriesStorage::<ListClustersQuery>::invalidate_all()).await; )
timed("bundle_updates", QueriesStorage::<BundleUpdatesQuery>::invalidate_all()).await; .await;
timed("package_updates", QueriesStorage::<PackageUpdatesQuery>::invalidate_all()).await; timed(
"bundle_overrides",
QueriesStorage::<BundleOverridesQuery>::invalidate_all(),
)
.await;
timed(
"bundles_with_status",
QueriesStorage::<BundlesWithStatusQuery>::invalidate_all(),
)
.await;
timed(
"clusters",
QueriesStorage::<ListClustersQuery>::invalidate_all(),
)
.await;
timed(
"bundle_updates",
QueriesStorage::<BundleUpdatesQuery>::invalidate_all(),
)
.await;
timed(
"package_updates",
QueriesStorage::<PackageUpdatesQuery>::invalidate_all(),
)
.await;
tracing::debug!( tracing::debug!(
target: "oneclient_app::perf", target: "oneclient_app::perf",
ms = started.elapsed().as_millis() as u64, ms = started.elapsed().as_millis() as u64,
@ -47,10 +71,26 @@ pub async fn invalidate_cluster_content_queries() {
/// The bundle queries do move a toggle writes a bundle override and both read /// The bundle queries do move a toggle writes a bundle override and both read
/// those back /// those back
async fn invalidate_enabled_flag_queries() { async fn invalidate_enabled_flag_queries() {
timed("cluster_content", QueriesStorage::<ClusterContentQuery>::invalidate_all()).await; timed(
timed("bundle_overrides", QueriesStorage::<BundleOverridesQuery>::invalidate_all()).await; "cluster_content",
timed("bundles_with_status", QueriesStorage::<BundlesWithStatusQuery>::invalidate_all()).await; QueriesStorage::<ClusterContentQuery>::invalidate_all(),
timed("bundle_updates", QueriesStorage::<BundleUpdatesQuery>::invalidate_all()).await; )
.await;
timed(
"bundle_overrides",
QueriesStorage::<BundleOverridesQuery>::invalidate_all(),
)
.await;
timed(
"bundles_with_status",
QueriesStorage::<BundlesWithStatusQuery>::invalidate_all(),
)
.await;
timed(
"bundle_updates",
QueriesStorage::<BundleUpdatesQuery>::invalidate_all(),
)
.await;
} }
pub async fn invalidate_profile_queries() { pub async fn invalidate_profile_queries() {
@ -195,7 +235,13 @@ impl MutationCapability for ClusterMutation {
if let Err(err) = result if let Err(err) = result
&& let Ok(state) = crate::launcher::state() && let Ok(state) = crate::launcher::state()
{ {
state.services.events.notify("Action failed").body(err).error().send(); state
.services
.events
.notify("Action failed")
.body(err)
.error()
.send();
} }
if matches!(keys, ClusterAction::SetArtifactEnabled { .. }) { if matches!(keys, ClusterAction::SetArtifactEnabled { .. }) {
invalidate_enabled_flag_queries().await; invalidate_enabled_flag_queries().await;

View file

@ -27,7 +27,10 @@ impl QueryCapability for FetchPlayerProfileQuery {
let state = crate::launcher::state()?; let state = crate::launcher::state()?;
let client = &state.services.requester; let client = &state.services.requester;
Ok(minecraft::fetch_player_profile_view(client, &keys.uuid, access_token.as_deref()).await?) Ok(
minecraft::fetch_player_profile_view(client, &keys.uuid, access_token.as_deref())
.await?,
)
} }
// TODO Cache // TODO Cache

View file

@ -6,8 +6,8 @@ use std::time::Duration;
use bytes::Bytes; use bytes::Bytes;
use freya::prelude::{spawn, use_hook}; use freya::prelude::{spawn, use_hook};
use freya::query::{ use freya::query::{
Mutation, MutationCapability, QueriesStorage, Query, QueryCapability, Mutation, MutationCapability, QueriesStorage, Query, QueryCapability, UseMutation, UseQuery,
UseMutation, UseQuery, use_mutation, use_query, use_mutation, use_query,
}; };
use notify::{EventKind, RecursiveMode, Watcher}; use notify::{EventKind, RecursiveMode, Watcher};
use oneclient_core::{LauncherError, ScreenshotInfo}; use oneclient_core::{LauncherError, ScreenshotInfo};
@ -119,15 +119,14 @@ async fn watch_folder(
std::fs::create_dir_all(folder)?; std::fs::create_dir_all(folder)?;
let (tx, mut rx) = mpsc::unbounded_channel(); let (tx, mut rx) = mpsc::unbounded_channel();
let mut watcher = notify::recommended_watcher(move |event: notify::Result<notify::Event>| { let mut watcher =
match event { notify::recommended_watcher(move |event: notify::Result<notify::Event>| match event {
Ok(event) if touches_image(&event) => { Ok(event) if touches_image(&event) => {
let _ = tx.send(()); let _ = tx.send(());
} }
Ok(_) => {} Ok(_) => {}
Err(err) => tracing::debug!(error = %err, "screenshot watcher reported an error"), Err(err) => tracing::debug!(error = %err, "screenshot watcher reported an error"),
} })?;
})?;
watcher.watch(folder, RecursiveMode::NonRecursive)?; watcher.watch(folder, RecursiveMode::NonRecursive)?;
let mut pending = false; let mut pending = false;

View file

@ -1,8 +1,8 @@
use freya::query::{Query, QueryCapability, UseQuery, use_query}; use freya::query::{Query, QueryCapability, UseQuery, use_query};
use oneclient_core::settings::GameSettingsProfile;
use oneclient_cluster::profiles::{ use oneclient_cluster::profiles::{
get_profile_or_default, list_named_profiles, resolve_cluster_profile, get_profile_or_default, list_named_profiles, resolve_cluster_profile,
}; };
use oneclient_core::settings::GameSettingsProfile;
use oneclient_db::models::ClusterId; use oneclient_db::models::ClusterId;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
@ -110,11 +110,15 @@ impl QueryCapability for ClusterSettingsQuery {
async fn run(&self, keys: &Self::Keys) -> Result<Self::Ok, Self::Err> { async fn run(&self, keys: &Self::Keys) -> Result<Self::Ok, Self::Err> {
let cluster_id = keys.cluster_id; let cluster_id = keys.cluster_id;
let state = crate::launcher::state().map_err(|e| e.to_string())?; let state = crate::launcher::state().map_err(|e| e.to_string())?;
let cluster = state.clusters.get(cluster_id) let cluster = state
.clusters
.get(cluster_id)
.await .await
.map_err(|e| e.to_string())?; .map_err(|e| e.to_string())?;
let global = state.settings.read().global_game_settings.clone(); let global = state.settings.read().global_game_settings.clone();
state.clusters.resolve_settings(&global, &cluster) state
.clusters
.resolve_settings(&global, &cluster)
.await .await
.map_err(|e| e.to_string()) .map_err(|e| e.to_string())
} }

View file

@ -1,6 +1,6 @@
use oneclient_common::VersionKey; use oneclient_common::VersionKey;
use oneclient_core::VersionMetadata;
use oneclient_common::domain::GameLoader; use oneclient_common::domain::GameLoader;
use oneclient_core::VersionMetadata;
use super::use_versions; use super::use_versions;

View file

@ -15,12 +15,18 @@ impl QueryCapability for VersionsMetadataQuery {
async fn run(&self, _keys: &Self::Keys) -> Result<Self::Ok, Self::Err> { async fn run(&self, _keys: &Self::Keys) -> Result<Self::Ok, Self::Err> {
let state = crate::launcher::state()?; let state = crate::launcher::state()?;
let metadata = state.versions.metadata(&state.services.requester.config().meta_url_base).await; let metadata = state
.versions
.metadata(&state.services.requester.config().meta_url_base)
.await;
if !metadata.is_empty() { if !metadata.is_empty() {
return Ok(metadata); return Ok(metadata);
} }
state.versions.sync(&state.services).await?; state.versions.sync(&state.services).await?;
Ok(state.versions.metadata(&state.services.requester.config().meta_url_base).await) Ok(state
.versions
.metadata(&state.services.requester.config().meta_url_base)
.await)
} }
} }

View file

@ -155,7 +155,10 @@ mod imp {
} }
pub async fn bind() -> Result<Listener, BindError> { pub async fn bind() -> Result<Listener, BindError> {
match ServerOptions::new().first_pipe_instance(true).create(ENDPOINT) { match ServerOptions::new()
.first_pipe_instance(true)
.create(ENDPOINT)
{
Ok(server) => Ok(Listener { server }), Ok(server) => Ok(Listener { server }),
Err(err) if err.raw_os_error() == Some(ERROR_ACCESS_DENIED) => Err(BindError::Taken), Err(err) if err.raw_os_error() == Some(ERROR_ACCESS_DENIED) => Err(BindError::Taken),
Err(err) => Err(BindError::Io(err)), Err(err) => Err(BindError::Io(err)),
@ -244,7 +247,10 @@ mod imp {
let listener = UnixListener::bind(path)?; let listener = UnixListener::bind(path)?;
std::fs::set_permissions(path, std::fs::Permissions::from_mode(0o600))?; std::fs::set_permissions(path, std::fs::Permissions::from_mode(0o600))?;
Ok(Listener { listener, path: path.to_path_buf() }) Ok(Listener {
listener,
path: path.to_path_buf(),
})
} }
pub async fn send(request: &str) -> bool { pub async fn send(request: &str) -> bool {
@ -284,7 +290,10 @@ mod tests {
#[test] #[test]
fn focus_survives_the_wire() { fn focus_survives_the_wire() {
assert_eq!(IpcCommand::decode(&IpcCommand::Focus.encode()), Some(IpcCommand::Focus)); assert_eq!(
IpcCommand::decode(&IpcCommand::Focus.encode()),
Some(IpcCommand::Focus)
);
} }
#[test] #[test]
@ -313,7 +322,9 @@ mod tests {
fn a_bare_start_asks_only_for_the_window() { fn a_bare_start_asks_only_for_the_window() {
assert_eq!(request_for(&Cli::default()), IpcCommand::Focus); assert_eq!(request_for(&Cli::default()), IpcCommand::Focus);
assert_eq!( assert_eq!(
request_for(&Cli { launch: Some("pack".into()) }), request_for(&Cli {
launch: Some("pack".into())
}),
IpcCommand::Launch("pack".into()), IpcCommand::Launch("pack".into()),
); );
} }

View file

@ -18,8 +18,5 @@ pub fn install(state: Arc<LauncherState>) -> Arc<LauncherState> {
/// Errors with [`LauncherError::NotInitialized`] before startup installs the handle /// Errors with [`LauncherError::NotInitialized`] before startup installs the handle
pub fn state() -> LauncherResult<Arc<LauncherState>> { pub fn state() -> LauncherResult<Arc<LauncherState>> {
LAUNCHER LAUNCHER.get().cloned().ok_or(LauncherError::NotInitialized)
.get()
.cloned()
.ok_or(LauncherError::NotInitialized)
} }

View file

@ -77,7 +77,7 @@ impl Component for AppShell {
.on_global_file_hover_cancelled(move |_| drop_hovering.set(false)) .on_global_file_hover_cancelled(move |_| drop_hovering.set(false))
.on_file_drop(move |e: Event<FileEventData>| { .on_file_drop(move |e: Event<FileEventData>| {
drop_hovering.set(false); drop_hovering.set(false);
drop_pending.write().extend_from_slice(&e.file_paths); drop_pending.write().extend_from_slice(&e.file_paths);
}) })
.child(AppNavbar) .child(AppNavbar)
.child(AppHomeBackground) .child(AppHomeBackground)

View file

@ -4,7 +4,9 @@ use freya::router::*;
use oneclient_common::parse_mc_version; use oneclient_common::parse_mc_version;
use crate::components::{Button, Icon, IconType, TabBar, TabItem}; use crate::components::{Button, Icon, IconType, TabBar, TabItem};
use crate::hooks::{use_cluster, use_dispatch, use_game_snapshot, use_launcher, use_version_metadata}; use crate::hooks::{
use_cluster, use_dispatch, use_game_snapshot, use_launcher, use_version_metadata,
};
use crate::routes::Route; use crate::routes::Route;
use crate::theme::colors; use crate::theme::colors;
use crate::ui::entrance_motion_layer; use crate::ui::entrance_motion_layer;
@ -107,7 +109,9 @@ impl Component for ClusterShell {
// Queried unconditionally the shell can mount before the cluster list settles // Queried unconditionally the shell can mount before the cluster list settles
// and a conditional hook would change this component's hook count mid-life // and a conditional hook would change this component's hook count mid-life
let parsed = cluster.as_ref().and_then(|c| parse_mc_version(&c.mc_version)); let parsed = cluster
.as_ref()
.and_then(|c| parse_mc_version(&c.mc_version));
let metadata = use_version_metadata( let metadata = use_version_metadata(
parsed.as_ref().map(|p| p.major), parsed.as_ref().map(|p| p.major),
parsed.and_then(|p| p.key()), parsed.and_then(|p| p.key()),

View file

@ -117,7 +117,10 @@ fn report_already_open(
(None, true) => format!("{} is open already.", cluster.name), (None, true) => format!("{} is open already.", cluster.name),
(None, false) => format!("{} is on its way up.", cluster.name), (None, false) => format!("{} is on its way up.", cluster.name),
(Some(other), true) => { (Some(other), true) => {
format!("{other} is open. Close it before launching {}.", cluster.name) format!(
"{other} is open. Close it before launching {}.",
cluster.name
)
} }
(Some(other), false) => format!( (Some(other), false) => format!(
"{other} is on its way up. Wait for it before launching {}.", "{other} is on its way up. Wait for it before launching {}.",

View file

@ -2,13 +2,13 @@ use freya::prelude::*;
use freya::router::*; use freya::router::*;
use crate::components::{ use crate::components::{
AccountSwitcher, ClusterUpdatePopup, ControlCenter, GenericPromptOverlay, JavaPromptOverlay, OptionalModsPopup, MicrosoftJavaPromptOverlay, AccountSwitcher, ClusterUpdatePopup, ControlCenter, GenericPromptOverlay, JavaPromptOverlay,
NotificationCenter, PackageUpdatePopup, SplashCurtain, StatusBar, Toasts, TooltipHost, MicrosoftJavaPromptOverlay, NotificationCenter, OptionalModsPopup, PackageUpdatePopup,
UpdatePromptOverlay, use_provide_tooltips, SplashCurtain, StatusBar, Toasts, TooltipHost, UpdatePromptOverlay, use_provide_tooltips,
}; };
use crate::hooks::{SplashState, use_provide_overlay_claims, use_provide_splash};
#[cfg(not(target_os = "macos"))] #[cfg(not(target_os = "macos"))]
use crate::hooks::use_start_maximized; use crate::hooks::use_start_maximized;
use crate::hooks::{SplashState, use_provide_overlay_claims, use_provide_splash};
use crate::layout::{HomeArtPrefetch, PendingLaunchDriver}; use crate::layout::{HomeArtPrefetch, PendingLaunchDriver};
use crate::motion::AnimationClockDriver; use crate::motion::AnimationClockDriver;
use crate::routes::Route; use crate::routes::Route;
@ -26,45 +26,12 @@ impl Component for RootLayout {
use_provide_overlay_claims(); use_provide_overlay_claims();
use_provide_tooltips(); use_provide_tooltips();
// macOS rounds the window natively so Freya must not round on top of it
#[cfg(target_os = "macos")]
let corner = 0.;
// Elsewhere the window is borderless and squared when maximized
// No reactive maximized signal exists so root_size changes stand in as the trigger
#[cfg(not(target_os = "macos"))]
let corner = {
let root_size = Platform::get().root_size;
// Seeded because the query below only answers after the window is visible
// which would round the corners of the first frames of a maximized launch
let start_maximized = use_start_maximized();
let mut maximized = use_state(move || start_maximized);
let size = *root_size.read();
let dep = (size.width as i32, size.height as i32);
use_side_effect_with_deps(&dep, move |_| {
spawn(async move {
let is_max = Platform::get()
.post_callback(|id, ctx| {
ctx.windows.get(&id).map(|w| w.window().is_maximized())
})
.await;
if let Ok(Some(is_max)) = is_max {
if *maximized.peek() != is_max {
maximized.set(is_max);
}
}
});
});
if *maximized.read() { 0. } else { 12. }
};
rect() rect()
.width(Size::fill()) .width(Size::fill())
.height(Size::fill()) .height(Size::fill())
.background(colors::page()) .background(colors::page())
.color(colors::fg_primary()) .color(colors::fg_primary())
.font_family(theme::DEFAULT_FONT) .font_family(theme::DEFAULT_FONT)
//.corner_radius(CornerRadius::new_all(corner))
.overflow(Overflow::Clip) .overflow(Overflow::Clip)
.child( .child(
rect() rect()

View file

@ -68,8 +68,16 @@ const SEARCH_INDEX: &[SearchItem] = &[
title: "Launcher Folder", title: "Launcher Folder",
description: "Open the launcher data directory, or move it to another drive.", description: "Open the launcher data directory, or move it to another drive.",
keywords: &[ keywords: &[
"data dir", "directory", "folder", "open", "move", "change", "relocate", "location", "data dir",
"drive", "disk", "directory",
"folder",
"open",
"move",
"change",
"relocate",
"location",
"drive",
"disk",
], ],
route: Route::SettingsLauncher {}, route: Route::SettingsLauncher {},
}, },
@ -457,11 +465,7 @@ impl Component for SettingsShell {
} }
} }
fn content_header( fn content_header(title: String, search: State<String>, mut width: State<f32>) -> impl IntoElement {
title: String,
search: State<String>,
mut width: State<f32>,
) -> impl IntoElement {
let search_width = search_width_for(*width.read()); let search_width = search_width_for(*width.read());
rect() rect()
@ -770,9 +774,7 @@ impl Component for SidebarInfo {
.width(Size::fill()) .width(Size::fill())
.spacing(4.) .spacing(4.)
.font_size(12.) .font_size(12.)
.padding(Gaps::new( .padding(Gaps::new(15., 0., 0., 0.))
15.,0.,0.,0.
))
.color(colors::fg_secondary()) .color(colors::fg_secondary())
.children( .children(
items items

View file

@ -6,14 +6,12 @@
mod assets; mod assets;
pub mod cli; pub mod cli;
mod components; mod components;
pub mod hooks;
pub mod events; pub mod events;
pub(crate) mod file_content; pub(crate) mod file_content;
pub mod hooks;
mod install; mod install;
pub mod ipc; pub mod ipc;
mod launcher; mod launcher;
pub mod state;
mod transfer;
mod layout; mod layout;
pub mod microsoft_java; pub mod microsoft_java;
mod motion; mod motion;
@ -23,7 +21,9 @@ pub mod protocol;
pub mod recovery; pub mod recovery;
mod routes; mod routes;
pub mod shortcut; pub mod shortcut;
pub mod state;
pub mod theme; pub mod theme;
mod transfer;
mod ui; mod ui;
pub mod updater; pub mod updater;
pub(crate) mod utils; pub(crate) mod utils;
@ -32,8 +32,8 @@ mod view;
pub mod constants; pub mod constants;
pub use assets::AppAssets; pub use assets::AppAssets;
pub use events::EventPump;
pub use state::{AppChannel, AppState};
pub use components::ConfirmLinkOverlay; pub use components::ConfirmLinkOverlay;
pub use events::EventPump;
pub use hooks::*; pub use hooks::*;
pub use routes::{Route, router}; pub use routes::{Route, router};
pub use state::{AppChannel, AppState};

View file

@ -10,8 +10,8 @@ use oneclient_app::ipc::{self, Claim};
use oneclient_app::state::{AppChannel, AppState, LauncherInit}; use oneclient_app::state::{AppChannel, AppState, LauncherInit};
use oneclient_app::{ use oneclient_app::{
Actions, ConfirmLinkOverlay, EventPump, LinkConfirmState, StartMaximizedState, cli, constants, Actions, ConfirmLinkOverlay, EventPump, LinkConfirmState, StartMaximizedState, cli, constants,
events, platform, router, theme, use_provide_actions, use_provide_link_confirm, events, microsoft_java, platform, router, theme, use_provide_actions, use_provide_link_confirm,
use_provide_start_maximized, microsoft_java, use_provide_start_maximized,
}; };
use std::cell::Cell; use std::cell::Cell;
use tokio::runtime::Builder; use tokio::runtime::Builder;
@ -196,39 +196,40 @@ fn main() {
boot_launch: Cell::new(cli.launch), boot_launch: Cell::new(cli.launch),
ipc: Cell::new(ipc), ipc: Cell::new(ipc),
}) })
.with_title(constants::WINDOW_TITLE) .with_title(constants::WINDOW_TITLE)
.with_app_id(constants::WINDOW_APP_ID) .with_app_id(constants::WINDOW_APP_ID)
.with_icon(LaunchConfig::window_icon(include_bytes!( .with_icon(LaunchConfig::window_icon(include_bytes!(
"../icons/128x128.png" "../icons/128x128.png"
))) )))
.with_size(1200., 800.) .with_size(1200., 800.)
.with_min_size(800., 600.) .with_min_size(800., 600.)
.with_transparency(false) .with_transparency(false)
.with_background(Color::TRANSPARENT) .with_background(Color::TRANSPARENT)
// A half-copied data folder is unrecoverable, so the window refuses to // A half-copied data folder is unrecoverable, so the window refuses to
// close while one is being moved; the move screen says as much // close while one is being moved; the move screen says as much
.with_on_close(|_, _| { .with_on_close(|_, _| {
if oneclient_core::relocate::in_progress() { if oneclient_core::relocate::in_progress() {
tracing::warn!("close request ignored, the data folder is still being moved"); tracing::warn!("close request ignored, the data folder is still being moved");
CloseDecision::KeepOpen CloseDecision::KeepOpen
} else { } else {
CloseDecision::Close CloseDecision::Close
} }
}); });
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]
let window_config = window_config let window_config =
.with_decorations(true) window_config
.with_window_attributes(move |attrs, _| { .with_decorations(true)
use freya::winit::platform::macos::WindowAttributesExtMacOS; .with_window_attributes(move |attrs, _| {
attrs use freya::winit::platform::macos::WindowAttributesExtMacOS;
.with_title_hidden(true) attrs
.with_titlebar_transparent(true) .with_title_hidden(true)
.with_titlebar_buttons_hidden(true) .with_titlebar_transparent(true)
.with_fullsize_content_view(true) .with_titlebar_buttons_hidden(true)
.with_has_shadow(true) .with_fullsize_content_view(true)
.with_maximized(start_maximized) .with_has_shadow(true)
}); .with_maximized(start_maximized)
});
#[cfg(not(target_os = "macos"))] #[cfg(not(target_os = "macos"))]
let window_config = window_config let window_config = window_config

View file

@ -2,13 +2,13 @@ use std::collections::BTreeSet;
use std::sync::{Arc, Mutex}; use std::sync::{Arc, Mutex};
use freya::prelude::spawn_forever; use freya::prelude::spawn_forever;
use tokio::sync::mpsc::UnboundedSender;
use oneclient_common::Patch; use oneclient_common::Patch;
use oneclient_core::settings::store::save_settings_and_apply; use oneclient_core::settings::store::save_settings_and_apply;
use oneclient_core::{LauncherState, ProfileUpdate}; use oneclient_core::{LauncherState, ProfileUpdate};
use oneclient_db::models::ClusterId; use oneclient_db::models::ClusterId;
use oneclient_events::{Choice, Prompt, Signal}; use oneclient_events::{Choice, Prompt, Signal};
use oneclient_java::{JavaRuntime, JavaVendor}; use oneclient_java::{JavaRuntime, JavaVendor};
use tokio::sync::mpsc::UnboundedSender;
use crate::hooks::Actions; use crate::hooks::Actions;
use crate::launcher; use crate::launcher;
@ -59,7 +59,10 @@ async fn auto_install(refresh: UnboundedSender<()>) {
return; return;
} }
Err(err) => { Err(err) => {
tracing::warn!(cluster_id, "could not read the cluster's Java version: {err:#}"); tracing::warn!(
cluster_id,
"could not read the cluster's Java version: {err:#}"
);
return; return;
} }
}; };
@ -113,7 +116,10 @@ pub async fn offer_for_pinned_cluster(actions: &Actions, cluster_id: ClusterId)
return; return;
} }
Err(err) => { Err(err) => {
tracing::warn!(cluster_id, "could not read the cluster's Java version: {err:#}"); tracing::warn!(
cluster_id,
"could not read the cluster's Java version: {err:#}"
);
return; return;
} }
}; };

View file

@ -1,12 +1,12 @@
use std::collections::HashMap; use std::collections::HashMap;
use std::time::{Duration, Instant}; use std::time::{Duration, Instant};
use oneclient_events::{
Answer, Choice, Event, GroupedProgressEvent, Level, Notification, ProgressEvent, TaskCategory,
};
use oneclient_content::packages::ProviderId; use oneclient_content::packages::ProviderId;
use oneclient_core::BrowserPackageUpdate; use oneclient_core::BrowserPackageUpdate;
use oneclient_db::models::{ClusterId, OptionalModStatus}; use oneclient_db::models::{ClusterId, OptionalModStatus};
use oneclient_events::{
Answer, Choice, Event, GroupedProgressEvent, Level, Notification, ProgressEvent, TaskCategory,
};
use tokio::sync::oneshot; use tokio::sync::oneshot;
use uuid::Uuid; use uuid::Uuid;
@ -80,9 +80,11 @@ impl OptionalModsGroup {
} }
pub fn offers(&self) -> impl Iterator<Item = (ClusterId, OptionalModRef)> + '_ { pub fn offers(&self) -> impl Iterator<Item = (ClusterId, OptionalModRef)> + '_ {
self.mods self.mods.iter().filter_map(move |item| {
.iter() item.offer
.filter_map(move |item| item.offer.clone().map(|reference| (self.cluster_id, reference))) .clone()
.map(|reference| (self.cluster_id, reference))
})
} }
} }
@ -274,8 +276,11 @@ impl GroupedTasks {
let done_count = self.done_count.get(&cat).copied().unwrap_or(0); let done_count = self.done_count.get(&cat).copied().unwrap_or(0);
let reserved_units = self.reserved_units.get(&cat).copied().unwrap_or(0); let reserved_units = self.reserved_units.get(&cat).copied().unwrap_or(0);
let live: Vec<&ChildRec> = let live: Vec<&ChildRec> = self
self.children.values().filter(|c| c.category == cat).collect(); .children
.values()
.filter(|c| c.category == cat)
.collect();
let live_current: u64 = live.iter().map(|c| c.current.min(c.total)).sum(); let live_current: u64 = live.iter().map(|c| c.current.min(c.total)).sum();
let live_total: u64 = live.iter().map(|c| c.total).sum(); let live_total: u64 = live.iter().map(|c| c.total).sum();
@ -471,7 +476,7 @@ impl NotificationState {
); );
self.push_ephemeral_toast(entry_id, MESSAGE_TOAST_TTL); self.push_ephemeral_toast(entry_id, MESSAGE_TOAST_TTL);
} }
Event::Progress(ProgressEvent::Update { id, .. }) Event::Progress(ProgressEvent::Update { id, .. })
if id == oneclient_auth::MICROSOFT_LOGIN_PROGRESS => {} if id == oneclient_auth::MICROSOFT_LOGIN_PROGRESS => {}
Event::Progress(ProgressEvent::Update { id, .. }) Event::Progress(ProgressEvent::Update { id, .. })
@ -741,8 +746,7 @@ impl NotificationState {
self.update_inbox_entry(inbox, entry_id, title, body, None, false); self.update_inbox_entry(inbox, entry_id, title, body, None, false);
self.ensure_progress_toast(entry_id); self.ensure_progress_toast(entry_id);
} else { } else {
let entry_id = let entry_id = self.push_inbox(inbox, title, body, Level::Info, None, false);
self.push_inbox(inbox, title, body, Level::Info, None, false);
self.push_ephemeral_toast(entry_id, MESSAGE_TOAST_TTL); self.push_ephemeral_toast(entry_id, MESSAGE_TOAST_TTL);
} }
} }
@ -1017,7 +1021,10 @@ mod package_update_tests {
); );
state.resolve_package_update(1, "b"); state.resolve_package_update(1, "b");
assert!(wait.try_recv().is_ok(), "the last answer releases the launch"); assert!(
wait.try_recv().is_ok(),
"the last answer releases the launch"
);
assert!(state.package_updates.is_none()); assert!(state.package_updates.is_none());
} }
@ -1042,7 +1049,10 @@ mod package_update_tests {
state.open_package_updates(vec![group(&[])], Some(done)); state.open_package_updates(vec![group(&[])], Some(done));
assert!(wait.try_recv().is_ok(), "a modal that never opens cannot be answered"); assert!(
wait.try_recv().is_ok(),
"a modal that never opens cannot be answered"
);
assert!(state.package_updates.is_none()); assert!(state.package_updates.is_none());
} }
@ -1055,7 +1065,10 @@ mod package_update_tests {
state.open_package_updates(vec![group(&["a"])], Some(first)); state.open_package_updates(vec![group(&["a"])], Some(first));
state.open_package_updates(vec![group(&["b"])], Some(second)); state.open_package_updates(vec![group(&["b"])], Some(second));
assert!(first_wait.try_recv().is_ok(), "the replaced launch is let go"); assert!(
first_wait.try_recv().is_ok(),
"the replaced launch is let go"
);
assert!(second_wait.try_recv().is_err(), "the new one still waits"); assert!(second_wait.try_recv().is_err(), "the new one still waits");
} }

View file

@ -72,7 +72,10 @@ mod tests {
#[test] #[test]
fn a_folder_survives_the_round_trip() { fn a_folder_survives_the_round_trip() {
let folder = "26.1.2 Fabric"; let folder = "26.1.2 Fabric";
assert_eq!(parse_launch_url(&launch_url(folder)).as_deref(), Some(folder)); assert_eq!(
parse_launch_url(&launch_url(folder)).as_deref(),
Some(folder)
);
} }
#[test] #[test]

View file

@ -44,8 +44,7 @@ pub fn write_string(subkey: &str, name: Option<&str>, value: &str) -> Result<()>
let bytes = unsafe { std::slice::from_raw_parts(wide.as_ptr().cast::<u8>(), wide.len() * 2) }; let bytes = unsafe { std::slice::from_raw_parts(wide.as_ptr().cast::<u8>(), wide.len() * 2) };
let name = name.map(HSTRING::from); let name = name.map(HSTRING::from);
let status = let status = unsafe { RegSetValueExW(key, name_ptr(name.as_ref()), None, REG_SZ, Some(bytes)) };
unsafe { RegSetValueExW(key, name_ptr(name.as_ref()), None, REG_SZ, Some(bytes)) };
close(key); close(key);
check(status, "writing the registry value") check(status, "writing the registry value")

View file

@ -49,7 +49,9 @@ fn retry(actions: &Actions) {
let events = actions.events(); let events = actions.events();
{ {
let mut guard = station.clone().write_channel(crate::state::AppChannel::Launcher); let mut guard = station
.clone()
.write_channel(crate::state::AppChannel::Launcher);
guard.launcher.error = None; guard.launcher.error = None;
guard.launcher.snapshots = snapshots().len(); guard.launcher.snapshots = snapshots().len();
} }

View file

@ -79,7 +79,10 @@ mod tests {
#[test] #[test]
fn a_bare_binary_has_no_bundle() { fn a_bare_binary_has_no_bundle() {
assert_eq!(bundle_root(&PathBuf::from("/usr/local/bin/oneclient_app")), None); assert_eq!(
bundle_root(&PathBuf::from("/usr/local/bin/oneclient_app")),
None
);
assert_eq!( assert_eq!(
bundle_root(&PathBuf::from("/tmp/target/debug/oneclient_app")), bundle_root(&PathBuf::from("/tmp/target/debug/oneclient_app")),
None, None,

View file

@ -38,7 +38,11 @@ pub fn create(request: &ShortcutRequest) -> Result<PathBuf> {
std::fs::create_dir_all(&request.dir) std::fs::create_dir_all(&request.dir)
.with_context(|| format!("couldn't open {}", request.dir.display()))?; .with_context(|| format!("couldn't open {}", request.dir.display()))?;
let path = unique_path(&request.dir, &file_stem(&request.cluster_name), imp::EXTENSION)?; let path = unique_path(
&request.dir,
&file_stem(&request.cluster_name),
imp::EXTENSION,
)?;
imp::write(request, &exe, &path) imp::write(request, &exe, &path)
.with_context(|| format!("couldn't write {}", path.display()))?; .with_context(|| format!("couldn't write {}", path.display()))?;

View file

@ -106,13 +106,15 @@ async fn run_check(auto_install: bool, events: EventBus) -> anyhow::Result<()> {
} }
fn can_self_update() -> bool { fn can_self_update() -> bool {
if cfg!(debug_assertions) { if cfg!(debug_assertions) {
return false; return false;
} }
if std::env::var_os("ONECLIENT_DISABLE_AUTOUPDATE").is_some_and(|val| val.eq_ignore_ascii_case("1")) { if std::env::var_os("ONECLIENT_DISABLE_AUTOUPDATE")
return false; .is_some_and(|val| val.eq_ignore_ascii_case("1"))
} {
return false;
}
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
{ {

View file

@ -2,9 +2,9 @@ use std::cmp::Ordering;
use std::collections::BTreeMap; use std::collections::BTreeMap;
use chrono::{Datelike, NaiveDate}; use chrono::{Datelike, NaiveDate};
use oneclient_core::clusters::Cluster;
use oneclient_common::domain::GameLoader; use oneclient_common::domain::GameLoader;
use oneclient_common::{ParsedMcVersion, VersionKey, format_mc_version, parse_mc_version}; use oneclient_common::{ParsedMcVersion, VersionKey, format_mc_version, parse_mc_version};
use oneclient_core::clusters::Cluster;
use oneclient_common::MEMORY_HEADROOM_GB; use oneclient_common::MEMORY_HEADROOM_GB;
pub use oneclient_common::total_ram_mb; pub use oneclient_common::total_ram_mb;
@ -207,7 +207,7 @@ pub fn format_res((w, h): (u32, u32)) -> String {
format!("{w}×{h}") format!("{w}×{h}")
} }
/// Prevents user from choosing his max amount of ram preset (e.g Someone has 16GB of RAM, /// Prevents user from choosing his max amount of ram preset (e.g Someone has 16GB of RAM,
/// so the max preset is 16GB - 2GB = 14GB) /// so the max preset is 16GB - 2GB = 14GB)
const MEMORY_PRESETS_GB: [u32; 11] = [2, 3, 4, 6, 8, 12, 16, 24, 32, 48, 64]; const MEMORY_PRESETS_GB: [u32; 11] = [2, 3, 4, 6, 8, 12, 16, 24, 32, 48, 64];

View file

@ -82,9 +82,7 @@ impl Component for PlaytimeChart {
"Playtime by day", "Playtime by day",
format!("Every {day} you have played, added up"), format!("Every {day} you have played, added up"),
BarChart::new(self.per_weekday.clone(), weekday_labels()) BarChart::new(self.per_weekday.clone(), weekday_labels())
.readout_labels( .readout_labels(WEEKDAY_FULL.iter().map(|d| format!("All {d}s")).collect())
WEEKDAY_FULL.iter().map(|d| format!("All {d}s")).collect(),
)
.highlight(self.peak_weekday) .highlight(self.peak_weekday)
.unit(ValueUnit::Duration) .unit(ValueUnit::Duration)
.gap(6.) .gap(6.)
@ -241,15 +239,15 @@ impl Component for DailyChart {
.child(DateRangePicker::new(custom, (first_day, last_day)).height(30.)) .child(DateRangePicker::new(custom, (first_day, last_day)).height(30.))
.into_element(); .into_element();
chart_card( chart_card(
"Daily playtime", "Daily playtime",
subtitle, subtitle,
Some(nav), Some(nav),
BarChart::new(values, labels) BarChart::new(values, labels)
.unit(ValueUnit::Duration) .unit(ValueUnit::Duration)
.gap(3.) .gap(3.)
.into_element(), .into_element(),
) )
} }
} }
@ -293,10 +291,8 @@ fn monthly_series(daily: &[DayPlaytime]) -> Vec<(NaiveDate, i64)> {
*totals.entry(month).or_insert(0) += day.secs; *totals.entry(month).or_insert(0) += day.secs;
} }
let (Some(first), Some(last)) = ( let (Some(first), Some(last)) = (totals.keys().min().copied(), totals.keys().max().copied())
totals.keys().min().copied(), else {
totals.keys().max().copied(),
) else {
return Vec::new(); return Vec::new();
}; };

View file

@ -278,4 +278,3 @@ fn persona_icon(persona: Persona) -> IconType {
Persona::Sprinter => IconType::Play, Persona::Sprinter => IconType::Play,
} }
} }

View file

@ -175,13 +175,7 @@ pub(crate) fn activity_badge(active: bool) -> impl IntoElement {
.padding(Gaps::new_symmetric(2., 8.)) .padding(Gaps::new_symmetric(2., 8.))
.corner_radius(CornerRadius::new_all(999.)) .corner_radius(CornerRadius::new_all(999.))
.background(color.with_a(38)) .background(color.with_a(38))
.child( .child(label().text(text).font_size(11.).max_lines(1).color(color))
label()
.text(text)
.font_size(11.)
.max_lines(1)
.color(color),
)
} }
fn badge( fn badge(
@ -444,7 +438,10 @@ mod tests {
); );
let sodium = entry(&map, "sodium"); let sodium = entry(&map, "sodium");
assert!(sodium.is_version("v1"), "the older version is still in there"); assert!(
sodium.is_version("v1"),
"the older version is still in there"
);
assert!(sodium.is_version("v2")); assert!(sodium.is_version("v2"));
assert_eq!( assert_eq!(
sodium.find_version("v2").and_then(|v| v.hash.clone()), sodium.find_version("v2").and_then(|v| v.hash.clone()),
@ -464,7 +461,11 @@ mod tests {
); );
let sodium = entry(&map, "sodium"); let sodium = entry(&map, "sodium");
assert_eq!(sodium.source, InstallSource::Bundled, "the bundle owns the project"); assert_eq!(
sodium.source,
InstallSource::Bundled,
"the bundle owns the project"
);
assert_eq!( assert_eq!(
sodium.find_version("v1").map(|v| v.source), sodium.find_version("v1").map(|v| v.source),
Some(InstallSource::Bundled) Some(InstallSource::Bundled)

View file

@ -2,6 +2,7 @@ use freya::prelude::*;
use oneclient_content::packages::{ContentType, ProviderId}; use oneclient_content::packages::{ContentType, ProviderId};
use crate::components::ScrollArea; use crate::components::ScrollArea;
use crate::hooks::use_cluster;
use crate::hooks::{ use crate::hooks::{
bundles_with_status_items, cluster_content_items, content_type_for_slug, project_detail, bundles_with_status_items, cluster_content_items, content_type_for_slug, project_detail,
use_browser_compat, use_bundles_with_status, use_cluster_content, use_dispatch, use_browser_compat, use_bundles_with_status, use_cluster_content, use_dispatch,
@ -10,7 +11,6 @@ use crate::hooks::{
}; };
use crate::theme::colors; use crate::theme::colors;
use crate::ui::border_all_color; use crate::ui::border_all_color;
use crate::hooks::use_cluster;
use super::{ use super::{
Installed, InstalledVersion, PackageBanner, Thumbnail, activity_badge, installed_badge, Installed, InstalledVersion, PackageBanner, Thumbnail, activity_badge, installed_badge,

View file

@ -5,7 +5,9 @@ use oneclient_content::packages::markdown::normalize_markdown;
use oneclient_content::packages::types::{PackageBody, ProjectDetail, ReleaseType, VersionSummary}; use oneclient_content::packages::types::{PackageBody, ProjectDetail, ReleaseType, VersionSummary};
use crate::Actions; use crate::Actions;
use crate::components::{Button, Icon, IconType, Markdown, MarkdownStyle, Segment, SegmentedControl}; use crate::components::{
Button, Icon, IconType, Markdown, MarkdownStyle, Segment, SegmentedControl,
};
use crate::hooks::VERSIONS_PAGE_SIZE; use crate::hooks::VERSIONS_PAGE_SIZE;
use crate::theme::colors; use crate::theme::colors;
use crate::ui::border_all_color; use crate::ui::border_all_color;
@ -283,14 +285,7 @@ fn version_row(
.map(|installed| activity_badge(installed.enabled).into_element()), .map(|installed| activity_badge(installed.enabled).into_element()),
) )
.child(version_button( .child(version_button(
installed, installed, v.name, provider, project_id, version_id, cluster_id, dispatch, installing,
v.name,
provider,
project_id,
version_id,
cluster_id,
dispatch,
installing,
)) ))
} }
@ -323,7 +318,11 @@ fn version_button(
}; };
let Some(hash) = installed.hash else { let Some(hash) = installed.hash else {
return Button::new().secondary().small().enabled(false).text("Install"); return Button::new()
.secondary()
.small()
.enabled(false)
.text("Install");
}; };
Button::new() Button::new()

View file

@ -1,10 +1,8 @@
use freya::prelude::*; use freya::prelude::*;
use oneclient_common::Patch; use oneclient_common::Patch;
use oneclient_java::JavaRuntime;
use oneclient_common::domain::GameLoader; use oneclient_common::domain::GameLoader;
use oneclient_core::settings::{ use oneclient_core::settings::{GameSettingsProfile, PackageUpdateMode, ProfileUpdate, Resolution};
GameSettingsProfile, PackageUpdateMode, ProfileUpdate, Resolution, use oneclient_java::JavaRuntime;
};
use crate::components::{ use crate::components::{
Button, Dropdown, Icon, IconType, ScrollArea, TextInput, memory_field, toggle, Button, Dropdown, Icon, IconType, ScrollArea, TextInput, memory_field, toggle,
@ -309,7 +307,11 @@ impl Component for VerifyFilesRow {
.secondary() .secondary()
.enabled(!running) .enabled(!running)
.maybe(!running, |el| el.on_press(on_press)) .maybe(!running, |el| el.on_press(on_press))
.text(if running { "Verifying..." } else { "Verify Files" }); .text(if running {
"Verifying..."
} else {
"Verify Files"
});
settings_row( settings_row(
IconType::ClipboardCheck, IconType::ClipboardCheck,

View file

@ -336,9 +336,7 @@ pub fn viewer_header(
) )
.into_element(), .into_element(),
) )
.child( .child(UploadToMclogs::new(has_log, confirm));
UploadToMclogs::new(has_log, confirm)
);
rect() rect()
.vertical() .vertical()

View file

@ -7,10 +7,10 @@ use crate::hooks::{
}; };
use crate::layout::cluster_content; use crate::layout::cluster_content;
use super::cluster_not_found;
use super::package_manager::{ use super::package_manager::{
PackageManager, bundle_categories, bundle_packages, use_content_meta, PackageManager, bundle_categories, bundle_packages, use_content_meta,
}; };
use super::cluster_not_found;
use crate::hooks::use_cluster; use crate::hooks::use_cluster;
#[derive(PartialEq)] #[derive(PartialEq)]

View file

@ -4,7 +4,7 @@ use crate::components::ScrollArea;
use crate::hooks::{try_cluster_analytics, use_cluster_analytics}; use crate::hooks::{try_cluster_analytics, use_cluster_analytics};
use crate::layout::cluster_content; use crate::layout::cluster_content;
use crate::theme::colors; use crate::theme::colors;
use crate::ui::{centered_note}; use crate::ui::centered_note;
use crate::view::app::{analytics_body, analytics_placeholder}; use crate::view::app::{analytics_body, analytics_placeholder};
use super::cluster_not_found; use super::cluster_not_found;

View file

@ -74,10 +74,7 @@ pub fn use_content_meta(
} }
} }
let local = use_package_meta_batch( let local = use_package_meta_batch(ProviderId::Local, local_project_ids(content, content_type));
ProviderId::Local,
local_project_ids(content, content_type),
);
for (hash, meta) in package_meta_batch(&local) { for (hash, meta) in package_meta_batch(&local) {
out.insert((ProviderId::Local, hash), meta); out.insert((ProviderId::Local, hash), meta);
} }
@ -257,9 +254,7 @@ fn make_row(
hash: installed_info.map(|i| i.hash.clone()), hash: installed_info.map(|i| i.hash.clone()),
update_available, update_available,
hidden, hidden,
seen_status: installed_info seen_status: installed_info.map(|i| i.seen_status).unwrap_or_default(),
.map(|i| i.seen_status)
.unwrap_or_default(),
} }
} }
@ -405,7 +400,10 @@ impl Component for PackageManager {
.await .await
{ {
Ok(cleared) if cleared > 0 => { Ok(cleared) if cleared > 0 => {
tracing::debug!(cleared, "retired package badges after the list was viewed"); tracing::debug!(
cleared,
"retired package badges after the list was viewed"
);
} }
Ok(_) => {} Ok(_) => {}
Err(err) => tracing::warn!(%err, "failed to retire package badges"), Err(err) => tracing::warn!(%err, "failed to retire package badges"),

View file

@ -170,19 +170,11 @@ pub(super) fn toolbar_bar(
} else { } else {
Size::flex(1.0) Size::flex(1.0)
}) })
.height(if stacked { .height(if stacked { Size::fill() } else { Size::auto() })
Size::fill()
} else {
Size::auto()
})
.child( .child(
TabBar::new() TabBar::new()
.width(Size::auto()) .width(Size::auto())
.height(if stacked { .height(if stacked { Size::fill() } else { Size::auto() })
Size::fill()
} else {
Size::auto()
})
.spacing(20.) .spacing(20.)
.font_size(12.) .font_size(12.)
.tabs(tab_items), .tabs(tab_items),

View file

@ -70,7 +70,11 @@ impl Component for ProcessLogs {
} }
} }
fn filter_lines(lines: &Arc<Vec<Arc<str>>>, search: &str, level: LevelFilter) -> Arc<Vec<Arc<str>>> { fn filter_lines(
lines: &Arc<Vec<Arc<str>>>,
search: &str,
level: LevelFilter,
) -> Arc<Vec<Arc<str>>> {
let needle = (!search.trim().is_empty()).then(|| search.to_lowercase()); let needle = (!search.trim().is_empty()).then(|| search.to_lowercase());
let wanted = level.to_level(); let wanted = level.to_level();

View file

@ -9,7 +9,10 @@ use crate::components::{
Button, Icon, IconType, LocalImage, OverlayPopup, ScreenshotViewer, ScrollArea, Segment, Button, Icon, IconType, LocalImage, OverlayPopup, ScreenshotViewer, ScrollArea, Segment,
SegmentedControl, open_folder_button, screenshot_context_menu, SegmentedControl, open_folder_button, screenshot_context_menu,
}; };
use crate::hooks::{ScreenshotAction, query_is_loading, try_cluster_screenshots, use_cluster_screenshots, use_dispatch, use_screenshot_action, use_screenshot_folder_watch, use_view_state}; use crate::hooks::{
ScreenshotAction, query_is_loading, try_cluster_screenshots, use_cluster_screenshots,
use_dispatch, use_screenshot_action, use_screenshot_folder_watch, use_view_state,
};
use crate::layout::cluster_content; use crate::layout::cluster_content;
use crate::theme::colors; use crate::theme::colors;
use crate::ui::{border_all_color, flow_grid, fmt_date, grid_columns_for_width}; use crate::ui::{border_all_color, flow_grid, fmt_date, grid_columns_for_width};
@ -65,8 +68,7 @@ impl Component for ClusterScreenshots {
); );
let content: Element = if shots.is_empty() { let content: Element = if shots.is_empty() {
empty_state(query_is_loading(&query)) empty_state(query_is_loading(&query)).into_element()
.into_element()
} else { } else {
let mut items: Vec<Element> = Vec::new(); let mut items: Vec<Element> = Vec::new();
for (idx, info) in shots.iter().enumerate() { for (idx, info) in shots.iter().enumerate() {

View file

@ -7,10 +7,10 @@ use crate::hooks::{
}; };
use crate::layout::cluster_content; use crate::layout::cluster_content;
use super::cluster_not_found;
use super::package_manager::{ use super::package_manager::{
PackageManager, bundle_categories, bundle_packages, use_content_meta, PackageManager, bundle_categories, bundle_packages, use_content_meta,
}; };
use super::cluster_not_found;
use crate::hooks::use_cluster; use crate::hooks::use_cluster;
#[derive(PartialEq)] #[derive(PartialEq)]

View file

@ -7,10 +7,10 @@ use crate::hooks::{
}; };
use crate::layout::cluster_content; use crate::layout::cluster_content;
use super::cluster_not_found;
use super::package_manager::{ use super::package_manager::{
PackageManager, bundle_categories, bundle_packages, use_content_meta, PackageManager, bundle_categories, bundle_packages, use_content_meta,
}; };
use super::cluster_not_found;
use crate::hooks::use_cluster; use crate::hooks::use_cluster;
#[derive(PartialEq)] #[derive(PartialEq)]

View file

@ -1,13 +1,16 @@
use freya::prelude::*; use freya::prelude::*;
use freya::router::RouterContext; use freya::router::RouterContext;
use oneclient_common::VersionKey; use oneclient_common::VersionKey;
use oneclient_core::clusters::Cluster;
use oneclient_common::domain::GameLoader; use oneclient_common::domain::GameLoader;
use oneclient_core::clusters::Cluster;
use crate::components::{ use crate::components::{
Button, ClusterLandscapeArt, Dropdown, Icon, IconType, ScrollArea, VersionCard, Button, ClusterLandscapeArt, Dropdown, Icon, IconType, ScrollArea, VersionCard,
}; };
use crate::hooks::{settled_or_loading, use_active_cluster_id, use_clusters, use_dispatch, use_game_snapshot, use_launcher, use_version_metadata}; use crate::hooks::{
settled_or_loading, use_active_cluster_id, use_clusters, use_dispatch, use_game_snapshot,
use_launcher, use_version_metadata,
};
use crate::routes::Route; use crate::routes::Route;
use crate::theme::colors; use crate::theme::colors;
use crate::ui::border_all_color; use crate::ui::border_all_color;
@ -294,7 +297,12 @@ impl Component for DetailSidebar {
.color(colors::fg_secondary()), .color(colors::fg_secondary()),
), ),
) )
.children(version_rows(self.line, &keys, version_value, self.selected_version)) .children(version_rows(
self.line,
&keys,
version_value,
self.selected_version,
))
.children(loader_rows(&loaders, loader_value, self.selected_loader)), .children(loader_rows(&loaders, loader_value, self.selected_loader)),
) )
.child( .child(

View file

@ -25,7 +25,7 @@ pub struct Debug;
impl Component for Debug { impl Component for Debug {
fn render(&self) -> impl IntoElement { fn render(&self) -> impl IntoElement {
let dispatch = use_dispatch(); let dispatch = use_dispatch();
let log_debug_info = use_state(|| false); let log_debug_info = use_state(|| false);
let show_dev_stuff = use_state(|| false); let show_dev_stuff = use_state(|| false);
let seen_onboarding = use_state(|| true); let seen_onboarding = use_state(|| true);
@ -103,12 +103,15 @@ impl Component for Debug {
.child(divider()) .child(divider())
.child(section( .child(section(
"Other", "Other",
vec![action_row(&dispatch, vec![ vec![action_row(
("Open Dev Tools", IconType::CodeSnippet02), &dispatch,
("Open Onboarding", IconType::Rocket02), vec![
("Open Launcher Data", IconType::Folder), ("Open Dev Tools", IconType::CodeSnippet02),
("Log Running Processes", IconType::Terminal), ("Open Onboarding", IconType::Rocket02),
])], ("Open Launcher Data", IconType::Folder),
("Log Running Processes", IconType::Terminal),
],
)],
)), )),
) )
} }
@ -432,52 +435,50 @@ const CLUSTER_UPDATE_PRESETS: [(&str, IconType, ClusterUpdatePreset); 7] = [
&[], &[],
), ),
preset_summary(2, "Skyblock", &[], &[], &["OptiFine", "Skytils"], &[]), preset_summary(2, "Skyblock", &[], &[], &["OptiFine", "Skytils"], &[]),
preset_summary(3, "Vanilla+", &[], &["Sodium", "Iris", "FerriteCore"], &[], &[]), preset_summary(
3,
"Vanilla+",
&[],
&["Sodium", "Iris", "FerriteCore"],
&[],
&[],
),
] ]
}), }),
("2 clusters · removals only", IconType::Trash01, || { ("2 clusters · removals only", IconType::Trash01, || {
vec![ vec![
preset_summary(1, "PolyBlock", &[], &[], &["OptiFine"], &[]), preset_summary(1, "PolyBlock", &[], &[], &["OptiFine"], &[]),
preset_summary(2, "Skyblock", &[], &[], &["Skytils", "NotEnoughUpdates"], &[]), preset_summary(
2,
"Skyblock",
&[],
&[],
&["Skytils", "NotEnoughUpdates"],
&[],
),
] ]
}), }),
("6 clusters · long names", IconType::Database01, || { ("6 clusters · long names", IconType::Database01, || {
(1..=6) (1..=6)
.map(|i| { .map(|i| ClusterUpdateSummary {
ClusterUpdateSummary { cluster_id: i,
cluster_id: i, cluster_name: format!(
cluster_name: format!( "Cluster {i} with a deliberately overlong name that has to truncate"
"Cluster {i} with a deliberately overlong name that has to truncate" ),
), updated: cluster_update_items(&[
updated: cluster_update_items(&[ format!("Sodium 0.{i} → 0.{}", i + 1),
format!("Sodium 0.{i} → 0.{}", i + 1), format!("Iris 1.{i} → 1.{}", i + 1),
format!("Iris 1.{i} → 1.{}", i + 1), ]),
]), added: cluster_update_items(&[format!("Lithium {i}")]),
added: cluster_update_items(&[format!("Lithium {i}")]), removed: Vec::new(),
removed: Vec::new(), optional: Vec::new(),
optional: Vec::new(),
}
}) })
.collect() .collect()
}), }),
("2 clusters · offers only", IconType::Plus, || { ("2 clusters · offers only", IconType::Plus, || {
vec![ vec![
preset_summary( preset_summary(1, "PolyBlock", &[], &[], &[], &["Lithium", "FerriteCore"]),
1, preset_summary(2, "Skyblock", &[], &["Skytils"], &[], &["Skytils"]),
"PolyBlock",
&[],
&[],
&[],
&["Lithium", "FerriteCore"],
),
preset_summary(
2,
"Skyblock",
&[],
&["Skytils"],
&[],
&["Skytils"],
),
] ]
}), }),
]; ];
@ -519,7 +520,7 @@ fn preset_summary(
updated: cluster_update_items(updated), updated: cluster_update_items(updated),
added: cluster_update_items(added), added: cluster_update_items(added),
removed: cluster_update_items(removed), removed: cluster_update_items(removed),
optional: cluster_update_items(optional) optional: cluster_update_items(optional),
} }
} }
@ -1036,9 +1037,11 @@ fn run_damage(dispatch: &crate::Actions, kind: DamageKind, cluster_id: i64) {
let dispatch = dispatch.clone(); let dispatch = dispatch.clone();
spawn(async move { spawn(async move {
let result = match kind { let result = match kind {
DamageKind::Assets(count, damage) => oneclient_core::simulate::damage_assets(count, damage) DamageKind::Assets(count, damage) => {
.await oneclient_core::simulate::damage_assets(count, damage)
.map(|report| (report, damage.verb())), .await
.map(|report| (report, damage.verb()))
}
DamageKind::Libraries(count, damage) => { DamageKind::Libraries(count, damage) => {
oneclient_core::simulate::damage_libraries(count, damage) oneclient_core::simulate::damage_libraries(count, damage)
.await .await
@ -1300,9 +1303,9 @@ fn action_row(dispatch: &Actions, buttons: Vec<(&'static str, IconType)>) -> Ele
.text(text); .text(text);
if text == "Open Onboarding" { if text == "Open Onboarding" {
let dispatch = dispatch.clone(); let dispatch = dispatch.clone();
button = button.on_press(move |_| { button = button.on_press(move |_| {
dispatch.reset_onboarding(); dispatch.reset_onboarding();
let _ = RouterContext::get().replace(Route::OnboardingWelcome {}); let _ = RouterContext::get().replace(Route::OnboardingWelcome {});
}); });
} }

View file

@ -555,10 +555,10 @@ impl Component for AccountRow {
.enabled(false) .enabled(false)
.tooltip("Edit") .tooltip("Edit")
.child( .child(
Icon::new(IconType::Pencil01) Icon::new(IconType::Pencil01)
.size(18.) .size(18.)
.color(colors::fg_secondary()), .color(colors::fg_secondary()),
), ),
) )
.child( .child(
Button::new() Button::new()

View file

@ -137,7 +137,11 @@ impl Component for ReleaseCard {
.font_weight(FontWeight::SEMI_BOLD) .font_weight(FontWeight::SEMI_BOLD)
.color(colors::fg_primary()), .color(colors::fg_primary()),
) )
.child(Icon::new(IconType::ChevronDown).size(18.).rotate(chevron_deg)), .child(
Icon::new(IconType::ChevronDown)
.size(18.)
.rotate(chevron_deg),
),
) )
.maybe_child(is_open.then(|| { .maybe_child(is_open.then(|| {
rect() rect()

View file

@ -6,7 +6,6 @@ use oneclient_java::{JavaRuntime, JavaVendor, is_launcher_managed};
use super::settings_page; use super::settings_page;
use crate::components::{Button, Icon, IconType, JavaInstallManager, OverlayPopup, ScrollArea}; use crate::components::{Button, Icon, IconType, JavaInstallManager, OverlayPopup, ScrollArea};
use crate::hooks::{Actions, java_runtimes, use_dispatch, use_java_runtimes}; use crate::hooks::{Actions, java_runtimes, use_dispatch, use_java_runtimes};
use crate::invalidate_java_queries;
use crate::theme::colors; use crate::theme::colors;
use crate::ui::border_all_color; use crate::ui::border_all_color;
use crate::view::app::settings::section_header; use crate::view::app::settings::section_header;
@ -23,19 +22,6 @@ impl Component for SettingsJava {
let mut show_manager = use_state(|| false); let mut show_manager = use_state(|| false);
let pending_remove = use_state(|| None::<PendingRemove>); let pending_remove = use_state(|| None::<PendingRemove>);
fn invalidate_runtimes(dispatch: Actions) {
spawn(async move {
invalidate_java_queries().await;
dispatch
.notify("Java runtimes refreshed")
.body("The installed runtime list is up to date")
.info()
.send();
});
}
let refresh_dispatch = dispatch.clone();
let mut shell = settings_page() let mut shell = settings_page()
.child(section_header("ADD RUNTIME")) .child(section_header("ADD RUNTIME"))
.child(AddRow { show_manager }.into_element()) .child(AddRow { show_manager }.into_element())
@ -56,7 +42,11 @@ impl Component for SettingsJava {
let pending = pending_remove.read().clone(); let pending = pending_remove.read().clone();
if let Some(target) = pending { if let Some(target) = pending {
shell = shell.child(confirm_remove_modal(removing_dispatch, pending_remove, target)) shell = shell.child(confirm_remove_modal(
removing_dispatch,
pending_remove,
target,
))
} }
shell.into_element() shell.into_element()
@ -141,7 +131,7 @@ fn runtimes_table(
RuntimeRow { RuntimeRow {
runtime, runtime,
last: idx + 1 == count, last: idx + 1 == count,
pending_remove pending_remove,
} }
.into_element(), .into_element(),
); );
@ -191,7 +181,7 @@ fn table_header() -> impl IntoElement {
struct RuntimeRow { struct RuntimeRow {
runtime: JavaRuntime, runtime: JavaRuntime,
last: bool, last: bool,
pending_remove: State<Option<PendingRemove>> pending_remove: State<Option<PendingRemove>>,
} }
impl Component for RuntimeRow { impl Component for RuntimeRow {
@ -303,7 +293,7 @@ fn confirm_remove_modal(
Button::new().primary().text("Remove from list") Button::new().primary().text("Remove from list")
}; };
OverlayPopup::new() OverlayPopup::new()
.on_close(move |()| pending.set(None)) .on_close(move |()| pending.set(None))
.child( .child(
rect() rect()
@ -359,7 +349,7 @@ fn confirm_remove_modal(
pending.set(None); pending.set(None);
})), })),
), ),
) ),
) )
.into_element() .into_element()
} }

View file

@ -125,7 +125,11 @@ impl Component for DataFolder {
.small() .small()
.disabled(checking_now) .disabled(checking_now)
.on_press(move |_| browse(pending, error, checking)) .on_press(move |_| browse(pending, error, checking))
.text(if checking_now { "Checking…" } else { "Change…" }); .text(if checking_now {
"Checking…"
} else {
"Change…"
});
let mut buttons = Vec::new(); let mut buttons = Vec::new();

View file

@ -1,15 +1,15 @@
use freya::prelude::*; use freya::prelude::*;
use oneclient_common::Patch; use oneclient_common::Patch;
use oneclient_core::settings::{PackageUpdateMode, ProfileUpdate, Resolution};
#[cfg(any(target_os = "linux", windows))] #[cfg(any(target_os = "linux", windows))]
use oneclient_core::settings::SettingsOsExtra; use oneclient_core::settings::SettingsOsExtra;
use oneclient_core::settings::{PackageUpdateMode, ProfileUpdate, Resolution};
use super::settings_page; use super::settings_page;
#[cfg(any(target_os = "linux", windows))]
use crate::components::toggle_controlled;
use crate::components::{ use crate::components::{
Dropdown, Icon, IconType, TextInput, memory_field, toggle, validate_number, Dropdown, Icon, IconType, TextInput, memory_field, toggle, validate_number,
}; };
#[cfg(any(target_os = "linux", windows))]
use crate::components::toggle_controlled;
use crate::hooks::{use_dispatch, use_settings_snapshot}; use crate::hooks::{use_dispatch, use_settings_snapshot};
use crate::theme::colors; use crate::theme::colors;
use crate::view::app::settings::{section_header, settings_row}; use crate::view::app::settings::{section_header, settings_row};
@ -219,10 +219,7 @@ fn build_update(
} }
/// Dispatched separately from [`build_update`] which debounces keystrokes a dropdown has no intermediate states /// Dispatched separately from [`build_update`] which debounces keystrokes a dropdown has no intermediate states
fn update_mode_field( fn update_mode_field(selected: PackageUpdateMode, dispatch: crate::Actions) -> impl IntoElement {
selected: PackageUpdateMode,
dispatch: crate::Actions,
) -> impl IntoElement {
let options: Vec<String> = PackageUpdateMode::ALL let options: Vec<String> = PackageUpdateMode::ALL
.iter() .iter()
.map(|mode| mode.label().to_string()) .map(|mode| mode.label().to_string())
@ -276,4 +273,3 @@ fn resolution_field(width: State<String>, height: State<String>) -> impl IntoEle
) )
.into_element() .into_element()
} }

View file

@ -14,9 +14,9 @@ use freya::prelude::*;
pub use accounts::SettingsAccounts; pub use accounts::SettingsAccounts;
pub use apis::SettingsApis; pub use apis::SettingsApis;
pub use appearance::SettingsAppearance;
pub use changelog::SettingsChangelog; pub use changelog::SettingsChangelog;
pub use developer::SettingsDeveloper; pub use developer::SettingsDeveloper;
pub use appearance::SettingsAppearance;
pub use java::SettingsJava; pub use java::SettingsJava;
pub use language::SettingsLanguage; pub use language::SettingsLanguage;
pub use launcher::SettingsLauncher; pub use launcher::SettingsLauncher;

View file

@ -322,10 +322,7 @@ fn unused_cache_description(entry: &ReclaimableEntry) -> String {
) )
} }
fn legacy_content_description( fn legacy_content_description(content: &ReclaimableEntry, natives: &ReclaimableEntry) -> String {
content: &ReclaimableEntry,
natives: &ReclaimableEntry,
) -> String {
if content.is_empty() && natives.is_empty() { if content.is_empty() && natives.is_empty() {
return "Nothing here - no old cluster content and no unused natives.".to_string(); return "Nothing here - no old cluster content and no unused natives.".to_string();
} }

View file

@ -3,7 +3,7 @@ use freya::prelude::*;
use crate::components::ScrollArea; use crate::components::ScrollArea;
use crate::hooks::{try_global_analytics, use_global_analytics}; use crate::hooks::{try_global_analytics, use_global_analytics};
use crate::theme::colors; use crate::theme::colors;
use crate::ui::{centered_note}; use crate::ui::centered_note;
use crate::view::app::{analytics_body, analytics_placeholder}; use crate::view::app::{analytics_body, analytics_placeholder};
#[derive(PartialEq)] #[derive(PartialEq)]

View file

@ -85,7 +85,11 @@ fn raise_to_debug() {
} }
fn restore_directives() { fn restore_directives() {
let Some(previous) = PREVIOUS_DIRECTIVES.lock().expect("console directives").take() else { let Some(previous) = PREVIOUS_DIRECTIVES
.lock()
.expect("console directives")
.take()
else {
return; return;
}; };

View file

@ -1,12 +1,15 @@
use std::collections::{HashMap, HashSet}; use std::collections::{HashMap, HashSet};
use freya::prelude::*; use freya::prelude::*;
use oneclient_core::clusters::Cluster;
use oneclient_content::packages::ProviderId; use oneclient_content::packages::ProviderId;
use oneclient_core::clusters::Cluster;
use oneclient_core::{BundleArchive, BundleFile, BundleFileKind}; use oneclient_core::{BundleArchive, BundleFile, BundleFileKind};
use crate::components::ScrollArea; use crate::components::ScrollArea;
use crate::hooks::{ClusterBundles, onboarding_bundles_items, package_meta_batch, query_error, query_is_loading, use_onboarding_bundles, use_onboarding_selection, use_package_meta_batch}; use crate::hooks::{
ClusterBundles, onboarding_bundles_items, package_meta_batch, query_error, query_is_loading,
use_onboarding_bundles, use_onboarding_selection, use_package_meta_batch,
};
type MetaMap = HashMap<String, oneclient_content::packages::CachedPackageMeta>; type MetaMap = HashMap<String, oneclient_content::packages::CachedPackageMeta>;
use crate::routes::Route; use crate::routes::Route;
@ -341,55 +344,50 @@ fn opt_in_card(
let yes_ids = all_ids.clone(); let yes_ids = all_ids.clone();
let no_bundle = bundle.clone(); let no_bundle = bundle.clone();
let mut card = rect() let mut card = rect().vertical().width(Size::fill()).spacing(10.).child(
.vertical() label()
.width(Size::fill()) .text(format!("Do you want to install {name} mods?"))
.spacing(10.) .font_size(15.)
.child( .font_weight(FontWeight::SEMI_BOLD)
label() .color(colors::fg_primary()),
.text(format!("Do you want to install {name} mods?")) );
.font_size(15.)
.font_weight(FontWeight::SEMI_BOLD)
.color(colors::fg_primary()),
);
card = card card = card.child(
.child( rect()
rect() .horizontal()
.horizontal() .width(Size::fill())
.width(Size::fill()) .spacing(8.)
.spacing(8.) .content(Content::Flex)
.content(Content::Flex) .child(
.child( rect()
rect() .width(Size::flex(1.0))
.width(Size::flex(1.0)) .height(Size::px(ANSWER_H))
.height(Size::px(ANSWER_H)) .child(choice_row_sized(
.child(choice_row_sized( "Yes",
"Yes", &format!("{} mods", yes_bundle.mod_count()),
&format!("{} mods", yes_bundle.mod_count()), wanted,
wanted, Size::fill(),
Size::fill(), move |()| {
move |()| { if wanted {
if wanted { return;
return; }
} set_bundle_versions(selected, user_touched, &yes_bundle, &yes_ids)
set_bundle_versions(selected, user_touched, &yes_bundle, &yes_ids) },
}, )),
)), )
) .child(
.child( rect()
rect() .width(Size::flex(1.0))
.width(Size::flex(1.0)) .height(Size::px(ANSWER_H))
.height(Size::px(ANSWER_H)) .child(choice_row_sized(
.child(choice_row_sized( "No",
"No", "",
"", !wanted,
!wanted, Size::fill(),
Size::fill(), move |()| set_bundle_versions(selected, user_touched, &no_bundle, &[]),
move |()| set_bundle_versions(selected, user_touched, &no_bundle, &[]), )),
)), ),
), );
);
if wanted && name.eq_ignore_ascii_case(FPS_WARNING_BUNDLE) { if wanted && name.eq_ignore_ascii_case(FPS_WARNING_BUNDLE) {
card = card.child(fps_warning_banner()); card = card.child(fps_warning_banner());

View file

@ -4,14 +4,14 @@ use std::time::{Duration, Instant};
use freya::animation::*; use freya::animation::*;
use freya::prelude::*; use freya::prelude::*;
use freya::router::RouterContext; use freya::router::RouterContext;
use oneclient_events::{
EventBus, GroupedProgressEvent, GroupedProgressSession, ProgressEvent, TaskCategory,
};
use oneclient_core::{ use oneclient_core::{
BundleArchive, BundleFile, Cluster, ImportTarget, MigrationSource, SentryExclusion, BundleArchive, BundleFile, Cluster, ImportTarget, MigrationSource, SentryExclusion,
VersionMetadata, VersionMetadata,
}; };
use oneclient_db::models::OverrideType; use oneclient_db::models::OverrideType;
use oneclient_events::{
EventBus, GroupedProgressEvent, GroupedProgressSession, ProgressEvent, TaskCategory,
};
use tokio::sync::mpsc; use tokio::sync::mpsc;
use uuid::Uuid; use uuid::Uuid;
@ -502,12 +502,7 @@ fn build_plans(
let kept = kept_package_ids(cb.cluster.id, &cb.archives, selected); let kept = kept_package_ids(cb.cluster.id, &cb.archives, selected);
let mut overrides = Vec::new(); let mut overrides = Vec::new();
for archive in &cb.archives { for archive in &cb.archives {
overrides.extend(archive_overrides( overrides.extend(archive_overrides(cb.cluster.id, archive, selected, &kept));
cb.cluster.id,
archive,
selected,
&kept,
));
} }
ClusterPlan { ClusterPlan {
cluster_id: cb.cluster.id, cluster_id: cb.cluster.id,
@ -693,7 +688,9 @@ fn run_install_batch(plans: Vec<ClusterPlan>, predownload: bool, handles: Instal
apply_grouped(&mut local, event); apply_grouped(&mut local, event);
} }
while let Ok(notification) = notif_rx.try_recv() { while let Ok(notification) = notif_rx.try_recv() {
if let oneclient_events::Event::Progress(ProgressEvent::Grouped(event)) = notification { if let oneclient_events::Event::Progress(ProgressEvent::Grouped(event)) =
notification
{
apply_grouped(&mut local, event); apply_grouped(&mut local, event);
} }
} }
@ -799,15 +796,18 @@ async fn install_one(
"Saving your package choices...".to_string(), "Saving your package choices...".to_string(),
)); ));
} }
oneclient_core::set_bundle_package_overrides(plan.cluster_id, &plan.overrides, &state.services.content()) oneclient_core::set_bundle_package_overrides(
.await?; plan.cluster_id,
&plan.overrides,
&state.services.content(),
)
.await?;
if !predownload { if !predownload {
return Ok(()); return Ok(());
} }
let session = let session = GroupedProgressSession::start(events, format!("Downloading {}", plan.mc_version));
GroupedProgressSession::start(events, format!("Downloading {}", plan.mc_version));
let _ = ui_tx.send(InstallUiEvent::Activity(format!( let _ = ui_tx.send(InstallUiEvent::Activity(format!(
"Installing mods & content for {}...", "Installing mods & content for {}...",

View file

@ -251,10 +251,7 @@ fn build_stats(view: StatsView) -> Option<Element> {
} else { } else {
// Both readings come off the same smoothed rate so they always agree // Both readings come off the same smoothed rate so they always agree
match transfer.and_then(|t| t.eta_secs) { match transfer.and_then(|t| t.eta_secs) {
Some(remaining) => parts.push(format!( Some(remaining) => parts.push(format!("~{} left", format_durations(remaining as i64))),
"~{} left",
format_durations(remaining as i64)
)),
None => parts.push("~- left".to_string()), None => parts.push("~- left".to_string()),
} }

View file

@ -24,8 +24,7 @@ const ONBOARDING_TIPS_BACKUP: &[&str] = &[
"You can sleep through a thunderstorm, even during the day", "You can sleep through a thunderstorm, even during the day",
]; ];
const FUNFACTS_URL: &str = const FUNFACTS_URL: &str = "https://data-v2.polyfrost.org/oneclient/funfacts.txt";
"https://data-v2.polyfrost.org/oneclient/funfacts.txt";
fn normalize_line_breaks(value: &str) -> String { fn normalize_line_breaks(value: &str) -> String {
value.replace("/n", "\n").replace("\\n", "\n") value.replace("/n", "\n").replace("\\n", "\n")

View file

@ -1,6 +1,6 @@
use oneclient_core::clusters::{Cluster, ClusterStage};
use oneclient_common::domain::{ContentType, GameLoader}; use oneclient_common::domain::{ContentType, GameLoader};
use oneclient_content::packages::types::ExternalFile; use oneclient_content::packages::types::ExternalFile;
use oneclient_core::clusters::{Cluster, ClusterStage};
use oneclient_core::{Bundle, BundleArchive, BundleFile, BundleFileKind, BundleManifest}; use oneclient_core::{Bundle, BundleArchive, BundleFile, BundleFileKind, BundleManifest};
use std::path::PathBuf; use std::path::PathBuf;

View file

@ -46,7 +46,11 @@ fn copying(plan: &RelocationPlan, copied: u64, total: u64) -> Element {
let total = if total > 0 { total } else { plan.bytes }.max(1); let total = if total > 0 { total } else { plan.bytes }.max(1);
screen() screen()
.child(heading(IconType::FolderDownload, "Moving your data folder", colors::fg_primary())) .child(heading(
IconType::FolderDownload,
"Moving your data folder",
colors::fg_primary(),
))
.child(path_block("From", &plan.from)) .child(path_block("From", &plan.from))
.child(path_block("To", &plan.to)) .child(path_block("To", &plan.to))
.child(progress_track( .child(progress_track(
@ -90,7 +94,11 @@ fn moved(outcome: &RelocationOutcome, actions: Actions) -> Element {
} }
screen() screen()
.child(heading(IconType::FolderCheck, "Game data moved", colors::brand())) .child(heading(
IconType::FolderCheck,
"Game data moved",
colors::brand(),
))
.child(note(body, colors::brand())) .child(note(body, colors::brand()))
.child( .child(
label() label()
@ -103,26 +111,28 @@ fn moved(outcome: &RelocationOutcome, actions: Actions) -> Element {
.color(colors::fg_secondary()), .color(colors::fg_secondary()),
) )
.child( .child(
buttons() buttons().child(back_button(actions)).child(
.child(back_button(actions)) Button::new()
.child( .primary()
Button::new() .on_press(|_| {
.primary() let platform = Platform::get();
.on_press(|_| { Platform::get().with_window(Platform::window_id(), move |window| {
let platform = Platform::get(); platform.close_window(window.id());
Platform::get().with_window(Platform::window_id(), move |window| { });
platform.close_window(window.id()); })
}); .text("Quit OneClient"),
}) ),
.text("Quit OneClient"),
),
) )
.into_element() .into_element()
} }
fn failed(message: String, actions: Actions) -> Element { fn failed(message: String, actions: Actions) -> Element {
screen() screen()
.child(heading(IconType::AlertTriangle, "The move didn't finish", colors::danger())) .child(heading(
IconType::AlertTriangle,
"The move didn't finish",
colors::danger(),
))
.child(note(message, colors::danger())) .child(note(message, colors::danger()))
.child( .child(
label() label()

View file

@ -3,12 +3,12 @@ use freya::{prelude::*, router::*};
use crate::AppAssets; use crate::AppAssets;
use crate::Route; use crate::Route;
use crate::hooks::{
terms_document, terms_is_loading, use_launcher, use_notifications_snapshot, use_settings_snapshot,
use_splash, use_terms,
};
use crate::hooks::use_dispatch;
use crate::components::{Button, progress_track}; use crate::components::{Button, progress_track};
use crate::hooks::use_dispatch;
use crate::hooks::{
terms_document, terms_is_loading, use_launcher, use_notifications_snapshot,
use_settings_snapshot, use_splash, use_terms,
};
use crate::theme::colors; use crate::theme::colors;
/// Startup is a sequence of boolean gates, not measurable work, so the bar steps /// Startup is a sequence of boolean gates, not measurable work, so the bar steps
@ -227,7 +227,9 @@ impl Component for Startup {
if is_error { if is_error {
content = content content = content
.child(rect().height(Size::px(6.))) .child(rect().height(Size::px(6.)))
.child(RecoveryActions { snapshots: launcher.snapshots }) .child(RecoveryActions {
snapshots: launcher.snapshots,
})
.child( .child(
label() label()
.text(format!( .text(format!(

View file

@ -4,7 +4,7 @@
use oneclient_net::{NetworkFailure, classify_network_failure}; use oneclient_net::{NetworkFailure, classify_network_failure};
use reqwest::StatusCode; use reqwest::StatusCode;
use super::error::{friendly_xbox_error, MinecraftAuthError, MinecraftAuthStep}; use super::error::{MinecraftAuthError, MinecraftAuthStep, friendly_xbox_error};
#[derive(Debug, Clone, serde::Serialize)] #[derive(Debug, Clone, serde::Serialize)]
pub struct AuthErrorGuidance { pub struct AuthErrorGuidance {
@ -298,7 +298,9 @@ fn xerr(code: u64) -> MinecraftAuthError {
MinecraftAuthError::XboxError { MinecraftAuthError::XboxError {
step: MinecraftAuthStep::XstsAuthorize, step: MinecraftAuthStep::XstsAuthorize,
error_code: code, error_code: code,
message: friendly_xbox_error(code).unwrap_or("Xbox rejected sign-in.").to_string(), message: friendly_xbox_error(code)
.unwrap_or("Xbox rejected sign-in.")
.to_string(),
redirect: None, redirect: None,
} }
} }

View file

@ -35,7 +35,10 @@ pub enum MinecraftAuthError {
}, },
// Rendered via `error_chain` reqwest's `Display` names only the URL and the // Rendered via `error_chain` reqwest's `Display` names only the URL and the
// cause we need (certificate handshake DNS refused) is under it // cause we need (certificate handshake DNS refused) is under it
#[error("failed to request using HTTP during MSA step {step:?}: {}", oneclient_net::error_chain(source))] #[error(
"failed to request using HTTP during MSA step {step:?}: {}",
oneclient_net::error_chain(source)
)]
RequestError { RequestError {
step: MinecraftAuthStep, step: MinecraftAuthStep,
#[source] #[source]
@ -79,12 +82,24 @@ pub enum MinecraftAuthError {
pub fn friendly_xbox_error(code: u64) -> Option<&'static str> { pub fn friendly_xbox_error(code: u64) -> Option<&'static str> {
Some(match code { Some(match code {
2_148_916_227 => "This account has been banned or suspended from Xbox.", 2_148_916_227 => "This account has been banned or suspended from Xbox.",
2_148_916_229 => "This account is a child account that must be added to a Family group by an adult before signing in.", 2_148_916_229 => {
2_148_916_233 => "This Microsoft account does not have an Xbox profile yet. Create one at xbox.com, then try again.", "This account is a child account that must be added to a Family group by an adult before signing in."
2_148_916_234 => "This account has not accepted the Xbox Terms of Service. Sign in at xbox.com to accept them first.", }
2_148_916_235 => "Xbox Live is not available in your country or region, so sign-in is blocked.", 2_148_916_233 => {
2_148_916_236 | 2_148_916_237 => "This account requires adult verification (South Korea) before it can sign in.", "This Microsoft account does not have an Xbox profile yet. Create one at xbox.com, then try again."
2_148_916_238 => "This is a child account. An adult must add it to a Microsoft Family group before it can sign in.", }
2_148_916_234 => {
"This account has not accepted the Xbox Terms of Service. Sign in at xbox.com to accept them first."
}
2_148_916_235 => {
"Xbox Live is not available in your country or region, so sign-in is blocked."
}
2_148_916_236 | 2_148_916_237 => {
"This account requires adult verification (South Korea) before it can sign in."
}
2_148_916_238 => {
"This is a child account. An adult must add it to a Microsoft Family group before it can sign in."
}
_ => return None, _ => return None,
}) })
} }

Some files were not shown because too many files have changed in this diff Show more