399 lines
No EOL
13 KiB
Rust
399 lines
No EOL
13 KiB
Rust
use std::path::Path;
|
|
|
|
use freya::prelude::*;
|
|
use oneclient_java::{JavaRuntime, JavaVendor, is_launcher_managed};
|
|
|
|
use super::settings_page;
|
|
use crate::components::{Button, Icon, IconType, JavaInstallManager, OverlayPopup, ScrollArea};
|
|
use crate::hooks::{Actions, java_runtimes, use_dispatch, use_java_runtimes};
|
|
use crate::invalidate_java_queries;
|
|
use crate::theme::colors;
|
|
use crate::ui::border_all_color;
|
|
use crate::view::app::settings::section_header;
|
|
|
|
#[derive(PartialEq)]
|
|
pub struct SettingsJava;
|
|
|
|
impl Component for SettingsJava {
|
|
fn render(&self) -> impl IntoElement {
|
|
let dispatch = use_dispatch();
|
|
let removing_dispatch = dispatch.clone();
|
|
let runtimes_query = use_java_runtimes();
|
|
let runtimes = java_runtimes(&runtimes_query);
|
|
let mut show_manager = use_state(|| false);
|
|
let pending_remove = use_state(|| None::<PendingRemove>);
|
|
|
|
let mut shell = settings_page()
|
|
.child(section_header("ADD RUNTIME"))
|
|
.child(AddRow { show_manager }.into_element())
|
|
.child(section_header("INSTALLED RUNTIMES"))
|
|
.child(runtimes_table(runtimes, pending_remove));
|
|
|
|
if *show_manager.read() {
|
|
shell = shell.child(
|
|
JavaInstallManager::new()
|
|
.on_install(move |(vendor, major): (JavaVendor, u32)| {
|
|
dispatch.install_java_runtime(vendor, major);
|
|
show_manager.set(false);
|
|
})
|
|
.on_close(move |()| show_manager.set(false))
|
|
.into_element(),
|
|
);
|
|
}
|
|
|
|
let pending = pending_remove.read().clone();
|
|
if let Some(target) = pending {
|
|
shell = shell.child(confirm_remove_modal(
|
|
removing_dispatch,
|
|
pending_remove,
|
|
target,
|
|
))
|
|
}
|
|
|
|
shell.into_element()
|
|
}
|
|
}
|
|
|
|
#[derive(PartialEq)]
|
|
struct AddRow {
|
|
show_manager: State<bool>,
|
|
}
|
|
|
|
impl Component for AddRow {
|
|
fn render(&self) -> impl IntoElement {
|
|
let dispatch = use_dispatch();
|
|
let mut show_manager = self.show_manager;
|
|
|
|
let pick_dispatch = dispatch.clone();
|
|
let pick = move |_| {
|
|
let dispatch = pick_dispatch.clone();
|
|
spawn(async move {
|
|
if let Some(handle) = rfd::AsyncFileDialog::new()
|
|
.set_title("Select a Java installation folder")
|
|
.pick_folder()
|
|
.await
|
|
{
|
|
dispatch.add_custom_java_runtime(handle.path().to_path_buf());
|
|
}
|
|
});
|
|
};
|
|
|
|
let refresh_dispatch = dispatch;
|
|
let refresh = move |_| {
|
|
let dispatch = refresh_dispatch.clone();
|
|
spawn(async move {
|
|
invalidate_java_queries().await;
|
|
dispatch
|
|
.notify("Java runtimes refreshed")
|
|
.body("The installed runtime list is up to date")
|
|
.info()
|
|
.toast_only()
|
|
.send();
|
|
});
|
|
};
|
|
|
|
rect()
|
|
.horizontal()
|
|
.width(Size::fill())
|
|
.spacing(10.)
|
|
.child(
|
|
Button::new()
|
|
.primary()
|
|
.on_press(move |_| show_manager.set(true))
|
|
.child(Icon::new(IconType::Download01).size(14.))
|
|
.text("Install Manager"),
|
|
)
|
|
.child(
|
|
Button::new()
|
|
.secondary()
|
|
.on_press(pick)
|
|
.child(Icon::new(IconType::Folder).size(14.))
|
|
.text("Add from folder"),
|
|
)
|
|
.child(
|
|
Button::new()
|
|
.secondary()
|
|
.on_press(refresh)
|
|
.child(Icon::new(IconType::RefreshCcw02).size(14.))
|
|
.text("Refresh"),
|
|
)
|
|
}
|
|
}
|
|
|
|
fn runtimes_table(
|
|
runtimes: Vec<JavaRuntime>,
|
|
pending_remove: State<Option<PendingRemove>>,
|
|
) -> impl IntoElement {
|
|
if runtimes.is_empty() {
|
|
return rect()
|
|
.width(Size::fill())
|
|
.padding(Gaps::new_symmetric(16., 16.))
|
|
.corner_radius(CornerRadius::new_all(12.))
|
|
.background(colors::page_elevated())
|
|
.child(
|
|
label()
|
|
.text("No Java runtimes installed yet.")
|
|
.font_size(12.)
|
|
.color(colors::fg_secondary()),
|
|
)
|
|
.into_element();
|
|
}
|
|
|
|
let mut table = rect()
|
|
.vertical()
|
|
.width(Size::fill())
|
|
.corner_radius(CornerRadius::new_all(12.))
|
|
.background(colors::page_elevated())
|
|
.border(border_all_color(1., colors::component_border()))
|
|
.overflow(Overflow::Clip)
|
|
.child(table_header());
|
|
|
|
let count = runtimes.len();
|
|
for (idx, runtime) in runtimes.into_iter().enumerate() {
|
|
table = table.child(
|
|
RuntimeRow {
|
|
runtime,
|
|
last: idx + 1 == count,
|
|
pending_remove,
|
|
}
|
|
.into_element(),
|
|
);
|
|
}
|
|
|
|
table.into_element()
|
|
}
|
|
|
|
fn table_header() -> impl IntoElement {
|
|
fn head(text: &'static str, width: Size) -> impl IntoElement {
|
|
rect()
|
|
.width(width)
|
|
.child(
|
|
label()
|
|
.text(text)
|
|
.font_size(11.)
|
|
.font_weight(FontWeight::MEDIUM)
|
|
.color(colors::fg_secondary()),
|
|
)
|
|
.into_element()
|
|
}
|
|
|
|
rect()
|
|
.horizontal()
|
|
.width(Size::fill())
|
|
.content(Content::Flex)
|
|
.cross_align(Alignment::Center)
|
|
.spacing(12.)
|
|
.padding(Gaps::new_symmetric(8., 14.))
|
|
.background(colors::component_bg())
|
|
.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()),
|
|
),
|
|
)
|
|
.child(rect().width(Size::px(34.)))
|
|
.into_element()
|
|
}
|
|
|
|
#[derive(PartialEq)]
|
|
struct RuntimeRow {
|
|
runtime: JavaRuntime,
|
|
last: bool,
|
|
pending_remove: State<Option<PendingRemove>>,
|
|
}
|
|
|
|
impl Component for RuntimeRow {
|
|
fn render(&self) -> impl IntoElement {
|
|
let runtime = &self.runtime;
|
|
let path = runtime.absolute_path.clone();
|
|
|
|
// so the scrollarea becomes bigger when horizontal scroll bar is visible (then it won't obscure the file path)
|
|
let mut viewport_w = use_state(|| 0f32);
|
|
let content_w = path_content_width(&runtime.absolute_path);
|
|
let measured_w = *viewport_w.read();
|
|
let has_scrollbar = measured_w <= 0. || measured_w < content_w;
|
|
let path_height = if has_scrollbar { 28. } else { 18. };
|
|
|
|
fn cell(text: String, width: Size) -> impl IntoElement {
|
|
rect()
|
|
.width(width)
|
|
.child(
|
|
label()
|
|
.text(text)
|
|
.font_size(13.)
|
|
.color(colors::fg_primary()),
|
|
)
|
|
.into_element()
|
|
}
|
|
|
|
rect()
|
|
.horizontal()
|
|
.width(Size::fill())
|
|
.content(Content::Flex)
|
|
.cross_align(Alignment::Center)
|
|
.spacing(12.)
|
|
.padding(Gaps::new_symmetric(8., 14.))
|
|
.maybe(!self.last, |el| {
|
|
el.border(
|
|
Border::new()
|
|
.width(BorderWidth {
|
|
bottom: 1.,
|
|
..Default::default()
|
|
})
|
|
.fill(colors::component_border()),
|
|
)
|
|
})
|
|
.child(cell(
|
|
format!("{} {}", runtime.vendor, runtime.major),
|
|
Size::px(130.),
|
|
))
|
|
.child(cell(runtime.version.clone(), Size::px(90.)))
|
|
.child(
|
|
rect()
|
|
.width(Size::flex(1.0))
|
|
.height(Size::px(path_height))
|
|
.overflow(Overflow::Clip)
|
|
.on_sized(move |e: Event<SizedEventData>| {
|
|
let w = e.area.width();
|
|
if (*viewport_w.read() - w).abs() > 0.5 {
|
|
viewport_w.set(w);
|
|
}
|
|
})
|
|
.child(
|
|
ScrollArea::new()
|
|
.horizontal(content_w)
|
|
.width(Size::fill())
|
|
.height(Size::px(path_height))
|
|
.show_scrollbar(false)
|
|
.child(
|
|
label()
|
|
.text(runtime.absolute_path.clone())
|
|
.font_size(12.)
|
|
.max_lines(1)
|
|
.color(colors::fg_secondary())
|
|
.into_element(),
|
|
),
|
|
),
|
|
)
|
|
.child(remove_button(self.pending_remove, path))
|
|
}
|
|
}
|
|
|
|
fn path_content_width(path: &str) -> f32 {
|
|
(path.chars().count() as f32 * 7.0).max(1.0)
|
|
}
|
|
|
|
#[derive(Clone, PartialEq)]
|
|
struct PendingRemove {
|
|
path: String,
|
|
/// Whether the files live in OneClient's own java dir, which is the only
|
|
/// case where removal takes them off disk
|
|
managed: bool,
|
|
}
|
|
|
|
fn confirm_remove_modal(
|
|
dispatch: Actions,
|
|
mut pending: State<Option<PendingRemove>>,
|
|
target: PendingRemove,
|
|
) -> impl IntoElement {
|
|
let PendingRemove { path, managed } = target;
|
|
let remove_path = path.clone();
|
|
|
|
let consequence = if managed {
|
|
"This runtime is managed by OneClient. Its files WILL be deleted."
|
|
} else {
|
|
"This runtime is not managed by OneClient. Its files will not be deleted."
|
|
};
|
|
|
|
let confirm = if managed {
|
|
Button::new().danger().text("Remove and delete files")
|
|
} else {
|
|
Button::new().primary().text("Remove from list")
|
|
};
|
|
|
|
OverlayPopup::new()
|
|
.on_close(move |()| pending.set(None))
|
|
.child(
|
|
rect()
|
|
.width(Size::window_percent(100.))
|
|
.height(Size::window_percent(100.))
|
|
.center()
|
|
.child(
|
|
rect()
|
|
.vertical()
|
|
.width(Size::px(440.))
|
|
.max_width(Size::window_percent(90.))
|
|
.spacing(14.)
|
|
.padding(Gaps::new_all(20.))
|
|
.corner_radius(CornerRadius::new_all(14.))
|
|
.background(colors::page_elevated())
|
|
.child(
|
|
label()
|
|
.text("Remove Java Runtime?")
|
|
.font_size(16.)
|
|
.font_weight(FontWeight::SEMI_BOLD)
|
|
.color(colors::fg_primary()),
|
|
)
|
|
.child(
|
|
label()
|
|
.text(consequence)
|
|
.font_size(12.)
|
|
.max_lines(4)
|
|
.width(Size::fill())
|
|
.color(colors::fg_secondary()),
|
|
)
|
|
.child(
|
|
label()
|
|
.text(path)
|
|
.font_size(12.)
|
|
.max_lines(3)
|
|
.width(Size::fill())
|
|
.color(colors::fg_secondary()),
|
|
)
|
|
.child(
|
|
rect()
|
|
.horizontal()
|
|
.width(Size::fill())
|
|
.main_align(Alignment::End)
|
|
.spacing(8.)
|
|
.child(
|
|
Button::new()
|
|
.secondary()
|
|
.on_press(move |_| pending.set(None))
|
|
.text("Cancel"),
|
|
)
|
|
.child(confirm.on_press(move |_| {
|
|
dispatch.remove_java_runtime(remove_path.clone());
|
|
pending.set(None);
|
|
})),
|
|
),
|
|
),
|
|
)
|
|
.into_element()
|
|
}
|
|
|
|
fn remove_button(
|
|
mut pending_remove: State<Option<PendingRemove>>,
|
|
path: String,
|
|
) -> impl IntoElement {
|
|
Button::new()
|
|
.ghost()
|
|
.small()
|
|
.on_press(move |_| {
|
|
pending_remove.set(Some(PendingRemove {
|
|
managed: is_launcher_managed(Path::new(&path)),
|
|
path: path.clone(),
|
|
}));
|
|
})
|
|
.child(
|
|
Icon::new(IconType::Trash01)
|
|
.size(14.)
|
|
.color(colors::danger()),
|
|
)
|
|
.into_element()
|
|
} |