Fixed #492
Fixed #510
Fixed #506
Fixed #568
Fixed #582
Fixed #578
Fixed #567
Fixed #588
Fixed #585
Fixed #571
Fixed #587
Fixed #584
This commit is contained in:
Julian Chang 2026-07-15 13:41:43 -04:00
parent 68337a79dc
commit 29d1f1c9d2
No known key found for this signature in database
125 changed files with 9155 additions and 824 deletions

670
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -28,12 +28,24 @@ oneclient_macro = { path = "./packages/oneclient_macro" }
# core (extra)
gamemode_rs = {path = "./packages/gamemode_rs"}
interfrost = {version = "=1.3.2"}
discord-rich-presence = {version = "=1.1.0"}
# logging
tracing = {version = "=0.1.44"}
tracing-futures = {version = "=0.2.5"}
tracing-subscriber = {version = "=0.3.23", features = ["env-filter", "fmt", "ansi", "json", "parking_lot", "registry"]}
sentry = {version = "=0.48.5", default-features = false, features = [
"backtrace",
"contexts",
"debug-images",
"panic",
"release-health",
"reqwest",
"rustls",
]}
sentry-tracing = {version = "=0.48.5"}
# database
sqlx = {version = "=0.9.0", default-features = false, features = [
"runtime-tokio",
@ -65,6 +77,7 @@ reqwest = {version = "=0.13.3", default-features = false, features = [
"form",
"hickory-dns",
]}
reqwest-websocket = {version = "=0.6.0"}
tokio = {version = "=1.52.3", features = ["full"]}
tokio-stream = {version = "=0.1.18", features = ["full"]}
tokio-util = {version = "=0.7.18", features = ["full"]}

View file

@ -1,4 +1,3 @@
use freya::prelude::*;
struct MinApp;

View file

@ -4,17 +4,12 @@ pub struct AppAssets;
impl AppAssets {
pub fn get_bytes(path: &str) -> Option<bytes::Bytes> {
AppAssets::get(path).map(|file| {
match file.data {
std::borrow::Cow::Borrowed(slice) => {
bytes::Bytes::copy_from_slice(slice)
},
std::borrow::Cow::Owned(vec) => {
tracing::warn!("Cloning asset data for '{}'", path);
bytes::Bytes::from(vec)
},
AppAssets::get(path).map(|file| match file.data {
std::borrow::Cow::Borrowed(slice) => bytes::Bytes::copy_from_slice(slice),
std::borrow::Cow::Owned(vec) => {
tracing::warn!("Cloning asset data for '{}'", path);
bytes::Bytes::from(vec)
}
})
}
}

View file

@ -1,4 +1,3 @@
use std::path::PathBuf;
use oneclient_core::notification::UserChoice;
@ -12,25 +11,43 @@ use crate::notifications::{ClusterUpdateSummary, NotificationSpec};
pub enum BridgeCommand {
ReloadSettings,
SaveSettings,
SetSettings { settings: LauncherSettings },
RecordSeenVersion { version: String },
SetSettings {
settings: LauncherSettings,
},
RecordSeenVersion {
version: String,
},
MarkOnboardingSeen,
AcceptTos {
terms_version: u32,
privacy_version: u32,
},
SaveGlobalProfile { profile: GameSettingsProfile },
UpdateGlobalProfile { update: ProfileUpdate },
SaveGlobalProfile {
profile: GameSettingsProfile,
},
UpdateGlobalProfile {
update: ProfileUpdate,
},
CreateSettingsProfile { name: String },
CreateSettingsProfile {
name: String,
},
CreateProfileFromGlobal {
name: String,
mem_max: Option<u32>,
force_fullscreen: Option<bool>,
},
UpsertNamedProfile { profile: GameSettingsProfile },
UpsertNamedProfile {
profile: GameSettingsProfile,
},
UpdateNamedProfile {
name: String,
update: ProfileUpdate,
},
DeleteNamedProfile { name: String },
DeleteNamedProfile {
name: String,
},
UpdateClusterProfile {
cluster_id: ClusterId,
@ -49,8 +66,12 @@ pub enum BridgeCommand {
vendor: oneclient_core::java::JavaVendor,
major: u32,
},
AddCustomJavaRuntime { path: PathBuf },
RemoveJavaRuntime { path: String },
AddCustomJavaRuntime {
path: PathBuf,
},
RemoveJavaRuntime {
path: String,
},
ToggleNotificationCenter,
CloseNotificationCenter,
@ -64,14 +85,20 @@ pub enum BridgeCommand {
AnswerPrompt(UserChoice),
OpenClusterUpdate(ClusterUpdateSummary),
CloseClusterUpdate,
SendNotification { spec: NotificationSpec },
SendNotification {
spec: NotificationSpec,
},
SendTestProgress {
current: u64,
total: u64,
},
LaunchCluster { cluster_id: ClusterId },
KillCluster { cluster_id: ClusterId },
LaunchCluster {
cluster_id: ClusterId,
},
KillCluster {
cluster_id: ClusterId,
},
DismissGameError,
ImportLocalFile {
@ -91,7 +118,9 @@ pub enum BridgeCommand {
bundle_name: String,
skip_compatibility: bool,
},
ApplyBundleUpdates { cluster_id: ClusterId },
ApplyBundleUpdates {
cluster_id: ClusterId,
},
SyncBundles,
ImportLauncher {

View file

@ -1,4 +1,3 @@
mod commands;
mod runtime;
mod snapshot;

View file

@ -9,8 +9,8 @@ use tokio::sync::{mpsc, watch};
use crate::components::IconType;
use crate::notifications::{
ClusterUpdateSummary, InboxEntry, MESSAGE_TOAST_TTL, NotificationAction, NotificationActionKind,
NotificationSpec, NotificationState, PendingPrompt, PendingPromptView,
ClusterUpdateSummary, InboxEntry, MESSAGE_TOAST_TTL, NotificationAction,
NotificationActionKind, NotificationSpec, NotificationState, PendingPrompt, PendingPromptView,
};
use super::commands::BridgeCommand;
@ -243,6 +243,7 @@ impl CoreBridgeRuntime {
LauncherState::initialize(notifier).await?;
oneclient_core::status::start();
oneclient_core::plus::start();
let data_dir = oneclient_core::paths::launcher_dir()
.map(|p| p.display().to_string())
@ -277,10 +278,12 @@ impl CoreBridgeRuntime {
snapshots.settings.error = None;
let state = LauncherState::get()?;
let loaded = store::load_settings(Some(&state.services.notifier)).await;
let discord_enabled = loaded.discord_enabled;
{
let mut lock = state.settings.write();
*lock = loaded;
}
state.discord.set_enabled(discord_enabled);
refresh_settings(snapshots)?;
}
BridgeCommand::SaveSettings => {
@ -298,6 +301,7 @@ impl CoreBridgeRuntime {
mutate_settings(snapshots, |s| *s = settings)?;
let state = LauncherState::get()?;
let snapshot = state.settings.read().clone();
state.discord.set_enabled(snapshot.discord_enabled);
if let Err(err) = save_settings(&snapshot).await {
snapshots.settings.error = Some(err.to_string());
}
@ -319,6 +323,20 @@ impl CoreBridgeRuntime {
snapshots.settings.error = Some(err.to_string());
}
}
BridgeCommand::AcceptTos {
terms_version,
privacy_version,
} => {
mutate_settings(snapshots, |settings| {
settings.accepted_tos_version = terms_version;
settings.accepted_privacy_version = privacy_version;
})?;
let state = LauncherState::get()?;
let snapshot = state.settings.read().clone();
if let Err(err) = save_settings(&snapshot).await {
snapshots.settings.error = Some(err.to_string());
}
}
BridgeCommand::SaveGlobalProfile { profile } => {
let state = LauncherState::get()?;
@ -609,20 +627,18 @@ impl CoreBridgeRuntime {
version_id,
} => {
let state = LauncherState::get()?;
match install_package(&state, provider, &project_id, &version_id, cluster_id).await
{
Ok(()) => {
state
.services
.notifier
.send_info("Installed", "Package added to cluster");
bump_clusters_generation(snapshots);
tokio::spawn(async move {
let notifier = state.services.notifier.clone();
match install_package(&state, provider, &project_id, &version_id, cluster_id)
.await
{
Ok(name) => {
notifier.send_info("Installed", &format!("Added {name}"));
notifier.invalidate_clusters();
}
Err(err) => notifier.send_error("Install failed", &err.to_string()),
}
Err(err) => state
.services
.notifier
.send_error("Install failed", &err.to_string()),
}
});
}
BridgeCommand::InstallBundle {
@ -631,15 +647,24 @@ impl CoreBridgeRuntime {
skip_compatibility,
} => {
let state = LauncherState::get()?;
oneclient_core::install_bundle(
cluster_id,
&bundle_name,
skip_compatibility,
state.bundles.as_ref(),
&state.services,
)
.await?;
bump_clusters_generation(snapshots);
tokio::spawn(async move {
let notifier = state.services.notifier.clone();
match oneclient_core::install_bundle(
cluster_id,
&bundle_name,
skip_compatibility,
state.bundles.as_ref(),
&state.services,
)
.await
{
Ok(_) => {
notifier.send_info("Installed", &format!("Added {bundle_name}"));
notifier.invalidate_clusters();
}
Err(err) => notifier.send_error("Install failed", &err.to_string()),
}
});
}
BridgeCommand::ApplyBundleUpdates { cluster_id } => {
let state = LauncherState::get()?;
@ -792,7 +817,7 @@ async fn install_package(
project_id: &str,
version_id: &str,
cluster_id: i64,
) -> anyhow::Result<()> {
) -> anyhow::Result<String> {
let provider_impl = state.services.packages.get(provider)?;
let project = provider_impl
.get_project(project_id, &state.services)
@ -823,7 +848,7 @@ async fn install_package(
child.finish();
session.finish();
result?;
Ok(())
Ok(project.name)
}
fn apply_game_stage(snapshots: &mut BridgeSnapshot, cluster_id: i64, stage: LaunchStage) {

View file

@ -32,10 +32,7 @@ impl GameSnapshot {
}
pub fn logs_for(&self, cluster_id: i64) -> Arc<Vec<Arc<str>>> {
self.logs
.get(&cluster_id)
.cloned()
.unwrap_or_default()
self.logs.get(&cluster_id).cloned().unwrap_or_default()
}
}

View file

@ -60,7 +60,9 @@ impl Component for ActiveClusterPanel {
let p = intro.get().value();
let slide_x = (p - 1.0) * 48.0;
let parsed = active.as_ref().and_then(|c| parse_mc_version(&c.mc_version));
let parsed = active
.as_ref()
.and_then(|c| parse_mc_version(&c.mc_version));
let metadata = use_version_metadata(
parsed.as_ref().map(|p| p.major),
parsed.and_then(|p| p.minor),
@ -114,7 +116,11 @@ impl Component for ActiveClusterPanel {
.spacing(8.)
.cross_align(Alignment::Center)
.margin(Gaps::new(8., 0., 0., 0.))
.child(launch_button(cluster_id, dispatch, launch_button_state(&game, cluster_id)))
.child(launch_button(
cluster_id,
dispatch,
launch_button_state(&game, cluster_id),
))
.child(cluster_settings_button(cluster_id)),
)
}

View file

@ -3,7 +3,10 @@ use std::borrow::Cow;
use freya::prelude::*;
use crate::{components::{IconType, Icon}, theme::colors};
use crate::{
components::{Icon, IconType},
theme::colors,
};
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum ButtonVariant {
@ -285,8 +288,7 @@ impl Component for Button {
rect.get_style().clone_from(&self.style);
rect = rect.background(background)
.color(foreground);
rect = rect.background(background).color(foreground);
if let Some(border) = border_color {
rect = rect.border(
@ -420,7 +422,11 @@ fn size_layout(size: ButtonSize) -> (Gaps, CornerRadius, f32, Size, Size) {
pub fn link_button() -> Button {
Button::new()
.primary()
.child(Icon::new(IconType::LinkExternal01).size(14.).color(colors::fg_primary()))
.child(
Icon::new(IconType::LinkExternal01)
.size(14.)
.color(colors::fg_primary()),
)
.child(label().text("Open"))
}
@ -433,4 +439,4 @@ pub fn open_folder_button(folder: std::path::PathBuf) -> Button {
crate::platform::open_url(&folder.to_string_lossy());
})
.child(Icon::new(IconType::Folder).size(16.))
}
}

View file

@ -49,7 +49,13 @@ fn dialog(summary: &ClusterUpdateSummary, dispatch: crate::BridgeDispatch) -> im
.corner_radius(CornerRadius::new_all(16.))
.background(CARD_BG)
.border(border_all_color(1., colors::component_border()))
.shadow(Shadow::from((0., 18., 52., 0., Color::from_argb(150, 0, 0, 0))))
.shadow(Shadow::from((
0.,
18.,
52.,
0.,
Color::from_argb(150, 0, 0, 0),
)))
.child(banner(summary))
.child(content(summary, dispatch))
}
@ -93,7 +99,11 @@ fn banner(summary: &ClusterUpdateSummary) -> impl IntoElement {
.horizontal()
.cross_align(Alignment::Center)
.spacing(5.)
.child(Icon::new(IconType::RefreshCw01).size(12.).color(Color::WHITE))
.child(
Icon::new(IconType::RefreshCw01)
.size(12.)
.color(Color::WHITE),
)
.child(
label()
.text("Updated")

View file

@ -81,8 +81,8 @@ impl Component for Dropdown {
};
let visible = options.len().min(MAX_VISIBLE_OPTIONS);
let list_h = visible as f32 * OPTION_HEIGHT
+ visible.saturating_sub(1) as f32 * OPTION_SPACING;
let list_h =
visible as f32 * OPTION_HEIGHT + visible.saturating_sub(1) as f32 * OPTION_SPACING;
let offset_y = match (button_area(), list_size()) {
(Some(button), Some(list)) => {
@ -141,10 +141,10 @@ impl Component for Dropdown {
.font_size(12.)
.color(colors::fg_primary()),
)
.child(Icon::new(
IconType::ChevronDown)
.size(14.)
.color(colors::fg_secondary()),
.child(
Icon::new(IconType::ChevronDown)
.size(14.)
.color(colors::fg_secondary()),
),
)
.maybe_child(is_open.then(|| {

View file

@ -83,9 +83,12 @@ pub fn use_art_bytes(
let state = reader.state();
match (&art_url, &*state) {
(Some(url), QueryStateData::Settled { res: Ok(bytes), .. })
| (Some(url), QueryStateData::Loading { res: Some(Ok(bytes)) }) => {
(format!("{max_edge}|{url}"), bytes.clone())
}
| (
Some(url),
QueryStateData::Loading {
res: Some(Ok(bytes)),
},
) => (format!("{max_edge}|{url}"), bytes.clone()),
_ => (
format!("{max_edge}|{HOME_BACKGROUND_ASSET}"),
fallback.read().clone(),

View file

@ -1,4 +1,3 @@
use std::sync::{Arc, LazyLock};
use freya::prelude::*;
@ -289,12 +288,11 @@ fn auto_scroll_toggle(mut auto_scroll: State<bool>) -> impl IntoElement {
} else {
colors::fg_secondary()
}))
.child(
label()
.text("Auto-scroll")
.font_size(11.)
.color(if on { colors::brand() } else { colors::fg_secondary() }),
)
.child(label().text("Auto-scroll").font_size(11.).color(if on {
colors::brand()
} else {
colors::fg_secondary()
}))
.into_element()
}
@ -308,11 +306,15 @@ struct PosGeom {
corrected_y: f32,
}
fn pos_at_screen(lines: &[Arc<str>], geom: PosGeom, screen_x: f32, screen_y: f32) -> (usize, usize) {
fn pos_at_screen(
lines: &[Arc<str>],
geom: PosGeom,
screen_x: f32,
screen_y: f32,
) -> (usize, usize) {
let region_y = screen_y - geom.viewport_top_px;
let band_y = region_y - geom.corrected_y - BODY_PAD_TOP;
let line =
((band_y / LINE_H).floor() as i64).clamp(0, (geom.count as i64 - 1).max(0)) as usize;
let line = ((band_y / LINE_H).floor() as i64).clamp(0, (geom.count as i64 - 1).max(0)) as usize;
let text_origin_x = ROW_PAD_X + geom.num_col_w + NUM_GAP;
let local_x = screen_x - geom.viewport_left_px - geom.corrected_x - text_origin_x;

View file

@ -55,11 +55,7 @@ impl Component for MajorVersionCard {
let on_press = self.on_press.clone();
let opacity = if selected || hovered || focused {
if selected {
1.0
} else {
0.85
}
if selected { 1.0 } else { 0.85 }
} else {
0.6
};
@ -108,7 +104,10 @@ impl Component for MajorVersionCard {
.main_align(Alignment::SpaceBetween)
.corner_radius(CornerRadius::new_all(12.))
.cross_align(Alignment::Start)
.border(border_all_color(1., border_color).alignment(BorderAlignment::Inner))
.border(
border_all_color(1., border_color)
.alignment(BorderAlignment::Inner),
)
.layer(Layer::Relative(3))
.background(
LinearGradient::new()

View file

@ -84,7 +84,7 @@ pub fn use_microsoft_login() -> MicrosoftLogin {
return;
}
// open the browser immediately
// open the browser immediately
platform::open_url(session.auth_url());
finish.mutate(session.clone());
pending_login.set(Some(session));
@ -100,7 +100,7 @@ pub fn use_microsoft_login() -> MicrosoftLogin {
}
});
// is true once the flow has some status updates (just to ensure the user has seen the popup) and the flow is still in progress
// is true once the flow has some status updates (just to ensure the user has seen the popup) and the flow is still in progress
let locked = status.as_ref().is_some_and(|s| s.current > 0);
let in_flight = begin.read().state().is_loading() || finish.read().state().is_loading();
let pending = in_flight && !*cancelled.read();

View file

@ -37,7 +37,7 @@ impl Component for Navbar {
.window_drag()
.width(Size::window_percent(100.))
.height(Size::px(theme::NAVBAR_HEIGHT_PX))
.position(Position::new_absolute().top(0.).left(0.).right(0.))
.position(Position::new_absolute().top(0.).left(0.).right(0.)),
)
}
}
@ -95,8 +95,8 @@ struct NavLink {
impl Component for NavLink {
fn render(&self) -> impl IntoElement {
let mut hovering = use_state(|| false);
let a11y_id = use_a11y();
let focused = use_focus(a11y_id);
let a11y_id = use_a11y();
let focused = use_focus(a11y_id);
let active = self.active;
let target = self.target.clone();
@ -125,12 +125,12 @@ impl Component for NavLink {
.cross_align(Alignment::Center)
.spacing(2.)
.width(Size::px(nav_label.len() as f32 * 10. + 10.))
// TODO: Hacky workaround for weird freya measurement bug, where if a rectangle has a transparent background (so in dec = 0), it will be measured weirdly.
// This basically fools freya into thinking it indeed has a box and properly handles pointer events
.background(Color::RED.with_a(0))
.a11y_id(a11y_id)
.a11y_focusable(true)
.a11y_role(AccessibilityRole::Button)
// TODO: Hacky workaround for weird freya measurement bug, where if a rectangle has a transparent background (so in dec = 0), it will be measured weirdly.
// This basically fools freya into thinking it indeed has a box and properly handles pointer events
.background(Color::RED.with_a(0))
.a11y_id(a11y_id)
.a11y_focusable(true)
.a11y_role(AccessibilityRole::Button)
.on_all_press(move |e: Event<PressEventData>| {
e.prevent_default();
let _ = RouterContext::get().push(target.clone());

View file

@ -391,9 +391,12 @@ fn remote_icon(
let reader = icon_query.read();
let loaded = match (icon_url, &*reader.state()) {
(Some(url), QueryStateData::Settled { res: Ok(bytes), .. })
| (Some(url), QueryStateData::Loading { res: Some(Ok(bytes)) }) => {
Some((url.clone(), bytes.clone()))
}
| (
Some(url),
QueryStateData::Loading {
res: Some(Ok(bytes)),
},
) => Some((url.clone(), bytes.clone())),
_ => None,
};
@ -420,7 +423,11 @@ fn icon_box(icon: IconType, size: f32) -> Element {
.height(Size::px(size))
.corner_radius(CornerRadius::new_all(8.))
.background(colors::component_bg())
.child(Icon::new(icon).size(size * 0.4).color(colors::fg_secondary()))
.child(
Icon::new(icon)
.size(size * 0.4)
.color(colors::fg_secondary()),
)
.into_element()
}
@ -436,7 +443,10 @@ fn meta_size(size: u64) -> impl IntoElement {
}
fn provider_badge(provider: ProviderId) -> Element {
badge(Icon::new(provider).size(12.).into_element(), provider.to_string())
badge(
Icon::new(provider).size(12.).into_element(),
provider.to_string(),
)
}
fn local_badge() -> Element {

View file

@ -128,7 +128,7 @@ impl Component for PlayerModel {
let (w, h) = (ctx.size.width, ctx.size.height);
if w <= 0.0 || h <= 0.0 {
if w <= 0.0 || h <= 0.0 {
return;
}

View file

@ -49,7 +49,9 @@ impl Component for RecentsRow {
conf.on_creation(OnCreation::Run);
conf.on_change(OnChange::Rerun);
let total = CARD_MS + (count.saturating_sub(1) as u64) * STAGGER_MS;
AnimNum::new(0., 1.).time(total.max(1)).function(Function::Linear)
AnimNum::new(0., 1.)
.time(total.max(1))
.function(Function::Linear)
});
let progress = intro.get().value();
@ -112,11 +114,7 @@ impl Component for ClusterCard {
let eased = 1.0 - (1.0 - local).powi(3);
let rise = (1.0 - eased) * CARD_RISE_PX;
let title = format!(
"{} {}",
self.cluster.mc_version,
self.cluster.mc_loader
);
let title = format!("{} {}", self.cluster.mc_version, self.cluster.mc_loader);
let cluster_id = self.cluster.id;
let on_press = move |_| {

View file

@ -102,11 +102,15 @@ impl Component for ScreenshotViewer {
.height(Size::fill())
.child(preview),
)
.child(chevron_btn(IconType::ArrowRight, has_next, move |_| {
if idx + 1 < len {
index.set(idx + 1);
}
})),
.child(chevron_btn(
IconType::ArrowRight,
has_next,
move |_| {
if idx + 1 < len {
index.set(idx + 1);
}
},
)),
)
.child(
rect()
@ -119,7 +123,9 @@ impl Component for ScreenshotViewer {
.secondary()
.on_press(move |_| {
if let Some(dir) = open_path.parent() {
crate::platform::open_url(&dir.to_string_lossy());
crate::platform::open_url(
&dir.to_string_lossy(),
);
}
})
.child(Icon::new(IconType::Folder).size(14.))
@ -198,8 +204,12 @@ fn header_row(
len: usize,
on_close: impl Into<EventHandler<Event<PressEventData>>>,
) -> impl IntoElement {
let mut meta = format!("{} • {}", format_size(info.size_bytes), fmt_date(info.created));
let mut meta = format!(
"{} • {}",
format_size(info.size_bytes),
fmt_date(info.created)
);
if let Some(res) = info.resolution {
meta = format!("{} • {}", format_res(res), meta);
}

View file

@ -314,7 +314,11 @@ impl ScrollArea {
let shift = *shift_held.read();
let h_delta = if horizontal {
if shift && e.delta_x == 0.0 { e.delta_y } else { e.delta_x }
if shift && e.delta_x == 0.0 {
e.delta_y
} else {
e.delta_x
}
} else {
0.0
};

View file

@ -19,7 +19,7 @@ impl TabItem {
}
}
#[allow(dead_code)]
#[allow(dead_code)]
pub fn count_text(mut self, text: impl Into<String>) -> Self {
self.count = Some(text.into());
self

View file

@ -32,7 +32,6 @@ impl Variant {
Self::Message
}
}
}
fn entry_icon(entry: &InboxEntry) -> (IconType, Color) {

View file

@ -19,7 +19,7 @@ impl Component for UpdatePromptOverlay {
let Some(prompt) = snapshot.pending_prompt.clone() else {
return rect().into_element();
};
if prompt.kind != PromptKind::Update {
return rect().into_element();
}

View file

@ -1,2 +1,2 @@
pub const APP_ID: &str = "org.polyfrost.OneClient";
pub const WINDOW_TITLE: &str = "OneClient";
pub const WINDOW_TITLE: &str = "OneClient";

View file

@ -42,6 +42,13 @@ impl BridgeDispatch {
self.send(BridgeCommand::MarkOnboardingSeen);
}
pub fn accept_tos(&self, terms_version: u32, privacy_version: u32) {
self.send(BridgeCommand::AcceptTos {
terms_version,
privacy_version,
});
}
pub fn import_launcher(
&self,
source: oneclient_core::MigrationSource,
@ -64,9 +71,7 @@ impl BridgeDispatch {
}
pub fn create_settings_profile(&self, name: impl Into<String>) {
self.send(BridgeCommand::CreateSettingsProfile {
name: name.into(),
});
self.send(BridgeCommand::CreateSettingsProfile { name: name.into() });
}
pub fn create_profile_from_global(
@ -94,20 +99,11 @@ impl BridgeDispatch {
}
pub fn delete_named_profile(&self, name: impl Into<String>) {
self.send(BridgeCommand::DeleteNamedProfile {
name: name.into(),
});
self.send(BridgeCommand::DeleteNamedProfile { name: name.into() });
}
pub fn update_cluster_profile(
&self,
cluster_id: ClusterId,
update: ProfileUpdate,
) {
self.send(BridgeCommand::UpdateClusterProfile {
cluster_id,
update,
});
pub fn update_cluster_profile(&self, cluster_id: ClusterId, update: ProfileUpdate) {
self.send(BridgeCommand::UpdateClusterProfile { cluster_id, update });
}
pub fn create_and_assign_cluster_profile(
@ -121,11 +117,7 @@ impl BridgeDispatch {
});
}
pub fn set_cluster_loader_version(
&self,
cluster_id: ClusterId,
version: impl Into<String>,
) {
pub fn set_cluster_loader_version(&self, cluster_id: ClusterId, version: impl Into<String>) {
self.send(BridgeCommand::SetClusterLoaderVersion {
cluster_id,
version: version.into(),
@ -318,6 +310,7 @@ impl NotificationBuilder {
}
pub fn send(self) {
self.dispatch.send(BridgeCommand::SendNotification { spec: self.spec });
self.dispatch
.send(BridgeCommand::SendNotification { spec: self.spec });
}
}

View file

@ -19,30 +19,30 @@ pub use queries::{
CancelMicrosoftLoginKeys, CancelMicrosoftLoginMutation, ClusterAction, ClusterBundles,
ClusterLogsQuery, FinishMicrosoftLoginMutation, LogAction, LogContentQuery, MigrationQuery,
OnboardingBundlesQuery, RefreshAccountKeys, RemoveAccountKeys, ScreenshotAction,
SetDefaultAccountKeys, UploadLogKeys, UploadLogMutation, UseLogAction, UseRefreshAccount,
UseRemoveAccount, UseScreenshotAction, UseSetDefaultAccount, UseUploadLog, VERSIONS_PAGE_SIZE,
accounts_have_microsoft, bundle_overrides_map, bundles_with_status_items, category_list,
changelog_error, changelog_groups, changelog_is_loading, cluster_content_items,
SetDefaultAccountKeys, TermsQuery, UploadLogKeys, UploadLogMutation, UseLogAction,
UseRefreshAccount, UseRemoveAccount, UseScreenshotAction, UseSetDefaultAccount, UseUploadLog,
VERSIONS_PAGE_SIZE, accounts_have_microsoft, bundle_overrides_map, bundles_with_status_items,
category_list, changelog_error, changelog_groups, changelog_is_loading, cluster_content_items,
content_type_for_slug, has_migration_data, invalidate_cluster_queries, invalidate_java_queries,
invalidate_logs_queries, invalidate_profile_queries, invalidate_screenshots_queries,
java_runtimes, loader_versions, login_code_already_handled, migration_detection,
mutation_error, mutation_is_pending, onboarding_bundles_items, package_meta_batch,
pick_version_metadata, project_detail, provider_versions, reset_login_code_dedup, search_items,
search_pending, search_total, try_account, try_accounts, try_cluster_analytics,
try_cluster_logs, try_cluster_screenshots, try_default_account, try_game_profile,
try_global_analytics, try_log_content, use_account, use_accounts, use_add_microsoft_account,
use_add_offline_account, use_begin_microsoft_login, use_bundle_overrides, use_bundle_updates,
use_bundles_with_status, use_cached_image, use_cancel_microsoft_login, use_changelog,
use_cluster_analytics, use_cluster_content, use_cluster_logs, use_cluster_mutation,
use_cluster_profile, use_cluster_screenshots, use_cluster_settings, use_clusters,
use_current_account, use_default_account, use_finish_microsoft_login, use_game_profile,
use_global_analytics, use_java_runtimes, use_loader_versions, use_local_image, use_log_action,
use_log_content, use_migration, use_named_profiles, use_onboarding_bundles,
use_package_categories, use_package_meta_batch, use_package_project, use_package_search,
use_package_versions, use_player_profile, use_player_skin, use_provider_versions,
use_refresh_account, use_refresh_all_accounts, use_remove_account, use_screenshot_action,
use_set_default_account, use_upload_log, use_version_metadata, use_versions, version_list,
versions_total,
search_pending, search_total, terms_document, terms_error, terms_is_loading, try_account,
try_accounts, try_cluster_analytics, try_cluster_logs, try_cluster_screenshots,
try_default_account, try_game_profile, try_global_analytics, try_log_content, use_account,
use_accounts, use_add_microsoft_account, use_add_offline_account, use_begin_microsoft_login,
use_bundle_overrides, use_bundle_updates, use_bundles_with_status, use_cached_image,
use_cancel_microsoft_login, use_changelog, use_cluster_analytics, use_cluster_content,
use_cluster_logs, use_cluster_mutation, use_cluster_profile, use_cluster_screenshots,
use_cluster_settings, use_clusters, use_current_account, use_default_account,
use_finish_microsoft_login, use_game_profile, use_global_analytics, use_java_runtimes,
use_loader_versions, use_local_image, use_log_action, use_log_content, use_migration,
use_named_profiles, use_onboarding_bundles, use_package_categories, use_package_meta_batch,
use_package_project, use_package_search, use_package_versions, use_player_profile,
use_player_skin, use_provider_versions, use_refresh_account, use_refresh_all_accounts,
use_remove_account, use_screenshot_action, use_set_default_account, use_terms, use_upload_log,
use_version_metadata, use_versions, version_list, versions_total,
};
use crate::{

View file

@ -2,7 +2,6 @@ use freya::query::{Query, QueryCapability, QueryStateData, UseQuery, use_query};
use oneclient_core::LauncherError;
use oneclient_core::game::{Analytics, cluster_analytics, global_analytics};
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct GlobalAnalyticsKeys;
@ -33,7 +32,6 @@ pub fn try_global_analytics(query: &UseQuery<GlobalAnalyticsQuery>) -> Option<An
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct ClusterAnalyticsKeys {
pub cluster_id: i64,

View file

@ -1,4 +1,3 @@
use std::sync::Mutex;
use freya::query::{

View file

@ -52,7 +52,9 @@ pub fn onboarding_bundles_items(
let reader = query.read();
match &*reader.state() {
QueryStateData::Settled { res: Ok(list), .. } => Some(list.clone()),
QueryStateData::Loading { res: Some(Ok(list)) } => Some(list.clone()),
QueryStateData::Loading {
res: Some(Ok(list)),
} => Some(list.clone()),
_ => None,
}
}
@ -75,12 +77,8 @@ impl QueryCapability for BundlesWithStatusQuery {
async fn run(&self, keys: &Self::Keys) -> Result<Self::Ok, Self::Err> {
let _ = keys;
let state = LauncherState::get()?;
get_bundles_with_update_status(
self.cluster_id,
state.bundles.as_ref(),
&state.services,
)
.await
get_bundles_with_update_status(self.cluster_id, state.bundles.as_ref(), &state.services)
.await
}
}
@ -90,7 +88,9 @@ pub fn bundles_with_status_items(
let reader = query.read();
match &*reader.state() {
QueryStateData::Settled { res: Ok(list), .. } => list.clone(),
QueryStateData::Loading { res: Some(Ok(list)) } => list.clone(),
QueryStateData::Loading {
res: Some(Ok(list)),
} => list.clone(),
_ => Vec::new(),
}
}

View file

@ -1,5 +1,7 @@
use freya::query::{Query, QueryCapability, QueryStateData, UseQuery, use_query};
use oneclient_core::{fetch_changelog, parse_changelog, ChangelogGroup, LauncherError, LauncherState};
use oneclient_core::{
ChangelogGroup, LauncherError, LauncherState, fetch_changelog, parse_changelog,
};
use crate::hooks::use_settings_snapshot;
@ -32,15 +34,14 @@ pub fn use_changelog() -> UseQuery<ChangelogQuery> {
.trim()
.to_string();
use_query(Query::new(
ChangelogKeys { meta_url_base },
ChangelogQuery,
))
use_query(Query::new(ChangelogKeys { meta_url_base }, ChangelogQuery))
}
pub fn changelog_groups(query: &UseQuery<ChangelogQuery>) -> Option<Vec<ChangelogGroup>> {
match &*query.read().state() {
QueryStateData::Settled { res: Ok(groups), .. } => Some(groups.clone()),
QueryStateData::Settled {
res: Ok(groups), ..
} => Some(groups.clone()),
QueryStateData::Loading {
res: Some(Ok(groups)),
} => Some(groups.clone()),

View file

@ -1,4 +1,3 @@
use freya::query::{Query, QueryCapability, QueryStateData, UseQuery, use_query};
use oneclient_core::packages::{ContentType, PackageStore};
use oneclient_core::{LauncherError, LauncherState, LinkedArtifactInfo};
@ -51,7 +50,9 @@ pub fn cluster_content_items(query: &UseQuery<ClusterContentQuery>) -> Vec<Linke
let reader = query.read();
match &*reader.state() {
QueryStateData::Settled { res: Ok(list), .. } => list.clone(),
QueryStateData::Loading { res: Some(Ok(list)) } => list.clone(),
QueryStateData::Loading {
res: Some(Ok(list)),
} => list.clone(),
_ => Vec::new(),
}
}

View file

@ -1,7 +1,7 @@
use freya::query::{Query, QueryCapability, UseQuery, use_query};
use oneclient_core::clusters::{Cluster, ClusterManager};
use oneclient_core::LauncherError;
use oneclient_core::LauncherState;
use oneclient_core::clusters::{Cluster, ClusterManager};
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct ListClustersQuery;
@ -23,5 +23,3 @@ impl QueryCapability for ListClustersQuery {
pub fn use_clusters() -> UseQuery<ListClustersQuery> {
use_query(Query::new(ListClustersKeys, ListClustersQuery))
}

View file

@ -1,6 +1,4 @@
use freya::query::{
QueriesStorage, Query, QueryCapability, QueryStateData, UseQuery, use_query,
};
use freya::query::{QueriesStorage, Query, QueryCapability, QueryStateData, UseQuery, use_query};
use oneclient_core::LauncherState;
use oneclient_core::java::{AvailableJava, JavaManager, JavaRuntime, JavaVendor};
@ -31,7 +29,9 @@ pub fn java_runtimes(query: &UseQuery<ListJavaRuntimesQuery>) -> Vec<JavaRuntime
let reader = query.read();
match &*reader.state() {
QueryStateData::Settled { res: Ok(list), .. } => list.clone(),
QueryStateData::Loading { res: Some(Ok(list)) } => list.clone(),
QueryStateData::Loading {
res: Some(Ok(list)),
} => list.clone(),
_ => Vec::new(),
}
}
@ -68,14 +68,14 @@ pub fn use_provider_versions(vendor: JavaVendor) -> UseQuery<ProviderVersionsQue
))
}
pub fn provider_versions(
query: &UseQuery<ProviderVersionsQuery>,
) -> (Vec<AvailableJava>, bool) {
pub fn provider_versions(query: &UseQuery<ProviderVersionsQuery>) -> (Vec<AvailableJava>, bool) {
let reader = query.read();
match &*reader.state() {
QueryStateData::Settled { res: Ok(list), .. } => (list.clone(), false),
QueryStateData::Settled { res: Err(_), .. } => (Vec::new(), false),
QueryStateData::Loading { res: Some(Ok(list)) } => (list.clone(), true),
QueryStateData::Loading {
res: Some(Ok(list)),
} => (list.clone(), true),
_ => (Vec::new(), true),
}
}

View file

@ -89,7 +89,9 @@ where
{
match &*query.read().state() {
QueryStateData::Settled { res: Ok(value), .. } => Some(value.clone()),
QueryStateData::Loading { res: Some(Ok(value)) } => Some(value.clone()),
QueryStateData::Loading {
res: Some(Ok(value)),
} => Some(value.clone()),
_ => None,
}
}

View file

@ -14,6 +14,7 @@ mod player_profile;
mod screenshots;
mod settings_profiles;
mod skin;
mod tos;
mod version_metadata;
mod versions;
@ -69,5 +70,6 @@ pub use settings_profiles::{
use_named_profiles,
};
pub use skin::use_player_skin;
pub use tos::{TermsQuery, terms_document, terms_error, terms_is_loading, use_terms};
pub use version_metadata::{pick_version_metadata, use_version_metadata};
pub use versions::{loader_versions, use_loader_versions, use_versions};

View file

@ -1,4 +1,3 @@
use std::path::PathBuf;
use freya::query::{Mutation, MutationCapability, QueriesStorage, UseMutation, use_mutation};
@ -37,9 +36,18 @@ pub struct ClusterMutation;
#[derive(Clone, PartialEq, Eq, Hash)]
pub enum ClusterAction {
ToggleArtifact { cluster_id: ClusterId, hash: String },
RemoveArtifact { cluster_id: ClusterId, hash: String },
RemoveBundlePackageFromDisk { cluster_id: ClusterId, hash: String },
ToggleArtifact {
cluster_id: ClusterId,
hash: String,
},
RemoveArtifact {
cluster_id: ClusterId,
hash: String,
},
RemoveBundlePackageFromDisk {
cluster_id: ClusterId,
hash: String,
},
SetBundlePackageEnabled {
cluster_id: ClusterId,
bundle_name: String,
@ -106,10 +114,14 @@ impl MutationCapability for ClusterMutation {
ClusterAction::SetDedicatedDir {
cluster_id,
dedicated,
} => oneclient_core::clusters::ClusterManager::set_dedicated_dir(
&state, *cluster_id, *dedicated,
)
.await,
} => {
oneclient_core::clusters::ClusterManager::set_dedicated_dir(
&state,
*cluster_id,
*dedicated,
)
.await
}
};
result.map_err(|e| e.to_string())
}

View file

@ -1,4 +1,3 @@
use freya::query::{Query, QueryCapability, QueryStateData, UseQuery, use_query};
use oneclient_core::packages::domain::GameLoader;
use oneclient_core::packages::types::{
@ -48,7 +47,8 @@ impl QueryCapability for PackageSearchQuery {
&SearchFilters {
query: (!keys.query.trim().is_empty()).then(|| keys.query.trim().to_string()),
content_type: Some(keys.content_type),
game_versions: (!keys.game_versions.is_empty()).then(|| keys.game_versions.clone()),
game_versions: (!keys.game_versions.is_empty())
.then(|| keys.game_versions.clone()),
loaders: (!keys.loaders.is_empty()).then(|| keys.loaders.clone()),
categories: (!keys.categories.is_empty()).then(|| keys.categories.clone()),
sort: Some(keys.sort),
@ -91,7 +91,9 @@ pub fn search_items(query: &UseQuery<PackageSearchQuery>) -> Vec<ProjectSummary>
let reader = query.read();
match &*reader.state() {
QueryStateData::Settled { res: Ok(page), .. } => page.items.clone(),
QueryStateData::Loading { res: Some(Ok(page)) } => page.items.clone(),
QueryStateData::Loading {
res: Some(Ok(page)),
} => page.items.clone(),
_ => Vec::new(),
}
}
@ -100,13 +102,18 @@ pub fn search_total(query: &UseQuery<PackageSearchQuery>) -> usize {
let reader = query.read();
match &*reader.state() {
QueryStateData::Settled { res: Ok(page), .. } => page.total,
QueryStateData::Loading { res: Some(Ok(page)) } => page.total,
QueryStateData::Loading {
res: Some(Ok(page)),
} => page.total,
_ => 0,
}
}
pub fn search_pending(query: &UseQuery<PackageSearchQuery>) -> bool {
matches!(&*query.read().state(), QueryStateData::Pending | QueryStateData::Loading { .. })
matches!(
&*query.read().state(),
QueryStateData::Pending | QueryStateData::Loading { .. }
)
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
@ -126,7 +133,9 @@ impl QueryCapability for PackageProjectQuery {
async fn run(&self, keys: &Self::Keys) -> Result<Self::Ok, Self::Err> {
let state = LauncherState::get()?;
let provider = state.services.packages.get(keys.provider)?;
provider.get_project(&keys.project_id, &state.services).await
provider
.get_project(&keys.project_id, &state.services)
.await
}
}
@ -146,8 +155,12 @@ pub fn use_package_project(
pub fn project_detail(query: &UseQuery<PackageProjectQuery>) -> Option<ProjectDetail> {
let reader = query.read();
match &*reader.state() {
QueryStateData::Settled { res: Ok(detail), .. } => Some(detail.clone()),
QueryStateData::Loading { res: Some(Ok(detail)) } => Some(detail.clone()),
QueryStateData::Settled {
res: Ok(detail), ..
} => Some(detail.clone()),
QueryStateData::Loading {
res: Some(Ok(detail)),
} => Some(detail.clone()),
_ => None,
}
}
@ -260,7 +273,9 @@ pub fn version_list(query: &UseQuery<PackageVersionsQuery>) -> Vec<VersionSummar
let reader = query.read();
match &*reader.state() {
QueryStateData::Settled { res: Ok(page), .. } => page.items.clone(),
QueryStateData::Loading { res: Some(Ok(page)) } => page.items.clone(),
QueryStateData::Loading {
res: Some(Ok(page)),
} => page.items.clone(),
_ => Vec::new(),
}
}
@ -269,7 +284,9 @@ pub fn versions_total(query: &UseQuery<PackageVersionsQuery>) -> usize {
let reader = query.read();
match &*reader.state() {
QueryStateData::Settled { res: Ok(page), .. } => page.total,
QueryStateData::Loading { res: Some(Ok(page)) } => page.total,
QueryStateData::Loading {
res: Some(Ok(page)),
} => page.total,
_ => 0,
}
}
@ -315,7 +332,9 @@ pub fn category_list(query: &UseQuery<PackageCategoriesQuery>) -> Vec<String> {
let reader = query.read();
match &*reader.state() {
QueryStateData::Settled { res: Ok(list), .. } => list.clone(),
QueryStateData::Loading { res: Some(Ok(list)) } => list.clone(),
QueryStateData::Loading {
res: Some(Ok(list)),
} => list.clone(),
_ => Vec::new(),
}
}

View file

@ -39,7 +39,10 @@ impl QueryCapability for FetchPlayerProfileQuery {
// }
}
pub fn use_player_profile(uuid: impl Into<String>, access_token: Option<impl Into<String>>) -> UseQuery<FetchPlayerProfileQuery> {
pub fn use_player_profile(
uuid: impl Into<String>,
access_token: Option<impl Into<String>>,
) -> UseQuery<FetchPlayerProfileQuery> {
use_query(
Query::new(
PlayerProfileQueryKeys {

View file

@ -12,7 +12,6 @@ use tokio::sync::Semaphore;
static LOCAL_IMAGE_SEMAPHORE: OnceLock<Arc<Semaphore>> = OnceLock::new();
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct ClusterScreenshotsKeys {
pub cluster_id: i64,
@ -96,7 +95,6 @@ pub fn try_cluster_screenshots(
}
}
pub async fn invalidate_screenshots_queries() {
QueriesStorage::<ClusterScreenshotsQuery>::try_invalidate_all().await;
QueriesStorage::<LocalImageQuery>::try_invalidate_all().await;

View file

@ -42,4 +42,4 @@ fn java_string_hash(s: &str) -> i32 {
h = h.wrapping_mul(31).wrapping_add(c as i32);
}
h
}
}

View file

@ -0,0 +1,61 @@
use freya::query::{Query, QueryCapability, QueryStateData, UseQuery, use_query};
use oneclient_core::{LauncherError, LauncherState, TermsDocument, fetch_terms};
use crate::hooks::use_settings_snapshot;
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct TermsKeys {
pub meta_url_base: String,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct TermsQuery;
impl QueryCapability for TermsQuery {
type Ok = TermsDocument;
type Err = LauncherError;
type Keys = TermsKeys;
async fn run(&self, _keys: &Self::Keys) -> Result<Self::Ok, Self::Err> {
let state = LauncherState::get()?;
fetch_terms(&state.services).await
}
}
pub fn use_terms() -> UseQuery<TermsQuery> {
let settings = use_settings_snapshot().settings;
let meta_url_base = settings
.custom_meta_url_base
.clone()
.unwrap_or_default()
.trim()
.to_string();
use_query(Query::new(TermsKeys { meta_url_base }, TermsQuery))
}
pub fn terms_document(query: &UseQuery<TermsQuery>) -> Option<TermsDocument> {
match &*query.read().state() {
QueryStateData::Settled {
res: Ok(document), ..
} => Some(document.clone()),
QueryStateData::Loading {
res: Some(Ok(document)),
} => Some(document.clone()),
_ => None,
}
}
pub fn terms_error(query: &UseQuery<TermsQuery>) -> Option<String> {
match &*query.read().state() {
QueryStateData::Settled { res: Err(err), .. } => Some(err.to_string()),
_ => None,
}
}
pub fn terms_is_loading(query: &UseQuery<TermsQuery>) -> bool {
matches!(
&*query.read().state(),
QueryStateData::Pending | QueryStateData::Loading { res: None }
)
}

View file

@ -1,6 +1,6 @@
use freya::query::QueryStateData;
use oneclient_core::packages::domain::GameLoader;
use oneclient_core::VersionMetadata;
use oneclient_core::packages::domain::GameLoader;
use super::use_versions;
@ -46,7 +46,9 @@ pub fn use_version_metadata(
let state = reader.state();
let list = match &*state {
QueryStateData::Settled { res: Ok(list), .. } => list,
QueryStateData::Loading { res: Some(Ok(list)) } => list,
QueryStateData::Loading {
res: Some(Ok(list)),
} => list,
_ => return None,
};

View file

@ -69,7 +69,9 @@ pub fn loader_versions(query: &UseQuery<LoaderVersionsQuery>) -> Vec<String> {
let reader = query.read();
match &*reader.state() {
QueryStateData::Settled { res: Ok(list), .. } => list.clone(),
QueryStateData::Loading { res: Some(Ok(list)) } => list.clone(),
QueryStateData::Loading {
res: Some(Ok(list)),
} => list.clone(),
_ => Vec::new(),
}
}

View file

@ -5,38 +5,38 @@ use super::{use_dispatch, use_settings_snapshot};
#[derive(Clone, Copy)]
pub struct PersistedView {
pub layout: State<ViewLayout>,
pub sort: State<Option<String>>,
pub layout: State<ViewLayout>,
pub sort: State<Option<String>>,
}
pub fn use_view_state(key: &str) -> PersistedView {
let key = key.to_string();
let settings = use_settings_snapshot().settings;
let dispatch = use_dispatch();
let key = key.to_string();
let settings = use_settings_snapshot().settings;
let dispatch = use_dispatch();
let initial = settings.view_state(&key);
let init_layout = initial.layout;
let init_sort = initial.sort;
let layout = use_state(move || init_layout);
let sort = use_state(move || init_sort.clone());
let initial = settings.view_state(&key);
let init_layout = initial.layout;
let init_sort = initial.sort;
let layout = use_state(move || init_layout);
let sort = use_state(move || init_sort.clone());
let mut first = use_state(|| true);
use_side_effect(move || {
let next = ViewState {
layout: *layout.read(),
sort: sort.read().clone(),
};
if *first.peek() {
first.set(false);
return;
}
if settings.view_state(&key) == next {
return;
}
let mut updated = settings.clone();
updated.set_view_state(key.clone(), next);
dispatch.set_settings(updated);
});
let mut first = use_state(|| true);
use_side_effect(move || {
let next = ViewState {
layout: *layout.read(),
sort: sort.read().clone(),
};
if *first.peek() {
first.set(false);
return;
}
if settings.view_state(&key) == next {
return;
}
let mut updated = settings.clone();
updated.set_view_state(key.clone(), next);
dispatch.set_settings(updated);
});
PersistedView { layout, sort }
PersistedView { layout, sort }
}

View file

@ -9,8 +9,8 @@ use skia_safe::sampling_options::CubicResampler;
use crate::Route;
use crate::components::Button;
use crate::layout::AnimatedAppOutlet;
use crate::components::{AppNavbar, DynamicArt, Icon, IconType, OverlayPopup, ScrollArea};
use crate::layout::AnimatedAppOutlet;
use crate::theme;
use crate::use_settings_snapshot;
use oneclient_db::models::ClusterId;

View file

@ -33,7 +33,7 @@ impl ClusterViewShellTab {
match self {
Self::Overview => "Overview",
Self::Logs => "Logs",
Self::GameLog => "Game Log",
Self::GameLog => "Live Log",
Self::Screenshots => "Screenshots",
Self::Mods => "Mods",
Self::Shaders => "Shaders",

View file

@ -6,8 +6,8 @@ mod root_layout;
mod settings_shell;
pub use animated_outlet::AnimatedAppOutlet;
pub use app_shell::{AppShell, HOME_BACKGROUND_ASSET};
pub(crate) use app_shell::gradient_overlay_radial;
pub use app_shell::{AppShell, HOME_BACKGROUND_ASSET};
pub use cluster_shell::{ClusterShell, cluster_content};
pub use onboarding_shell::OnboardingShell;
pub use root_layout::RootLayout;

View file

@ -198,7 +198,10 @@ const SEARCH_INDEX: &[SearchItem] = &[
icon: IconType::Globe01,
title: "Language",
description: "Choose the launcher language.",
keywords: &["language", "locale", "english", "spanish", "french", "german", "russian", "japanese", "chinese"],
keywords: &[
"language", "locale", "english", "spanish", "french", "german", "russian", "japanese",
"chinese",
],
route: Route::SettingsLanguage {},
},
// Developer
@ -332,10 +335,7 @@ const GROUPS: &[SettingsGroup] = &[
},
SettingsGroup {
label: "ABOUT",
tabs: &[
SettingsTab::DeveloperOptions,
SettingsTab::Changelog,
],
tabs: &[SettingsTab::DeveloperOptions, SettingsTab::Changelog],
},
];
@ -490,18 +490,20 @@ fn search_results(query: String, mut search: State<String>) -> Vec<Element> {
matches.truncate(SEARCH_RESULTS_MAX);
if matches.is_empty() {
return vec![rect()
.width(Size::fill())
.padding(Gaps::new_symmetric(10., 14.))
.corner_radius(CornerRadius::new_all(12.))
.background(colors::page_elevated())
.child(
label()
.text("No settings matched your search.")
.font_size(12.)
.color(colors::fg_secondary()),
)
.into_element()];
return vec![
rect()
.width(Size::fill())
.padding(Gaps::new_symmetric(10., 14.))
.corner_radius(CornerRadius::new_all(12.))
.background(colors::page_elevated())
.child(
label()
.text("No settings matched your search.")
.font_size(12.)
.color(colors::fg_secondary()),
)
.into_element(),
];
}
let mut out: Vec<Element> = Vec::with_capacity(matches.len() + 1);
@ -696,11 +698,11 @@ impl Component for SidebarInfo {
.spacing(4.)
.font_size(12.)
.color(colors::fg_secondary())
.children(items.into_iter().map(|item| {
label()
.text(item)
.into_element()
}))
.children(
items
.into_iter()
.map(|item| label().text(item).into_element()),
)
.on_pointer_enter(|_| Cursor::set(CursorIcon::Pointer))
.on_pointer_leave(|_| Cursor::set(CursorIcon::default()))
.on_press(copy_to_clipboard)

View file

@ -12,8 +12,8 @@ mod notifications;
pub mod platform;
mod routes;
pub mod theme;
pub mod updater;
mod ui;
pub mod updater;
pub(crate) mod utils;
mod view;

View file

@ -39,12 +39,15 @@ fn main() {
let _tokio_guard = rt.enter();
let settings = rt.block_on(oneclient_core::settings::store::load_settings(None));
if settings.log_debug {
oneclient_core::logger::init_debug()
} else {
oneclient_core::logger::init()
}.expect("Failed to initialize logger");
}
.expect("Failed to initialize logger");
let _sentry_guard = oneclient_core::reporting::init(settings.crash_reporting);
let (bridge, handle): (OneClientBridge, CoreBridgeHandle) = OneClientBridge::new();
handle.spawn_runtime();
@ -67,7 +70,7 @@ fn main() {
std::env::var("ONECLIENT_GPU_CACHE_MB")
.ok()
.and_then(|v| v.parse::<usize>().ok())
.unwrap_or(32)
.unwrap_or(32),
)
.with_default_font(theme::DEFAULT_FONT);

View file

@ -17,13 +17,13 @@ use crate::view::{
ClusterShaders, ClusterTextures, ProcessLogs,
},
settings::{
SettingsApis, SettingsAppearance, SettingsChangelog, SettingsDeveloper,
SettingsJava, SettingsLanguage, SettingsLauncher, SettingsMinecraft,
SettingsApis, SettingsAppearance, SettingsChangelog, SettingsDeveloper, SettingsJava,
SettingsLanguage, SettingsLauncher, SettingsMinecraft,
},
},
onboarding::{
OnboardingAccount, OnboardingBundles, OnboardingDownloading, OnboardingLanguage,
OnboardingMigration, OnboardingPreferences, OnboardingWelcome,
OnboardingMigration, OnboardingPreferences, OnboardingTerms, OnboardingWelcome,
},
};
@ -37,6 +37,8 @@ pub enum Route {
#[layout(OnboardingShell)]
#[route("/onboarding")]
OnboardingWelcome {},
#[route("/onboarding/terms")]
OnboardingTerms {},
#[route("/onboarding/migration")]
OnboardingMigration {},
#[route("/onboarding/language")]
@ -120,6 +122,7 @@ impl Route {
match self {
Route::Startup { .. } => "Startup".to_string(),
Route::OnboardingWelcome { .. } => "Welcome".to_string(),
Route::OnboardingTerms { .. } => "Terms & Privacy".to_string(),
Route::OnboardingMigration { .. } => "Migration".to_string(),
Route::OnboardingLanguage { .. } => "Language".to_string(),
Route::OnboardingAccount { .. } => "Account".to_string(),

View file

@ -149,4 +149,4 @@ pub fn selection_bg() -> Color {
pub fn toast_action() -> Color {
Color::from_rgb(155, 161, 166)
}
}

View file

@ -17,14 +17,12 @@ pub fn border_all(width: f32) -> Border {
}
pub fn border_all_color(width: f32, color: Color) -> Border {
Border::new()
.fill(color)
.width(BorderWidth {
top: width,
right: width,
bottom: width,
left: width,
})
Border::new().fill(color).width(BorderWidth {
top: width,
right: width,
bottom: width,
left: width,
})
}
pub fn border_left(width: f32) -> Border {
@ -79,4 +77,4 @@ pub fn entrance_motion_layer(
.child(child),
)
.into_element()
}
}

View file

@ -55,10 +55,7 @@ fn check_for_update() -> anyhow::Result<Option<Update>> {
Ok(check_update(current, config)?)
}
async fn download_and_install(
update: Update,
notifier: NotificationService,
) -> anyhow::Result<()> {
async fn download_and_install(update: Update, notifier: NotificationService) -> anyhow::Result<()> {
let progress_id = Uuid::new_v4();
let version = update.version.clone();
let label = format!("Downloading OneClient {version}");

View file

@ -16,7 +16,10 @@ pub fn group_clusters_by_major(clusters: &[Cluster]) -> ClusterGroups {
let Some(parsed) = parse_mc_version(&cluster.mc_version) else {
continue;
};
groups.entry(parsed.major).or_default().push(cluster.clone());
groups
.entry(parsed.major)
.or_default()
.push(cluster.clone());
}
for list in groups.values_mut() {
@ -82,9 +85,7 @@ pub fn resolve_cluster(
.iter()
.find(|cluster| {
let version_ok = minor.is_none_or(|minor| {
parse_mc_version(&cluster.mc_version)
.and_then(|parsed| parsed.minor)
== Some(minor)
parse_mc_version(&cluster.mc_version).and_then(|parsed| parsed.minor) == Some(minor)
});
let loader_ok = loader.is_none_or(|loader| cluster.mc_loader == loader);
version_ok && loader_ok
@ -241,21 +242,22 @@ fn version_sort_key(cluster: &Cluster) -> (u32, u32) {
.unwrap_or((0, 0))
}
#[cfg(not(target_os = "linux"))]
pub fn is_wayland() -> bool {
false
false
}
#[cfg(target_os = "linux")]
pub fn is_wayland() -> bool {
static IS_WAYLAND: OnceLock<bool> = OnceLock::new();
static IS_WAYLAND: OnceLock<bool> = OnceLock::new();
*IS_WAYLAND.get_or_init(|| {
if cfg!(target_os = "linux") {
std::env::var("XDG_SESSION_TYPE").map(|v| v == "wayland").unwrap_or(false)
} else {
false
}
})
}
*IS_WAYLAND.get_or_init(|| {
if cfg!(target_os = "linux") {
std::env::var("XDG_SESSION_TYPE")
.map(|v| v == "wayland")
.unwrap_or(false)
} else {
false
}
})
}

View file

@ -1,7 +1,9 @@
use chrono::Utc;
use chrono::{DateTime, Utc};
use freya::animation::{AnimNum, Function, OnFinish, use_animation};
use freya::prelude::*;
use freya::query::{MutationCapability, MutationStateData, UseMutation};
use oneclient_core::auth::{AccountKind, MinecraftAccount};
use uuid::Uuid;
use crate::components::{
Avatar, Button, Icon, IconType, OverlayPopup, PlayerModel, ScrollArea, TextInput,
@ -74,13 +76,17 @@ impl Component for Accounts {
let mut rows: Vec<Element> = accounts
.iter()
.map(|account| {
account_row(
account,
Some(account.id) == default_id,
AccountRow {
id: account.id,
username: account.username.clone(),
kind: account.kind,
expires: account.expires,
is_default: Some(account.id) == default_id,
set_default,
remove,
refresh,
)
}
.into_element()
})
.collect();
if rows.is_empty() {
@ -339,113 +345,182 @@ fn hint_line(icon: IconType, text: String, color: Color) -> impl IntoElement {
.into_element()
}
fn account_row(
account: &MinecraftAccount,
const REFRESH_SPIN_TIME: u64 = 800;
const REFRESHING_OPACITY: f32 = 0.7;
struct AccountRow {
id: Uuid,
username: String,
kind: AccountKind,
expires: DateTime<Utc>,
is_default: bool,
set_default: crate::hooks::UseSetDefaultAccount,
remove: crate::hooks::UseRemoveAccount,
refresh: crate::hooks::UseRefreshAccount,
) -> Element {
let id = account.id;
let is_microsoft = account.is_microsoft();
let expired = is_microsoft && account.expires <= Utc::now();
}
let border_color = if expired {
colors::danger()
} else if is_default {
colors::brand()
} else {
colors::component_border()
};
impl PartialEq for AccountRow {
fn eq(&self, other: &Self) -> bool {
self.id == other.id
&& self.username == other.username
&& self.kind == other.kind
&& self.expires == other.expires
&& self.is_default == other.is_default
}
}
rect()
.horizontal()
.width(Size::fill())
.cross_align(Alignment::Center)
.content(Content::Flex)
.spacing(16.)
.padding(Gaps::new_all(12.))
.corner_radius(CornerRadius::new_all(12.))
.background(colors::page_elevated())
.border(border_all_color(1., border_color))
.a11y_role(AccessibilityRole::Button)
.maybe(!is_default, |el| {
el.on_press(move |_| set_default.mutate(SetDefaultAccountKeys { id: Some(id) }))
})
.child(
Avatar::new(id.to_string())
.width(Size::px(40.))
.height(Size::px(40.)),
)
.child(
rect()
.vertical()
.width(Size::flex(1.0))
.spacing(4.)
.child(
rect()
.horizontal()
.cross_align(Alignment::Center)
.spacing(8.)
.child(
label()
.text(account.username.clone())
.font_size(16.)
.font_weight(FontWeight::MEDIUM)
.color(colors::fg_primary()),
)
.child(kind_badge(account.kind))
.maybe_child(is_default.then(default_badge))
.maybe_child(expired.then(expired_badge)),
)
.child(
label()
.text(id.to_string())
.font_size(11.)
.color(colors::fg_secondary()),
),
)
.maybe_child(is_microsoft.then(|| {
Button::new()
.ghost()
.icon()
.on_press(move |e: Event<PressEventData>| {
e.stop_propagation();
refresh.mutate(RefreshAccountKeys { id });
})
.child(
Icon::new(IconType::RefreshCw01)
.size(18.)
.color(if expired {
colors::danger()
} else {
colors::fg_secondary()
}),
)
.into_element()
}))
.child(
Button::new().ghost().icon().enabled(false).child(
Icon::new(IconType::Pencil01)
.size(18.)
.color(colors::fg_secondary()),
),
)
.child(
Button::new()
.ghost()
.icon()
.on_press(move |e: Event<PressEventData>| {
e.stop_propagation();
remove.mutate(RemoveAccountKeys { id });
})
.child(
Icon::new(IconType::Trash01)
impl Component for AccountRow {
fn render_key(&self) -> DiffKey {
DiffKey::from(&self.id)
}
fn render(&self) -> impl IntoElement {
let id = self.id;
let is_default = self.is_default;
let set_default = self.set_default;
let remove = self.remove;
let refresh = self.refresh;
let is_microsoft = self.kind == AccountKind::Microsoft;
let expired = is_microsoft && self.expires <= Utc::now();
let mut refreshing = use_state(|| false);
let is_refreshing = *refreshing.read();
let spin = use_animation(|conf| {
conf.on_finish(OnFinish::restart());
AnimNum::new(0., 360.)
.time(REFRESH_SPIN_TIME)
.function(Function::Linear)
});
use_side_effect_with_deps(&is_refreshing, move |&is_refreshing| {
let mut spin = spin;
if is_refreshing {
spin.start();
} else {
spin.reset();
}
});
let rotation = if is_refreshing {
spin.get().value()
} else {
0.
};
let border_color = if expired {
colors::danger()
} else if is_default {
colors::brand()
} else {
colors::component_border()
};
rect()
.horizontal()
.width(Size::fill())
.cross_align(Alignment::Center)
.content(Content::Flex)
.spacing(16.)
.padding(Gaps::new_all(12.))
.corner_radius(CornerRadius::new_all(12.))
.background(colors::page_elevated())
.border(border_all_color(1., border_color))
.opacity(if is_refreshing {
REFRESHING_OPACITY
} else {
1.
})
.a11y_role(AccessibilityRole::Button)
.maybe(!is_default, |el| {
el.on_press(move |_| set_default.mutate(SetDefaultAccountKeys { id: Some(id) }))
})
.child(
Avatar::new(id.to_string())
.width(Size::px(40.))
.height(Size::px(40.)),
)
.child(
rect()
.vertical()
.width(Size::flex(1.0))
.spacing(4.)
.child(
rect()
.horizontal()
.cross_align(Alignment::Center)
.spacing(8.)
.child(
label()
.text(self.username.clone())
.font_size(16.)
.font_weight(FontWeight::MEDIUM)
.color(colors::fg_primary()),
)
.child(kind_badge(self.kind))
.maybe_child(is_default.then(default_badge))
.maybe_child(expired.then(expired_badge)),
)
.child(
label()
.text(id.to_string())
.font_size(11.)
.color(colors::fg_secondary()),
),
)
.maybe_child(is_microsoft.then(|| {
Button::new()
.ghost()
.icon()
.on_press(move |e: Event<PressEventData>| {
e.stop_propagation();
if *refreshing.peek() {
return;
}
refreshing.set(true);
spawn(async move {
refresh.mutate_async(RefreshAccountKeys { id }).await;
refreshing.set(false);
});
})
.child(
rect().rotate(rotation).child(
Icon::new(IconType::RefreshCw01)
.size(18.)
.color(if expired {
colors::danger()
} else {
colors::fg_secondary()
}),
),
)
.into_element()
}))
.child(
Button::new().ghost().icon().enabled(false).child(
Icon::new(IconType::Pencil01)
.size(18.)
.color(colors::fg_secondary()),
),
)
.into_element()
)
.child(
Button::new()
.ghost()
.icon()
.on_press(move |e: Event<PressEventData>| {
e.stop_propagation();
remove.mutate(RemoveAccountKeys { id });
})
.child(
Icon::new(IconType::Trash01)
.size(18.)
.color(colors::fg_secondary()),
),
)
.into_element()
}
}
fn kind_badge(kind: AccountKind) -> impl IntoElement {

View file

@ -62,7 +62,10 @@ impl Component for WhenChart {
)
}
WhenMode::Hour => {
let hour = self.peak_hour.map(format_hour).unwrap_or_else(|| "—".to_string());
let hour = self
.peak_hour
.map(format_hour)
.unwrap_or_else(|| "—".to_string());
(
format!("Peak around {hour}"),
BarChart::new(self.per_hour.clone(), hour_labels())
@ -148,7 +151,12 @@ impl Component for DailyChart {
let win_total: i64 = values.iter().sum();
let subtitle = match (slice.first(), slice.last()) {
(Some((a, _)), Some((b, _))) => {
format!("{} · {} – {}", format_duration_hm(win_total), format_day(*a), format_day(*b))
format!(
"{} · {} – {}",
format_duration_hm(win_total),
format_day(*a),
format_day(*b)
)
}
_ => format_duration_hm(win_total),
};
@ -159,24 +167,16 @@ impl Component for DailyChart {
.horizontal()
.cross_align(Alignment::Center)
.spacing(8.)
.child(nav_button(
IconType::ChevronsLeft,
can_older,
move |_| {
if can_older {
*offset.write() = off + 1;
}
},
))
.child(nav_button(
IconType::ChevronsRight,
can_newer,
move |_| {
if can_newer {
*offset.write() = off - 1;
}
},
))
.child(nav_button(IconType::ChevronsLeft, can_older, move |_| {
if can_older {
*offset.write() = off + 1;
}
}))
.child(nav_button(IconType::ChevronsRight, can_newer, move |_| {
if can_newer {
*offset.write() = off - 1;
}
}))
.child(
SegmentedControl::new(range)
.height(30.)
@ -272,6 +272,12 @@ fn weekday_labels() -> Vec<String> {
fn hour_labels() -> Vec<String> {
(0..24)
.map(|h| if h % 6 == 0 { format_hour(h) } else { String::new() })
.map(|h| {
if h % 6 == 0 {
format_hour(h)
} else {
String::new()
}
})
.collect()
}

View file

@ -45,11 +45,7 @@ fn charts_grid(cards: Vec<Element>) -> Element {
let mut grid = rect().vertical().width(Size::fill()).spacing(16.);
for pair in cards.chunks(2) {
if pair.len() == 1 {
grid = grid.child(
rect()
.width(Size::fill())
.child(pair[0].clone()),
);
grid = grid.child(rect().width(Size::fill()).child(pair[0].clone()));
continue;
}

View file

@ -12,7 +12,12 @@ use super::{card, card_header};
pub(super) fn servers_section(servers: &[ServerStat]) -> Element {
let total_joins: i64 = servers.iter().map(|s| s.joins).sum();
let max_secs = servers.iter().map(|s| s.total_secs).max().unwrap_or(0).max(1);
let max_secs = servers
.iter()
.map(|s| s.total_secs)
.max()
.unwrap_or(0)
.max(1);
let mut list = rect().vertical().width(Size::fill()).spacing(4.);
for server in servers.iter().take(10) {
@ -155,9 +160,12 @@ impl Component for ServerIcon {
let reader = query.read();
let loaded = match (&self.url, &*reader.state()) {
(Some(url), QueryStateData::Settled { res: Ok(bytes), .. })
| (Some(url), QueryStateData::Loading { res: Some(Ok(bytes)) }) => {
Some((url.clone(), bytes.clone()))
}
| (
Some(url),
QueryStateData::Loading {
res: Some(Ok(bytes)),
},
) => Some((url.clone(), bytes.clone())),
_ => None,
};
@ -185,7 +193,11 @@ fn server_icon_placeholder(is_ip: bool, size: f32) -> Element {
.height(Size::px(size))
.corner_radius(CornerRadius::new_all(6.))
.background(colors::component_bg())
.child(Icon::new(icon).size(size * 0.5).color(colors::fg_secondary()))
.child(
Icon::new(icon)
.size(size * 0.5)
.color(colors::fg_secondary()),
)
.into_element()
}

View file

@ -3,9 +3,9 @@ use freya::prelude::*;
use oneclient_core::game::{Analytics, Persona};
use crate::components::{Icon, IconType};
use crate::utils::format_duration_hm;
use crate::theme::colors;
use crate::ui::border_all_color;
use crate::utils::format_duration_hm;
use super::card;
@ -19,10 +19,22 @@ pub(super) fn tiles_row(analytics: &Analytics) -> Element {
let total_joins: i64 = analytics.servers.iter().map(|s| s.joins).sum();
let mut tiles = vec![
stat_tile(IconType::ClockRewind, "Total playtime", format_duration_hm(stats.total_secs)),
stat_tile(
IconType::ClockRewind,
"Total playtime",
format_duration_hm(stats.total_secs),
),
stat_tile(IconType::Play, "Sessions", stats.session_count.to_string()),
stat_tile(IconType::Calendar, "Avg / session", format_duration_hm(avg_session)),
stat_tile(IconType::Rocket02, "Active days", stats.active_days.to_string()),
stat_tile(
IconType::Calendar,
"Avg / session",
format_duration_hm(avg_session),
),
stat_tile(
IconType::Rocket02,
"Active days",
stats.active_days.to_string(),
),
];
if !analytics.servers.is_empty() {
tiles.push(stat_tile(
@ -97,7 +109,11 @@ fn persona_card(persona: Persona) -> Element {
.corner_radius(CornerRadius::new_all(22.))
.background(colors::brand().with_a(40))
.center()
.child(Icon::new(persona_icon(persona)).size(22.).color(colors::brand())),
.child(
Icon::new(persona_icon(persona))
.size(22.)
.color(colors::brand()),
),
)
.child(
rect()

View file

@ -1,17 +1,19 @@
use super::*;
use freya::router::RouterContext;
use oneclient_core::packages::types::ProjectSummary;
use oneclient_core::packages::ProviderId;
use oneclient_core::packages::types::ProjectSummary;
use crate::components::{
Icon, IconType,
};
use crate::components::{Icon, IconType};
use crate::routes::Route;
use crate::theme::colors;
use crate::ui::border_all_color;
pub(super) fn grid_row(row: Vec<ProjectSummary>, cluster_id: i64, package_type: &str) -> impl IntoElement {
pub(super) fn grid_row(
row: Vec<ProjectSummary>,
cluster_id: i64,
package_type: &str,
) -> impl IntoElement {
let package_type = package_type.to_string();
let fill = GRID_COLUMNS - row.len();
@ -154,7 +156,11 @@ impl Component for PackageCard {
}
}
pub(super) fn list_row(item: ProjectSummary, cluster_id: i64, package_type: &str) -> impl IntoElement {
pub(super) fn list_row(
item: ProjectSummary,
cluster_id: i64,
package_type: &str,
) -> impl IntoElement {
ListRow {
item,
cluster_id,
@ -185,69 +191,69 @@ impl Component for ListRow {
Cursor::set(CursorIcon::default());
});
rect()
.horizontal()
.width(Size::fill())
.height(Size::px(LIST_ROW_H))
.cross_align(Alignment::Center)
.spacing(12.)
.padding(Gaps::new_all(16.))
.corner_radius(CornerRadius::new_all(10.))
.background(CARD_BG)
.border(border_all_color(
1.,
if focused {
colors::brand()
} else {
colors::component_border()
},
))
.overflow(Overflow::Clip)
.content(Content::Flex)
.a11y_id(a11y_id)
.a11y_focusable(true)
.a11y_role(AccessibilityRole::Button)
.on_pointer_enter(|_| Cursor::set(CursorIcon::Pointer))
.on_pointer_leave(|_| Cursor::set(CursorIcon::default()))
.on_all_press(move |_| open_package(cluster_id, &package_type, provider, &id))
.child(Thumbnail::new(item.icon_url.clone(), 48.).radius(8.))
.child(
rect()
.vertical()
.width(Size::flex(1.0))
.spacing(3.)
.child(
rect()
.horizontal()
.cross_align(Alignment::Center)
.spacing(6.)
.child(
label()
.text(item.name.clone())
.font_size(15.)
.font_weight(FontWeight::MEDIUM)
.max_lines(1)
.color(CARD_NAME),
)
.child(
label()
.text(format!("by {}", item.author))
.font_size(10.)
.max_lines(1)
.color(colors::fg_secondary()),
)
.child(Icon::new(item.provider).size(12.)),
)
.child(
label()
.text(item.summary.clone())
.font_size(11.)
.max_lines(2)
.width(Size::fill())
.color(colors::fg_secondary()),
),
)
.child(downloads_row(item.downloads))
rect()
.horizontal()
.width(Size::fill())
.height(Size::px(LIST_ROW_H))
.cross_align(Alignment::Center)
.spacing(12.)
.padding(Gaps::new_all(16.))
.corner_radius(CornerRadius::new_all(10.))
.background(CARD_BG)
.border(border_all_color(
1.,
if focused {
colors::brand()
} else {
colors::component_border()
},
))
.overflow(Overflow::Clip)
.content(Content::Flex)
.a11y_id(a11y_id)
.a11y_focusable(true)
.a11y_role(AccessibilityRole::Button)
.on_pointer_enter(|_| Cursor::set(CursorIcon::Pointer))
.on_pointer_leave(|_| Cursor::set(CursorIcon::default()))
.on_all_press(move |_| open_package(cluster_id, &package_type, provider, &id))
.child(Thumbnail::new(item.icon_url.clone(), 48.).radius(8.))
.child(
rect()
.vertical()
.width(Size::flex(1.0))
.spacing(3.)
.child(
rect()
.horizontal()
.cross_align(Alignment::Center)
.spacing(6.)
.child(
label()
.text(item.name.clone())
.font_size(15.)
.font_weight(FontWeight::MEDIUM)
.max_lines(1)
.color(CARD_NAME),
)
.child(
label()
.text(format!("by {}", item.author))
.font_size(10.)
.max_lines(1)
.color(colors::fg_secondary()),
)
.child(Icon::new(item.provider).size(12.)),
)
.child(
label()
.text(item.summary.clone())
.font_size(11.)
.max_lines(2)
.width(Size::fill())
.color(colors::fg_secondary()),
),
)
.child(downloads_row(item.downloads))
}
}

View file

@ -1,6 +1,5 @@
use freya::animation::{
AnimNum, Ease, Function, OnChange, OnCreation,
use_animation_with_dependencies,
AnimNum, Ease, Function, OnChange, OnCreation, use_animation_with_dependencies,
};
use freya::prelude::*;
use oneclient_core::packages::types::ProjectSummary;
@ -174,7 +173,9 @@ impl Component for Browser {
match mode {
ViewLayout::Grid => {
let rows = BROWSE_PAGE_SIZE.div_ceil(GRID_COLUMNS);
sa.lazy(rows, CARD_H, GRID_SPACING, |_| skeleton_grid_row().into_element())
sa.lazy(rows, CARD_H, GRID_SPACING, |_| {
skeleton_grid_row().into_element()
})
}
ViewLayout::List => sa.lazy(BROWSE_PAGE_SIZE, LIST_ROW_H, LIST_SPACING, |_| {
SkeletonListRow.into_element()
@ -289,4 +290,3 @@ fn controls(
),
)
}

View file

@ -120,4 +120,3 @@ impl Component for CategoryRow {
)
}
}

View file

@ -1,8 +1,6 @@
use super::*;
use freya::animation::{
AnimNum, Ease, Function, OnCreation, OnFinish, use_animation,
};
use freya::animation::{AnimNum, Ease, Function, OnCreation, OnFinish, use_animation};
use crate::theme::colors;
use crate::ui::border_all_color;

View file

@ -3,8 +3,8 @@ use oneclient_core::packages::{ContentType, ProviderId};
use crate::components::ScrollArea;
use crate::hooks::{
content_type_for_slug, project_detail, use_browser_compat, use_dispatch,
use_link_confirm, use_package_project, use_package_versions, version_list, versions_total,
content_type_for_slug, project_detail, use_browser_compat, use_dispatch, use_link_confirm,
use_package_project, use_package_versions, version_list, versions_total,
};
use crate::theme::colors;
use crate::ui::border_all_color;
@ -205,4 +205,3 @@ impl Component for BrowserPackage {
.into_element()
}
}

View file

@ -1,14 +1,14 @@
use super::*;
use freya::query::QueryStateData;
use oneclient_core::packages::ProviderId;
use oneclient_core::packages::types::{
GalleryImage, PackageBody, ProjectDetail, ReleaseType, VersionSummary,
};
use oneclient_core::packages::ProviderId;
use crate::BridgeDispatch;
use crate::components::{Button, Icon, IconType, Segment, SegmentedControl};
use crate::hooks::{use_cached_image, VERSIONS_PAGE_SIZE};
use crate::hooks::{VERSIONS_PAGE_SIZE, use_cached_image};
use crate::theme::colors;
use crate::ui::border_all_color;
use crate::utils::format_size;
@ -16,7 +16,6 @@ use crate::utils::format_size;
const GALLERY_IMAGE_H: f32 = 360.;
const GALLERY_EDGE: u32 = 1024;
pub(super) fn loading_body() -> impl IntoElement {
rect()
.width(Size::fill())
@ -356,4 +355,3 @@ fn release_badge(release_type: ReleaseType) -> impl IntoElement {
.color(color),
)
}

View file

@ -1,16 +1,13 @@
use super::*;
use oneclient_core::packages::types::{
ProjectDetail, ProjectMember,
};
use oneclient_core::packages::ProviderId;
use oneclient_core::packages::types::{ProjectDetail, ProjectMember};
use crate::BridgeDispatch;
use crate::components::{Button, Icon, IconType};
use crate::theme::colors;
use crate::ui::border_all_color;
pub(super) fn sidebar(
project: Option<ProjectDetail>,
latest_version: Option<String>,

View file

@ -1,4 +1,3 @@
use freya::prelude::*;
use oneclient_core::Patch;
use oneclient_core::java::JavaRuntime;
@ -6,7 +5,8 @@ use oneclient_core::packages::domain::GameLoader;
use oneclient_core::settings::{GameSettingsProfile, ProfileUpdate, Resolution};
use crate::components::{
Button, Dropdown, Icon, IconType, ScrollArea, TextInput, toggle, toggle_controlled, validate_number
Button, Dropdown, Icon, IconType, ScrollArea, TextInput, toggle, toggle_controlled,
validate_number,
};
use crate::hooks::{
ClusterAction, java_runtimes, loader_versions, try_game_profile, use_cluster_mutation,
@ -37,7 +37,10 @@ impl Component for ClusterSettings {
.as_ref()
.map(|c| c.mc_version.clone())
.unwrap_or_default();
let loader = cluster.as_ref().map(|c| c.mc_loader).unwrap_or(GameLoader::Fabric);
let loader = cluster
.as_ref()
.map(|c| c.mc_loader)
.unwrap_or(GameLoader::Fabric);
let versions_query = use_loader_versions(mc_version, loader);
let runtimes_query = use_java_runtimes();
@ -149,13 +152,11 @@ fn reset_button(overridden: bool, on_reset: EventHandler<()>) -> impl IntoElemen
Button::new()
.small()
.ghost()
.icon()
.ghost()
.icon()
.corner_radius(CornerRadius::new_all(7.))
.maybe(overridden, |el| {
el.on_press(move |_| on_reset.call(()))
})
.enabled(overridden)
.maybe(overridden, |el| el.on_press(move |_| on_reset.call(())))
.enabled(overridden)
.child(Icon::new(IconType::RefreshCcw02).size(15.).color(color))
.into_element()
}
@ -394,7 +395,11 @@ impl Component for ResolutionRow {
.text_align(TextAlign::Center)
.width(Size::px(70.)),
)
.child(Icon::new(IconType::X).size(14.).color(colors::fg_secondary()))
.child(
Icon::new(IconType::X)
.size(14.)
.color(colors::fg_secondary()),
)
.child(
TextInput::new(height)
.placeholder("480")
@ -527,7 +532,12 @@ impl Component for TextRow {
.placeholder(placeholder)
.width(Size::px(220.));
settings_row(icon, title, description, override_cell(control, overridden, on_reset))
settings_row(
icon,
title,
description,
override_cell(control, overridden, on_reset),
)
}
}
@ -564,8 +574,7 @@ impl Component for JavaRow {
.map(runtime_label)
.unwrap_or_else(|| "Automatic".into());
let paths: Vec<String> =
runtimes.iter().map(|r| r.absolute_path.clone()).collect();
let paths: Vec<String> = runtimes.iter().map(|r| r.absolute_path.clone()).collect();
let dispatch = dispatch.clone();
Dropdown::new(selected, options)
@ -641,7 +650,10 @@ impl Component for LoaderRow {
settings_row(
IconType::Rocket02,
"Loader Version",
format!("The {} loader version used to launch this cluster.", self.loader),
format!(
"The {} loader version used to launch this cluster.",
self.loader
),
control,
)
}

View file

@ -65,7 +65,8 @@ enum Confirm {
fn kind_badge(kind: LogKind) -> &'static str {
match kind {
LogKind::Game { .. } => "Launcher",
LogKind::Game { .. } => "Game output",
LogKind::Launcher => "Launcher logs",
LogKind::Minecraft => "Minecraft",
LogKind::CrashReport => "Crash report",
LogKind::Other => "Other",
@ -151,7 +152,7 @@ impl Component for ClusterLogs {
let value = search.read().clone();
(!value.trim().is_empty()).then_some(value)
};
let content_query = use_log_content(
selected_path.clone(),
level.read().to_level(),
@ -162,7 +163,7 @@ impl Component for ClusterLogs {
let Some(cluster) = load_cluster(cluster_id) else {
return cluster_not_found();
};
let folder = cluster.game_dir().ok().map(|d| d.join("logs"));
let folder = cluster.dir().ok().map(|d| d.join("logs"));
let files = try_cluster_logs(&logs_query).unwrap_or_default();
let has_log = !files.is_empty();

View file

@ -1,23 +1,23 @@
mod overview;
mod logs;
mod overview;
mod package_manager;
mod cluster_settings;
mod mods;
mod process_logs;
mod screenshots;
mod shaders;
mod textures;
mod process_logs;
pub use overview::ClusterOverview;
pub use logs::ClusterLogs;
pub use overview::ClusterOverview;
pub use cluster_settings::ClusterSettings;
pub use mods::ClusterMods;
pub use process_logs::ProcessLogs;
pub use screenshots::ClusterScreenshots;
pub use shaders::ClusterShaders;
pub use textures::ClusterTextures;
pub use process_logs::ProcessLogs;
use freya::prelude::*;
use freya::query::QueryStateData;

View file

@ -1,14 +1,14 @@
use freya::prelude::*;
use oneclient_core::packages::{ContentType, ProviderId};
use oneclient_core::packages::ContentType;
use crate::hooks::{
bundle_overrides_map, bundles_with_status_items, cluster_content_items, package_meta_batch,
use_bundle_overrides, use_bundles_with_status, use_cluster_content, use_package_meta_batch,
bundle_overrides_map, bundles_with_status_items, cluster_content_items, use_bundle_overrides,
use_bundles_with_status, use_cluster_content,
};
use crate::layout::cluster_content;
use super::package_manager::{
PackageManager, bundle_categories, bundle_packages, managed_project_ids,
PackageManager, bundle_categories, bundle_packages, use_content_meta,
};
use super::{cluster_not_found, load_cluster};
@ -23,8 +23,8 @@ impl Component for ClusterMods {
let bundles = use_bundles_with_status(self.cluster_id);
let overrides = use_bundle_overrides(self.cluster_id);
let bundle_items = bundles_with_status_items(&bundles);
let project_ids = managed_project_ids(&bundle_items, ContentType::Mod);
let meta = use_package_meta_batch(ProviderId::Modrinth, project_ids);
let content_items = cluster_content_items(&content);
let meta = use_content_meta(&content_items, &bundle_items, ContentType::Mod);
let Some(_cluster) = load_cluster(self.cluster_id) else {
return cluster_not_found();
@ -32,10 +32,10 @@ impl Component for ClusterMods {
let all_categories = bundle_categories(&bundle_items);
let items = bundle_packages(
cluster_content_items(&content),
content_items,
&bundle_items,
&bundle_overrides_map(&overrides),
&package_meta_batch(&meta),
&meta,
ContentType::Mod,
);

View file

@ -1,4 +1,3 @@
use std::collections::{HashMap, HashSet};
use freya::prelude::*;
@ -6,7 +5,7 @@ use oneclient_core::packages::{CachedPackageMeta, ContentType, ProviderId};
use oneclient_core::{BundleFileKind, BundleWithUpdateStatus, LinkedArtifactInfo};
use crate::components::{CardLayout, PackageEntry};
use crate::hooks::use_view_state;
use crate::hooks::{package_meta_batch, use_package_meta_batch, use_view_state};
mod views;
use views::{ContentBox, ContentKind, EnabledFilter, SortMode, toolbar_bar};
@ -18,9 +17,13 @@ const GRID_GAP: f32 = 10.;
const GRID_MIN_W: f32 = 260.;
const LAZY_OVERSCAN: i64 = 2;
pub fn managed_project_ids(
pub type PackageMetaMap = HashMap<(ProviderId, String), CachedPackageMeta>;
fn provider_project_ids(
content: &[LinkedArtifactInfo],
bundles: &[BundleWithUpdateStatus],
content_type: ContentType,
provider: ProviderId,
) -> Vec<String> {
let mut ids = Vec::new();
for bundle in bundles {
@ -28,19 +31,49 @@ pub fn managed_project_ids(
if file.content_type() != content_type {
continue;
}
if let BundleFileKind::Managed { project_id, .. } = &file.kind {
if let BundleFileKind::Managed {
project_id,
provider: file_provider,
..
} = &file.kind
&& *file_provider == provider
{
ids.push(project_id.clone());
}
}
}
for info in content {
if info.content_type != content_type || info.provider != Some(provider) {
continue;
}
if let Some(project_id) = &info.project_id {
ids.push(project_id.clone());
}
}
ids
}
pub fn use_content_meta(
content: &[LinkedArtifactInfo],
bundles: &[BundleWithUpdateStatus],
content_type: ContentType,
) -> PackageMetaMap {
let mut out = PackageMetaMap::new();
for provider in ProviderId::REMOTE_PROVIDERS.iter().copied() {
let ids = provider_project_ids(content, bundles, content_type, provider);
let query = use_package_meta_batch(provider, ids);
for (project_id, meta) in package_meta_batch(&query) {
out.insert((provider, project_id), meta);
}
}
out
}
pub fn bundle_packages(
content: Vec<LinkedArtifactInfo>,
bundles: &[BundleWithUpdateStatus],
overrides: &HashMap<(String, String), String>,
meta: &HashMap<String, CachedPackageMeta>,
meta: &PackageMetaMap,
content_type: ContentType,
) -> Vec<PackageEntry> {
let mut by_project: HashMap<&str, &LinkedArtifactInfo> = HashMap::new();
@ -104,10 +137,7 @@ pub fn bundle_packages(
}
for info in &content {
let in_bundle = info
.project_id
.as_deref()
.is_some_and(|p| seen.contains(p))
let in_bundle = info.project_id.as_deref().is_some_and(|p| seen.contains(p))
|| seen.contains(&info.hash);
if in_bundle {
continue;
@ -141,10 +171,10 @@ fn make_row(
categories: Vec<String>,
enabled: bool,
installed_info: Option<&LinkedArtifactInfo>,
meta: &HashMap<String, CachedPackageMeta>,
meta: &PackageMetaMap,
fallback_name: String,
) -> PackageEntry {
let m = meta.get(&package_id);
let m = meta.get(&(provider, package_id.clone()));
let name = m
.map(|p| p.name.clone())
.filter(|s| !s.is_empty())

View file

@ -264,7 +264,13 @@ impl Component for FilterPopover {
.background(colors::page_elevated().with_a(230))
.blur(12.)
.border(crate::ui::border_all_color(1., colors::component_border()))
.shadow(Shadow::from((0., 8., 32., 0., Color::from_argb(120, 0, 0, 0))))
.shadow(Shadow::from((
0.,
8.,
32.,
0.,
Color::from_argb(120, 0, 0, 0),
)))
.a11y_id(a11y_id)
.a11y_role(AccessibilityRole::Dialog)
.on_global_key_down(move |e: Event<KeyboardEventData>| {
@ -527,12 +533,16 @@ impl Component for ContentBox {
let header = (count > 0)
.then(|| match kind {
ContentKind::External => Some(
action_header(browse_button(cluster_id, package_type)).into_element(),
),
ContentKind::External => {
Some(action_header(browse_button(cluster_id, package_type)).into_element())
}
ContentKind::Local => Some(
action_header(add_from_file_button(cluster_id, content_type, dispatch.clone()))
.into_element(),
action_header(add_from_file_button(
cluster_id,
content_type,
dispatch.clone(),
))
.into_element(),
),
ContentKind::Other => None,
})

View file

@ -1,4 +1,3 @@
use freya::prelude::*;
use crate::components::LogViewer;
@ -30,9 +29,7 @@ impl Component for ProcessLogs {
not_running()
};
cluster_content()
.child(body)
.into_element()
cluster_content().child(body).into_element()
}
}

View file

@ -8,8 +8,7 @@ use oneclient_core::settings::ViewLayout;
use crate::components::{
Button, ContextMenu, Icon, IconType, LocalImage, OverlayPopup, ScreenshotViewer, ScrollArea,
open_folder_button,
Segment, SegmentedControl,
Segment, SegmentedControl, open_folder_button,
};
use crate::hooks::{
ScreenshotAction, try_cluster_screenshots, use_cluster_screenshots, use_dispatch,
@ -57,7 +56,14 @@ impl Component for ClusterScreenshots {
let grid_width = use_state(|| 0f32);
let mut menu = use_state(|| None::<(f32, f32, PathBuf)>);
let toolbar = toolbar_row(&shots, folder, view_mode, edit_mode, selected, confirm_delete);
let toolbar = toolbar_row(
&shots,
folder,
view_mode,
edit_mode,
selected,
confirm_delete,
);
let content: Element = if shots.is_empty() {
empty_state(matches!(
@ -210,7 +216,12 @@ fn toolbar_row(
.horizontal()
.cross_align(Alignment::Center)
.spacing(10.)
.maybe_child((!editing).then_some(folder).flatten().map(open_folder_button));
.maybe_child(
(!editing)
.then_some(folder)
.flatten()
.map(open_folder_button),
);
if editing {
let select_all_paths = all_paths.clone();
@ -422,8 +433,6 @@ fn confirm_panel(
.into_element()
}
fn res_badge(res: Option<(u32, u32)>) -> Option<Element> {
res.map(format_res).map(|text| {
rect()

View file

@ -1,14 +1,14 @@
use freya::prelude::*;
use oneclient_core::packages::{ContentType, ProviderId};
use oneclient_core::packages::ContentType;
use crate::hooks::{
bundle_overrides_map, bundles_with_status_items, cluster_content_items, package_meta_batch,
use_bundle_overrides, use_bundles_with_status, use_cluster_content, use_package_meta_batch,
bundle_overrides_map, bundles_with_status_items, cluster_content_items, use_bundle_overrides,
use_bundles_with_status, use_cluster_content,
};
use crate::layout::cluster_content;
use super::package_manager::{
PackageManager, bundle_categories, bundle_packages, managed_project_ids,
PackageManager, bundle_categories, bundle_packages, use_content_meta,
};
use super::{cluster_not_found, load_cluster};
@ -23,8 +23,8 @@ impl Component for ClusterShaders {
let bundles = use_bundles_with_status(self.cluster_id);
let overrides = use_bundle_overrides(self.cluster_id);
let bundle_items = bundles_with_status_items(&bundles);
let project_ids = managed_project_ids(&bundle_items, ContentType::Shader);
let meta = use_package_meta_batch(ProviderId::Modrinth, project_ids);
let content_items = cluster_content_items(&content);
let meta = use_content_meta(&content_items, &bundle_items, ContentType::Shader);
let Some(_cluster) = load_cluster(self.cluster_id) else {
return cluster_not_found();
@ -32,10 +32,10 @@ impl Component for ClusterShaders {
let all_categories = bundle_categories(&bundle_items);
let items = bundle_packages(
cluster_content_items(&content),
content_items,
&bundle_items,
&bundle_overrides_map(&overrides),
&package_meta_batch(&meta),
&meta,
ContentType::Shader,
);

View file

@ -1,14 +1,14 @@
use freya::prelude::*;
use oneclient_core::packages::{ContentType, ProviderId};
use oneclient_core::packages::ContentType;
use crate::hooks::{
bundle_overrides_map, bundles_with_status_items, cluster_content_items, package_meta_batch,
use_bundle_overrides, use_bundles_with_status, use_cluster_content, use_package_meta_batch,
bundle_overrides_map, bundles_with_status_items, cluster_content_items, use_bundle_overrides,
use_bundles_with_status, use_cluster_content,
};
use crate::layout::cluster_content;
use super::package_manager::{
PackageManager, bundle_categories, bundle_packages, managed_project_ids,
PackageManager, bundle_categories, bundle_packages, use_content_meta,
};
use super::{cluster_not_found, load_cluster};
@ -23,8 +23,8 @@ impl Component for ClusterTextures {
let bundles = use_bundles_with_status(self.cluster_id);
let overrides = use_bundle_overrides(self.cluster_id);
let bundle_items = bundles_with_status_items(&bundles);
let project_ids = managed_project_ids(&bundle_items, ContentType::ResourcePack);
let meta = use_package_meta_batch(ProviderId::Modrinth, project_ids);
let content_items = cluster_content_items(&content);
let meta = use_content_meta(&content_items, &bundle_items, ContentType::ResourcePack);
let Some(_cluster) = load_cluster(self.cluster_id) else {
return cluster_not_found();
@ -32,10 +32,10 @@ impl Component for ClusterTextures {
let all_categories = bundle_categories(&bundle_items);
let items = bundle_packages(
cluster_content_items(&content),
content_items,
&bundle_items,
&bundle_overrides_map(&overrides),
&package_meta_batch(&meta),
&meta,
ContentType::ResourcePack,
);

View file

@ -8,7 +8,6 @@ use crate::components::{Button, ClusterLandscapeArt, Dropdown, Icon, IconType, M
use crate::hooks::{
use_active_cluster_id, use_clusters, use_dispatch, use_game_snapshot, use_version_metadata,
};
use crate::view::app::launch_button_state;
use crate::routes::Route;
use crate::theme::colors;
use crate::ui::border_all_color;
@ -16,6 +15,7 @@ use crate::utils::{
default_loader, default_major, default_minor, group_clusters_by_major, loaders_for_major,
major_pretty_name, minor_label, minor_versions, resolve_cluster,
};
use crate::view::app::launch_button_state;
const GRID_GAP_PX: f32 = 12.;
const MIN_CARD_WIDTH_PX: f32 = 280.;
@ -212,7 +212,10 @@ fn detail_sidebar(
.as_ref()
.and_then(|m| m.long_description.clone())
.unwrap_or_else(|| PLACEHOLDER_VERSION_INFO.to_string());
let tags = metadata.as_ref().map(|m| m.tags.clone()).unwrap_or_default();
let tags = metadata
.as_ref()
.map(|m| m.tags.clone())
.unwrap_or_default();
rect()
.width(Size::px(SIDEBAR_WIDTH_PX))
@ -225,17 +228,9 @@ fn detail_sidebar(
.background(colors::page_elevated())
.border(border_all_color(1., colors::component_border()))
.overflow(Overflow::Clip)
.child(
rect()
.width(Size::fill())
.max_height(Size::px(140.))
.child(ClusterLandscapeArt::for_version(
major,
minor_value,
loader_value,
false,
)),
)
.child(rect().width(Size::fill()).max_height(Size::px(140.)).child(
ClusterLandscapeArt::for_version(major, minor_value, loader_value, false),
))
.child(
rect()
.vertical()
@ -274,7 +269,11 @@ fn detail_sidebar(
.content(Content::Flex)
.main_align(Alignment::Center)
.spacing(8.)
.child(play_button(cluster_id, dispatch, launch_button_state(&game, cluster_id)))
.child(play_button(
cluster_id,
dispatch,
launch_button_state(&game, cluster_id),
))
.child(view_button(cluster_id, active_id)),
),
)

View file

@ -1,14 +1,14 @@
mod analytics;
mod home;
mod accounts;
mod analytics;
mod clusters;
mod debug;
mod home;
mod skins;
mod stats;
pub mod browser;
pub mod settings;
pub mod cluster;
pub mod settings;
use crate::bridge::GameSnapshot;
use oneclient_core::notification::LaunchStage;
@ -23,10 +23,10 @@ pub fn launch_button_state(game: &GameSnapshot, cluster_id: i64) -> (&'static st
}
}
pub(crate) use analytics::analytics_body;
pub use accounts::Accounts;
pub use home::Home;
pub(crate) use analytics::analytics_body;
pub use clusters::Clusters;
pub use debug::Debug;
pub use home::Home;
pub use skins::AccountSkins;
pub use stats::Stats;

View file

@ -1,8 +1,8 @@
use freya::prelude::*;
use super::settings_page;
use crate::components::{IconType, TextInput};
use crate::hooks::{use_dispatch, use_settings_snapshot};
use super::settings_page;
use crate::view::app::settings::{section_header, settings_row};
fn normalize(value: &str) -> Option<String> {

View file

@ -1,8 +1,8 @@
use freya::prelude::*;
use super::settings_page;
use crate::components::{Icon, IconType, toggle};
use crate::hooks::{use_dispatch, use_settings_snapshot};
use super::settings_page;
use crate::theme::colors;
use crate::ui::border_all_color;
use crate::view::app::settings::settings_row;

View file

@ -1,8 +1,8 @@
use freya::prelude::*;
use super::settings_page;
use crate::components::{Icon, IconType};
use crate::hooks::{changelog_error, changelog_groups, changelog_is_loading, use_changelog};
use super::settings_page;
use crate::theme::colors;
#[derive(PartialEq)]
@ -119,14 +119,16 @@ impl Component for ReleaseCard {
.spacing(4.)
.padding(Gaps::new(0., 0., 0., 6.))
.children(if changes.is_empty() {
vec![rect()
.child(
label()
.text("No changes recorded for this version.")
.font_size(12.)
.color(colors::fg_secondary()),
)
.into_element()]
vec![
rect()
.child(
label()
.text("No changes recorded for this version.")
.font_size(12.)
.color(colors::fg_secondary()),
)
.into_element(),
]
} else {
changes
.into_iter()

View file

@ -1,10 +1,10 @@
use freya::prelude::*;
use freya::router::RouterContext;
use super::settings_page;
use crate::Route;
use crate::components::{Button, IconType, link_button, toggle};
use crate::hooks::{use_browser_compat, use_dispatch, use_settings_snapshot};
use super::settings_page;
use crate::view::app::settings::{section_header, settings_row};
#[derive(PartialEq)]
@ -67,7 +67,7 @@ impl Component for SettingsDeveloper {
"Debug Page",
"View the debug page.",
link_button().on_press(move |_| {
let _ = RouterContext::get().push(Route::Debug { });
let _ = RouterContext::get().push(Route::Debug {});
}),
))
.into_element()

View file

@ -1,12 +1,12 @@
use freya::prelude::*;
use oneclient_core::java::{AvailableJava, JavaRuntime, JavaVendor};
use super::settings_page;
use crate::components::{Button, Dropdown, Icon, IconType, OverlayPopup, ScrollArea};
use crate::hooks::{
java_runtimes, provider_versions, use_dispatch, use_java_runtimes, use_provider_versions,
BridgeDispatch,
BridgeDispatch, java_runtimes, provider_versions, use_dispatch, use_java_runtimes,
use_provider_versions,
};
use super::settings_page;
use crate::theme::colors;
use crate::ui::border_all_color;
use crate::view::app::settings::section_header;
@ -151,15 +151,13 @@ fn table_header() -> impl IntoElement {
.child(head("VENDOR", Size::px(130.)))
.child(head("VERSION", Size::px(90.)))
.child(
rect()
.width(Size::flex(1.0))
.child(
label()
.text("PATH")
.font_size(11.)
.font_weight(FontWeight::MEDIUM)
.color(colors::fg_secondary()),
),
rect().width(Size::flex(1.0)).child(
label()
.text("PATH")
.font_size(11.)
.font_weight(FontWeight::MEDIUM)
.color(colors::fg_secondary()),
),
)
.child(rect().width(Size::px(34.)))
.into_element()
@ -206,7 +204,10 @@ impl Component for RuntimeRow {
.fill(colors::component_border()),
)
})
.child(cell(format!("{} {}", runtime.vendor, runtime.major), Size::px(130.)))
.child(cell(
format!("{} {}", runtime.vendor, runtime.major),
Size::px(130.),
))
.child(cell(runtime.version.clone(), Size::px(90.)))
.child(
rect()
@ -241,7 +242,11 @@ fn remove_button(dispatch: BridgeDispatch, path: String) -> impl IntoElement {
.ghost()
.small()
.on_press(move |_| dispatch.remove_java_runtime(path.clone()))
.child(Icon::new(IconType::Trash01).size(14.).color(colors::danger()))
.child(
Icon::new(IconType::Trash01)
.size(14.)
.color(colors::danger()),
)
.into_element()
}

View file

@ -1,13 +1,11 @@
use freya::prelude::*;
use crate::components::{IconType, Icon};
use super::settings_page;
use crate::components::{Icon, IconType};
use crate::theme::colors;
use crate::view::app::settings::section_header;
const LANGUAGES: &[(&str, &str)] = &[
("English", "English (US)"),
];
const LANGUAGES: &[(&str, &str)] = &[("English", "English (US)")];
#[derive(PartialEq)]
pub struct SettingsLanguage;
@ -63,8 +61,11 @@ fn language_row(
.color(colors::fg_secondary()),
),
)
.maybe_child(
is_selected.then(|| Icon::new(IconType::Check).size(20.).color(colors::brand()).into_element()),
)
.maybe_child(is_selected.then(|| {
Icon::new(IconType::Check)
.size(20.)
.color(colors::brand())
.into_element()
}))
.into_element()
}

View file

@ -1,8 +1,8 @@
use freya::prelude::*;
use super::settings_page;
use crate::components::{IconType, link_button, toggle};
use crate::hooks::{use_dispatch, use_launcher, use_settings_snapshot};
use super::settings_page;
use crate::platform;
use crate::view::app::settings::{section_header, settings_row};
@ -20,17 +20,24 @@ impl Component for SettingsLauncher {
move || v
});
let crash_reporting = use_state({
let v = settings.crash_reporting;
move || v
});
let mut first = use_state(|| true);
{
let settings = settings.clone();
use_side_effect(move || {
let discord = *discord_rpc.read();
let crash = *crash_reporting.read();
if *first.peek() {
first.set(false);
return;
}
let mut next = settings.clone();
next.discord_enabled = discord;
next.crash_reporting = crash;
dispatch.set_settings(next);
});
}
@ -46,6 +53,12 @@ impl Component for SettingsLauncher {
"Enable Discord Rich Presence.",
toggle(discord_rpc),
))
.child(settings_row(
IconType::AlertTriangle,
"Crash Reporting",
"Send anonymous crash and error reports to help fix bugs. Applies on restart.",
toggle(crash_reporting),
))
.child(section_header("FOLDERS AND FILES"))
.child(settings_row(
IconType::Folder,

View file

@ -2,9 +2,9 @@ use freya::prelude::*;
use oneclient_core::Patch;
use oneclient_core::settings::{ProfileUpdate, Resolution};
use super::settings_page;
use crate::components::{Icon, IconType, TextInput, toggle, validate_number};
use crate::hooks::{use_dispatch, use_settings_snapshot};
use super::settings_page;
use crate::theme::colors;
use crate::view::app::settings::{section_header, settings_row};
@ -21,11 +21,17 @@ impl Component for SettingsMinecraft {
move || v
});
let width = use_state({
let v = profile.resolution.map(|r| r.width.to_string()).unwrap_or_default();
let v = profile
.resolution
.map(|r| r.width.to_string())
.unwrap_or_default();
move || v
});
let height = use_state({
let v = profile.resolution.map(|r| r.height.to_string()).unwrap_or_default();
let v = profile
.resolution
.map(|r| r.height.to_string())
.unwrap_or_default();
move || v
});
let memory = use_state({
@ -165,7 +171,11 @@ fn resolution_field(width: State<String>, height: State<String>) -> impl IntoEle
.width(Size::px(70.))
.text_align(TextAlign::Center),
)
.child(Icon::new(IconType::X).size(14.).color(colors::fg_secondary()))
.child(
Icon::new(IconType::X)
.size(14.)
.color(colors::fg_secondary()),
)
.child(
TextInput::new(height)
.placeholder("480")

View file

@ -15,14 +15,14 @@ pub use apis::SettingsApis;
pub use changelog::SettingsChangelog;
pub use developer::SettingsDeveloper;
// pub use feedback::SettingsFeedback;
pub use java::SettingsJava;
pub use appearance::SettingsAppearance;
pub use java::SettingsJava;
pub use language::SettingsLanguage;
pub use launcher::SettingsLauncher;
pub use minecraft::SettingsMinecraft;
use crate::{
components::{IconType, Icon},
components::{Icon, IconType},
theme::colors,
};

View file

@ -1,4 +1,3 @@
use freya::prelude::*;
use crate::components::{Button, ButtonVariant, Icon, IconType, PlayerModel};
@ -120,7 +119,11 @@ fn side_panel() -> impl IntoElement {
fn action_button(text: &'static str, icon: IconType, primary: bool) -> impl IntoElement {
Button::new()
.variant(if primary { ButtonVariant::Primary } else { ButtonVariant::Secondary })
.variant(if primary {
ButtonVariant::Primary
} else {
ButtonVariant::Secondary
})
.child(Icon::new(icon).size(16.).color(colors::fg_primary()))
.text(text)
.into_element()

View file

@ -1,8 +1,8 @@
mod not_found;
mod startup;
pub mod onboarding;
pub mod app;
pub mod onboarding;
pub use not_found::NotFound;
pub use startup::Startup;
pub use startup::Startup;

View file

@ -32,4 +32,4 @@ impl Component for NotFound {
.color(theme::colors::fg_secondary()),
)
}
}
}

View file

@ -125,12 +125,7 @@ fn sign_in_card(
.size(13.)
.color(colors::danger()),
)
.child(
label()
.text(message)
.font_size(12.)
.color(colors::danger()),
)
.child(label().text(message).font_size(12.).color(colors::danger()))
.into_element()
}))
.into_element()

View file

@ -2,9 +2,7 @@ use super::*;
use oneclient_core::clusters::Cluster;
use crate::components::{
Button, DynamicArt, Icon, IconType,
};
use crate::components::{Button, DynamicArt, Icon, IconType};
use crate::theme::colors;
use crate::ui::border_all_color;

View file

@ -8,8 +8,7 @@ use oneclient_core::{BundleArchive, BundleFile, BundleFileKind};
use crate::components::ScrollArea;
use crate::hooks::{
ClusterBundles, onboarding_bundles_items,
use_onboarding_bundles, use_onboarding_selection,
ClusterBundles, onboarding_bundles_items, use_onboarding_bundles, use_onboarding_selection,
};
use crate::routes::Route;
use crate::theme::colors;
@ -321,4 +320,3 @@ impl Component for OnboardingBundles {
.maybe_child(popup)
}
}

View file

@ -6,12 +6,8 @@ use freya::query::QueryStateData;
use oneclient_core::packages::{CachedPackageMeta, ProviderId};
use oneclient_core::{BundleArchive, BundleFileKind};
use crate::components::{
Button, Icon, OverlayPopup, ScrollArea, TabBar, TabItem,
};
use crate::hooks::{
package_meta_batch, use_cached_image, use_package_meta_batch,
};
use crate::components::{Button, Icon, OverlayPopup, ScrollArea, TabBar, TabItem};
use crate::hooks::{package_meta_batch, use_cached_image, use_package_meta_batch};
use crate::theme::colors;
use crate::ui::border_all_color;
use crate::utils::format_size;

View file

@ -46,7 +46,10 @@ pub(super) fn summary_view(
let migration_section: Element = match migration {
Some((source, folder, target)) => {
let mut rows = vec![summary_line("From", &source), summary_line("Files", &folder)];
let mut rows = vec![
summary_line("From", &source),
summary_line("Files", &folder),
];
if !target.is_empty() {
rows.push(summary_line("Destination", &target));
}

View file

@ -50,7 +50,14 @@ pub(super) fn use_onboarding_tip() -> String {
let Ok(state) = oneclient_core::LauncherState::get() else {
return;
};
match state.services.requester.http().get(FUNFACTS_URL).send().await {
match state
.services
.requester
.http()
.get(FUNFACTS_URL)
.send()
.await
{
Ok(res) if res.status().is_success() => match res.text().await {
Ok(text) => {
let parsed = parse_funfacts(&text);

View file

@ -14,9 +14,10 @@ struct Language {
percentage: u32,
}
const LANGUAGES: [Language; 1] = [
Language { name: "English", percentage: 100 },
];
const LANGUAGES: [Language; 1] = [Language {
name: "English",
percentage: 100,
}];
#[derive(PartialEq)]
pub struct OnboardingLanguage;
@ -29,15 +30,13 @@ impl Component for OnboardingLanguage {
let back = if has_migration_data(&migration_query) {
Route::OnboardingMigration {}
} else {
Route::OnboardingWelcome {}
Route::OnboardingTerms {}
};
let rows: Vec<Element> = LANGUAGES
.iter()
.enumerate()
.map(|(index, lang)| {
language_row(lang, index == *selected.read(), selected, language)
})
.map(|(index, lang)| language_row(lang, index == *selected.read(), selected, language))
.collect();
let content = rect()
@ -55,7 +54,13 @@ impl Component for OnboardingLanguage {
.font_weight(FontWeight::MEDIUM)
.color(colors::fg_primary()),
)
.child(rect().vertical().width(Size::fill()).spacing(4.).children(rows))
.child(
rect()
.vertical()
.width(Size::fill())
.spacing(4.)
.children(rows),
)
.into_element();
onboarding_page(
@ -92,7 +97,9 @@ fn language_row(
.padding(Gaps::new_symmetric(14., 20.))
.corner_radius(CornerRadius::new_all(10.))
.background(background)
.maybe(active, |el| el.border(border_all_color(1., colors::brand())))
.maybe(active, |el| {
el.border(border_all_color(1., colors::brand()))
})
.a11y_role(AccessibilityRole::Button)
.on_pointer_enter(|_| Cursor::set(CursorIcon::Pointer))
.on_pointer_leave(|_| Cursor::set(CursorIcon::default()))

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