Skip to content

Commit 2bc9bf4

Browse files
UI: Add confirmation dialog for network removal actions (#93)
1 parent 45e6eea commit 2bc9bf4

File tree

2 files changed

+207
-72
lines changed

2 files changed

+207
-72
lines changed

drasyl-ui/src/app.rs

Lines changed: 194 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ pub struct App {
3232
config_url: String,
3333
repaint_delay: std::time::Duration,
3434
token_path: String,
35+
// confirmation dialog state
36+
network_to_remove: Option<String>,
3537
}
3638

3739
impl App {
@@ -56,6 +58,7 @@ impl App {
5658
config_url: String::new(),
5759
repaint_delay: std::time::Duration::MAX,
5860
token_path,
61+
network_to_remove: None,
5962
}
6063
}
6164

@@ -270,7 +273,7 @@ impl App {
270273
// remove action
271274
let item = MenuItem::with_id(
272275
format!("remove_network {config_url_str}"),
273-
"Remove",
276+
"Remove",
274277
true,
275278
None,
276279
);
@@ -529,6 +532,7 @@ impl ApplicationHandler<UserEvent> for App {
529532
WindowEvent::CloseRequested => {
530533
trace!("Window close requested");
531534
self.config_url.clear();
535+
self.network_to_remove = None;
532536
self.gl_window.take();
533537
self.gl.take();
534538
self.egui_glow.take();
@@ -540,71 +544,155 @@ impl ApplicationHandler<UserEvent> for App {
540544
self.gl_window.as_mut().unwrap().window(),
541545
|egui_ctx| {
542546
egui::CentralPanel::default().show(egui_ctx, |ui| {
543-
// Helper function to add network
544-
let add_network = |url: String| {
545-
if Url::parse(url.trim()).is_ok() {
546-
// send event for processing
547-
self.proxy
548-
.send_event(UserEvent::AddNetwork(url))
549-
.expect("Failed to send event");
550-
true // return true to indicate success
547+
if let Some(network_url) = &self.network_to_remove {
548+
// Confirmation dialog for removing network
549+
// Get network name/URL for display
550+
let display_text = if let Some(Ok(status)) =
551+
self.status.lock().expect("Mutex poisoned").as_ref()
552+
{
553+
if let Ok(url) = Url::parse(network_url) {
554+
if let Some(network) = status.networks.get(&url) {
555+
match network.name.as_ref() {
556+
Some(name) => Self::sanitize_menu_text(name),
557+
None => mask_url(&url),
558+
}
559+
} else {
560+
network_url.clone()
561+
}
562+
} else {
563+
network_url.clone()
564+
}
551565
} else {
552-
false
553-
}
554-
};
555-
556-
// URL input field
557-
ui.label("Network configuration URL (https:// or file://):");
558-
let response = ui.add_sized(
559-
[ui.available_width() * 1.0, 0.0],
560-
egui::TextEdit::singleline(&mut self.config_url)
561-
.hint_text("https://example.com/network-config.toml"),
562-
);
563-
// automatically set focus on the text field
564-
response.request_focus();
565-
566-
// Check for Enter key press
567-
let input = ui.input(|i| i.clone());
568-
if input.key_pressed(egui::Key::Enter) {
569-
let url = self.config_url.clone();
570-
if add_network(url) {
566+
network_url.clone()
567+
};
568+
569+
ui.vertical_centered(|ui| {
570+
ui.label(format!(
571+
"Are you sure you want to remove the network »{}«?",
572+
display_text
573+
));
574+
});
575+
ui.add_space(10.0);
576+
577+
// Check for Escape key press
578+
let input = ui.input(|i| i.clone());
579+
if input.key_pressed(egui::Key::Escape) {
580+
trace!("Cancel remove action triggered");
571581
cancel = true;
572582
}
573-
}
574-
575-
// Check for Escape key press
576-
if input.key_pressed(egui::Key::Escape) {
577-
trace!("Cancel action triggered");
578-
cancel = true;
579-
}
580583

581-
ui.add_space(5.0);
582-
583-
// buttons
584-
ui.horizontal(|ui| {
585-
ui.with_layout(
586-
egui::Layout::right_to_left(egui::Align::Center),
587-
|ui| {
588-
if ui.button("Add").clicked() {
589-
let url = self.config_url.clone();
590-
if add_network(url) {
584+
// buttons - centered
585+
ui.horizontal(|ui| {
586+
ui.with_layout(
587+
egui::Layout::right_to_left(egui::Align::Center),
588+
|ui| {
589+
// Add flexible space to center the buttons
590+
ui.allocate_space(egui::Vec2::new(
591+
ui.available_width() * 0.5 - 60.0,
592+
0.0,
593+
));
594+
595+
if ui.button("Remove").clicked() {
596+
let url = network_url.clone();
597+
trace!(
598+
"Confirmed removing network with URL: {}",
599+
url
600+
);
601+
602+
self.rt.block_on(async {
603+
let client =
604+
RestApiClient::new(self.token_path.clone());
605+
match client.remove_network(&url).await {
606+
Ok(_) => {
607+
trace!("Removed network: {url}");
608+
}
609+
Err(e) => {
610+
trace!(
611+
"Failed to remove network: {}",
612+
e
613+
);
614+
}
615+
}
616+
});
591617
cancel = true;
592618
}
593-
}
594619

595-
if ui.button("Cancel").clicked() {
596-
trace!("Cancel action triggered");
597-
cancel = true;
598-
}
599-
},
620+
if ui.button("Cancel").clicked() {
621+
trace!("Cancel remove action triggered");
622+
cancel = true;
623+
}
624+
},
625+
);
626+
});
627+
} else {
628+
// Add network dialog
629+
// Helper function to add network
630+
let add_network = |url: String| {
631+
if Url::parse(url.trim()).is_ok() {
632+
// send event for processing
633+
self.proxy
634+
.send_event(UserEvent::AddNetwork(url))
635+
.expect("Failed to send event");
636+
true // return true to indicate success
637+
} else {
638+
false
639+
}
640+
};
641+
642+
// URL input field
643+
ui.label("Network configuration URL (https:// or file://):");
644+
let response = ui.add_sized(
645+
[ui.available_width() * 1.0, 0.0],
646+
egui::TextEdit::singleline(&mut self.config_url)
647+
.hint_text("https://example.com/network-config.toml"),
600648
);
601-
});
649+
// automatically set focus on the text field
650+
response.request_focus();
651+
652+
// Check for Enter key press
653+
let input = ui.input(|i| i.clone());
654+
if input.key_pressed(egui::Key::Enter) {
655+
let url = self.config_url.clone();
656+
if add_network(url) {
657+
cancel = true;
658+
}
659+
}
660+
661+
// Check for Escape key press
662+
if input.key_pressed(egui::Key::Escape) {
663+
trace!("Cancel action triggered");
664+
cancel = true;
665+
}
666+
667+
ui.add_space(10.0);
668+
669+
// buttons
670+
ui.horizontal(|ui| {
671+
ui.with_layout(
672+
egui::Layout::right_to_left(egui::Align::Center),
673+
|ui| {
674+
if ui.button("Add").clicked() {
675+
let url = self.config_url.clone();
676+
if add_network(url) {
677+
cancel = true;
678+
}
679+
}
680+
681+
if ui.button("Cancel").clicked() {
682+
trace!("Cancel action triggered");
683+
cancel = true;
684+
}
685+
},
686+
);
687+
});
688+
}
602689
});
603690
},
604691
);
605692

606693
if cancel {
607694
self.config_url.clear();
695+
self.network_to_remove = None;
608696
self.gl_window.take();
609697
self.gl.take();
610698
self.egui_glow.take();
@@ -713,11 +801,23 @@ impl ApplicationHandler<UserEvent> for App {
713801
id if id == MenuId::new("add_network") => {
714802
trace!("Add network item clicked");
715803

804+
// Close all existing windows/dialogs first
805+
self.config_url.clear();
806+
self.network_to_remove = None;
807+
self.gl_window.take();
808+
self.gl.take();
809+
self.egui_glow.take();
810+
716811
// create window if it doesn't exist yet
717812
if let Some(gl_window) = self.gl_window.as_mut() {
718813
gl_window.window().focus_window();
719814
} else {
720-
let (gl_window, gl) = crate::glow_tools::create_display(event_loop);
815+
let (gl_window, gl) = crate::glow_tools::create_display(
816+
event_loop,
817+
"Add Network",
818+
500.0,
819+
100.0,
820+
);
721821
let gl = Arc::new(gl);
722822
gl_window.window().set_visible(true);
723823
gl_window.window().focus_window();
@@ -750,19 +850,48 @@ impl ApplicationHandler<UserEvent> for App {
750850
id if id.0.starts_with("remove_network ") => {
751851
let url = id.0.split_once(' ').unwrap().1;
752852

753-
trace!("Removing network with URL: {}", url);
853+
trace!("Remove network confirmation dialog for URL: {}", url);
754854

755-
self.rt.block_on(async {
756-
let client = RestApiClient::new(self.token_path.clone());
757-
match client.remove_network(url).await {
758-
Ok(_) => {
759-
trace!("Removed network: {url}");
760-
}
761-
Err(e) => {
762-
trace!("Failed to remove network: {}", e);
763-
}
764-
}
765-
});
855+
// Close all existing windows/dialogs first
856+
self.config_url.clear();
857+
self.network_to_remove = None;
858+
self.gl_window.take();
859+
self.gl.take();
860+
self.egui_glow.take();
861+
862+
// Store the network URL to be removed and show confirmation dialog
863+
self.network_to_remove = Some(url.to_string());
864+
865+
// Create window if it doesn't exist yet
866+
if let Some(gl_window) = self.gl_window.as_mut() {
867+
gl_window.window().focus_window();
868+
} else {
869+
let (gl_window, gl) = crate::glow_tools::create_display(
870+
event_loop,
871+
"Remove Network",
872+
400.0,
873+
80.0,
874+
);
875+
let gl = Arc::new(gl);
876+
gl_window.window().set_visible(true);
877+
gl_window.window().focus_window();
878+
879+
let egui_glow =
880+
egui_glow::EguiGlow::new(event_loop, gl.clone(), None, None, true);
881+
882+
let event_loop_proxy = egui::mutex::Mutex::new(self.proxy.clone());
883+
egui_glow
884+
.egui_ctx
885+
.set_request_repaint_callback(move |info| {
886+
event_loop_proxy
887+
.lock()
888+
.send_event(UserEvent::Redraw(info.delay))
889+
.expect("Cannot send event");
890+
});
891+
self.gl_window = Some(gl_window);
892+
self.gl = Some(gl);
893+
self.egui_glow = Some(egui_glow);
894+
}
766895
}
767896
id if id.0.starts_with("network_enabled ") => {
768897
let url = id.0.split_once(' ').unwrap().1;

drasyl-ui/src/glow_tools.rs

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,12 @@ use tracing::debug;
44

55
pub fn create_display(
66
event_loop: &winit::event_loop::ActiveEventLoop,
7+
title: &str,
8+
width: f64,
9+
height: f64,
710
) -> (GlutinWindowContext, glow::Context) {
8-
let glutin_window_context = unsafe { GlutinWindowContext::new(event_loop) };
11+
let glutin_window_context =
12+
unsafe { GlutinWindowContext::new(event_loop, title, width, height) };
913
let gl = unsafe {
1014
glow::Context::from_loader_function(|s| {
1115
let s = std::ffi::CString::new(s)
@@ -30,7 +34,12 @@ impl GlutinWindowContext {
3034
// refactor this function to use `glutin-winit` crate eventually.
3135
// preferably add android support at the same time.
3236
#[expect(unsafe_code)]
33-
unsafe fn new(event_loop: &winit::event_loop::ActiveEventLoop) -> Self {
37+
unsafe fn new(
38+
event_loop: &winit::event_loop::ActiveEventLoop,
39+
title: &str,
40+
width: f64,
41+
height: f64,
42+
) -> Self {
3443
use glutin::context::NotCurrentGlContext as _;
3544
use glutin::display::GetGlDisplay as _;
3645
use glutin::display::GlDisplay as _;
@@ -39,11 +48,8 @@ impl GlutinWindowContext {
3948
use winit::raw_window_handle::HasWindowHandle;
4049
let winit_window_builder = winit::window::WindowAttributes::default()
4150
.with_resizable(true)
42-
.with_inner_size(winit::dpi::LogicalSize {
43-
width: 500.0,
44-
height: 100.0,
45-
})
46-
.with_title("Add Network") // Keep hidden until we've painted something. See https://github.com/emilk/egui/pull/2279
51+
.with_inner_size(winit::dpi::LogicalSize { width, height })
52+
.with_title(title) // Keep hidden until we've painted something. See https://github.com/emilk/egui/pull/2279
4753
.with_visible(false);
4854

4955
let config_template_builder = glutin::config::ConfigTemplateBuilder::new()

0 commit comments

Comments
 (0)