OneLauncher-Cracked/packages/oneclient_app/src/view/app/browser/package/panels.rs

342 lines
10 KiB
Rust

use super::*;
use oneclient_content::packages::ProviderId;
use oneclient_content::packages::types::{PackageBody, ProjectDetail, ReleaseType, VersionSummary};
use crate::Actions;
use crate::components::{Button, Icon, IconType, Segment, SegmentedControl};
use crate::hooks::VERSIONS_PAGE_SIZE;
use crate::theme::colors;
use crate::ui::border_all_color;
use crate::utils::format_size;
pub(super) fn loading_body() -> impl IntoElement {
rect()
.width(Size::fill())
.center()
.padding(Gaps::new_all(32.))
.child(
label()
.text("Loading package...")
.font_size(14.)
.color(colors::fg_secondary()),
)
.into_element()
}
pub(super) fn tabs(active_tab: State<usize>, has_gallery: bool) -> impl IntoElement {
let mut control = SegmentedControl::new(active_tab)
.height(36.)
.segment(Segment::new(0usize).label("About"))
.segment(Segment::new(1usize).label("Versions"));
if has_gallery {
control = control.segment(Segment::new(2usize).label("Gallery"));
}
control
}
pub(super) fn about_panel(project: &ProjectDetail) -> impl IntoElement {
let body = match &project.body {
PackageBody::Raw(md) => md.clone(),
PackageBody::Url(url) => format!("{}\n\n[View online]({url})", project.summary),
};
MarkdownPanel { body }.into_element()
}
#[derive(PartialEq)]
struct MarkdownPanel {
body: String,
}
impl Component for MarkdownPanel {
fn render(&self) -> impl IntoElement {
rect()
.width(Size::fill())
.padding(Gaps::new_all(20.))
.corner_radius(CornerRadius::new_all(12.))
.background(PANEL_BG)
.border(border_all_color(1., colors::component_border()))
.child(
MarkdownViewer::new(self.body.clone())
.width(Size::fill())
.color(colors::fg_primary())
.color_link(colors::code_info())
.background_code(colors::component_bg())
.color_code(colors::fg_primary())
.background_blockquote(colors::component_bg())
.border_blockquote(colors::brand())
.background_divider(colors::component_border())
.heading_h1(26.)
.heading_h2(22.)
.heading_h3(18.)
.heading_h4(16.)
.heading_h5(14.)
.heading_h6(13.)
.paragraph_size(13.)
.code_font_size(12.),
)
}
}
#[allow(clippy::too_many_arguments)]
pub(super) fn versions_panel(
versions: Vec<VersionSummary>,
total_versions: usize,
versions_page: State<usize>,
provider: ProviderId,
project_id: String,
cluster_id: i64,
dispatch: Actions,
installed: Option<Installed>,
installing: bool,
) -> impl IntoElement {
let current = *versions_page.read();
let total_pages = total_versions.div_ceil(VERSIONS_PAGE_SIZE).max(1);
let rows: Element = if versions.is_empty() {
rect()
.width(Size::fill())
.center()
.padding(Gaps::new_all(32.))
.child(
label()
.text("No matching versions.")
.font_size(13.)
.color(colors::fg_secondary()),
)
.into_element()
} else {
rect()
.vertical()
.width(Size::fill())
.spacing(8.)
.children(versions.into_iter().map(move |v| {
let tag = installed
.as_ref()
.and_then(|installed| installed.find_version(&v.version_id))
.cloned();
let duplicated = installed
.as_ref()
.is_some_and(|installed| installed.is_duplicated());
version_row(
v,
provider,
project_id.clone(),
cluster_id,
dispatch.clone(),
tag,
duplicated,
installing,
)
.into_element()
}))
.into_element()
};
rect()
.vertical()
.width(Size::fill())
.spacing(12.)
.child(rows)
.maybe(total_versions > 0, |el| {
el.child(version_pager(current, total_pages, versions_page))
})
.into_element()
}
fn version_pager(current: usize, total_pages: usize, page: State<usize>) -> impl IntoElement {
let nav = move |target: usize, enabled: bool, icon: IconType| {
let mut page = page;
rect()
.center()
.width(Size::px(32.))
.height(Size::px(32.))
.corner_radius(CornerRadius::new_all(8.))
.background(colors::component_bg())
.border(border_all_color(1., colors::component_border()))
.maybe(enabled, |el| {
el.cursor(CursorIcon::Pointer)
.on_press(move |_| page.set(target))
})
.child(Icon::new(icon).size(14.).color(if enabled {
colors::fg_primary()
} else {
colors::fg_secondary().with_a(90)
}))
.into_element()
};
rect()
.horizontal()
.width(Size::fill())
.main_align(Alignment::Center)
.cross_align(Alignment::Center)
.spacing(12.)
.child(nav(
current.saturating_sub(1),
current > 0,
IconType::ArrowLeft,
))
.child(
label()
.text(format!("Page {} / {}", current + 1, total_pages))
.font_size(12.)
.color(colors::fg_secondary()),
)
.child(nav(
current + 1,
current + 1 < total_pages,
IconType::ArrowRight,
))
.into_element()
}
#[allow(clippy::too_many_arguments)]
fn version_row(
v: VersionSummary,
provider: ProviderId,
project_id: String,
cluster_id: i64,
dispatch: Actions,
installed: Option<InstalledVersion>,
// Saying which version is live only tells the user anything when there are several
duplicated: bool,
installing: bool,
) -> impl IntoElement {
let version_id = v.version_id.clone();
let mut chips: Vec<String> = v.loaders.iter().map(|l| l.to_string()).collect();
chips.extend(v.game_versions.iter().cloned());
let stats = {
let mut parts = vec![format!("{} downloads", abbreviate_number(v.downloads))];
if v.file_size > 0 {
parts.push(format_size(v.file_size));
}
parts.push(v.published.format("%Y-%m-%d").to_string());
parts.join(" · ")
};
let has_chips = !chips.is_empty();
rect()
.horizontal()
.width(Size::fill())
.cross_align(Alignment::Center)
.spacing(12.)
.content(Content::Flex)
.padding(Gaps::new_all(12.))
.corner_radius(CornerRadius::new_all(10.))
.background(PANEL_BG)
.border(border_all_color(1., colors::component_border()))
.child(release_badge(v.release_type))
.child(
rect()
.vertical()
.width(Size::flex(1.0))
.spacing(6.)
.child(
label()
.text(v.name.clone())
.font_size(14.)
.font_weight(FontWeight::MEDIUM)
.max_lines(1)
.color(colors::fg_primary()),
)
.maybe(has_chips, |el| el.child(pill_flow(&chips, 8, 10)))
.child(
label()
.text(stats)
.font_size(11.)
.max_lines(1)
.color(colors::fg_secondary()),
),
)
.maybe_child(
installed
.as_ref()
.map(|installed| installed_badge(installed.source, 11.).into_element()),
)
.maybe_child(
installed
.as_ref()
.filter(|_| duplicated)
.map(|installed| activity_badge(installed.enabled).into_element()),
)
.child(version_button(
installed,
v.name,
provider,
project_id,
version_id,
cluster_id,
dispatch,
installing,
))
}
/// A bundle pin with nothing linked leaves nothing to press no artifact to remove and installing by hand would duplicate it
#[allow(clippy::too_many_arguments)]
fn version_button(
installed: Option<InstalledVersion>,
version_name: String,
provider: ProviderId,
project_id: String,
version_id: String,
cluster_id: i64,
dispatch: Actions,
busy: bool,
) -> impl IntoElement {
let Some(installed) = installed else {
return Button::new()
.secondary()
.small()
.enabled(!busy)
.on_press(move |_| {
dispatch.install_package(
cluster_id,
provider,
project_id.clone(),
version_id.clone(),
);
})
.text("Install");
};
let Some(hash) = installed.hash else {
return Button::new().secondary().small().enabled(false).text("Install");
};
Button::new()
.danger()
.small()
.enabled(!busy)
.on_press(move |_| {
dispatch.remove_package_version(
cluster_id,
provider,
project_id.clone(),
hash.clone(),
version_name.clone(),
);
})
.text("Remove")
}
fn release_badge(release_type: ReleaseType) -> impl IntoElement {
let (text, color) = match release_type {
ReleaseType::Release => ("R", colors::code_info()),
ReleaseType::Beta => ("B", colors::code_warn()),
ReleaseType::Alpha => ("A", colors::code_error()),
};
rect()
.center()
.width(Size::px(28.))
.height(Size::px(28.))
.corner_radius(CornerRadius::new_all(7.))
.background(color.with_a(40))
.child(
label()
.text(text)
.font_size(13.)
.font_weight(FontWeight::BOLD)
.color(color),
)
}