mirror of
https://github.com/librekeys/picoforge.git
synced 2026-07-28 08:01:19 -07:00
feat(ui): extract dialogs into a custom component
This commit is contained in:
Generated
+93
-93
File diff suppressed because it is too large
Load Diff
+1
-1
@@ -16,7 +16,7 @@ directories = "6" # For Applcation config/data dir handling
|
||||
|
||||
# For device management backend:
|
||||
pcsc = "2" # Standard Smart Card API (connect to the key)
|
||||
hex = "0.4" # For parsing "CAFE:4242" VID/PID strings
|
||||
hex = "0.4" # For parsing VID/PID strings
|
||||
byteorder = "1.5" # Required for writing Big-Endian numbers (firmware requirement)
|
||||
thiserror = "2" # Makes custom error handling much easier
|
||||
anyhow = "1" # For easy error propagation
|
||||
|
||||
Generated
+3
-3
@@ -20,11 +20,11 @@
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1771008912,
|
||||
"narHash": "sha256-gf2AmWVTs8lEq7z/3ZAsgnZDhWIckkb+ZnAo5RzSxJg=",
|
||||
"lastModified": 1771369470,
|
||||
"narHash": "sha256-0NBlEBKkN3lufyvFegY4TYv5mCNHbi5OmBDrzihbBMQ=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "a82ccc39b39b621151d6732718e3e250109076fa",
|
||||
"rev": "0182a361324364ae3f436a63005877674cf45efb",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
||||
@@ -0,0 +1,185 @@
|
||||
use gpui::*;
|
||||
use gpui_component::{
|
||||
WindowExt,
|
||||
button::{Button, ButtonVariant, ButtonVariants},
|
||||
dialog::DialogButtonProps,
|
||||
input::{Input, InputState},
|
||||
v_flex,
|
||||
};
|
||||
|
||||
pub fn open_pin_prompt(
|
||||
title: &str,
|
||||
description: &str,
|
||||
confirm_label: &str,
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
on_confirm: impl Fn(String, &mut Window, &mut App) + 'static,
|
||||
) {
|
||||
let title = SharedString::from(title.to_string());
|
||||
let description = SharedString::from(description.to_string());
|
||||
let confirm_label = SharedString::from(confirm_label.to_string());
|
||||
|
||||
let pin_input = cx.new(|cx| {
|
||||
InputState::new(window, cx)
|
||||
.placeholder("Enter FIDO PIN")
|
||||
.masked(true)
|
||||
});
|
||||
|
||||
let on_confirm = std::rc::Rc::new(on_confirm);
|
||||
|
||||
window.open_dialog(cx, move |dialog, _, _| {
|
||||
let pin_input_for_footer = pin_input.clone();
|
||||
let confirm_label = confirm_label.clone();
|
||||
let on_confirm = on_confirm.clone();
|
||||
|
||||
dialog
|
||||
.title(title.clone())
|
||||
.child(
|
||||
v_flex()
|
||||
.gap_4()
|
||||
.pb_4()
|
||||
.child(description.clone())
|
||||
.child(Input::new(&pin_input)),
|
||||
)
|
||||
.footer(move |_, _, _, _| {
|
||||
let input = pin_input_for_footer.clone();
|
||||
let on_confirm = on_confirm.clone();
|
||||
|
||||
vec![
|
||||
Button::new("cancel")
|
||||
.label("Cancel")
|
||||
.on_click(|_, window, cx| {
|
||||
window.close_dialog(cx);
|
||||
}),
|
||||
Button::new("confirm")
|
||||
.primary()
|
||||
.label(confirm_label.clone())
|
||||
.on_click(move |_, window, cx| {
|
||||
let pin = input.read(cx).text().to_string();
|
||||
if !pin.is_empty() {
|
||||
window.close_dialog(cx);
|
||||
on_confirm(pin, window, cx);
|
||||
}
|
||||
}),
|
||||
]
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
pub fn open_confirm(
|
||||
title: &str,
|
||||
message: String,
|
||||
ok_label: &str,
|
||||
ok_variant: ButtonVariant,
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
on_ok: impl Fn(&mut Window, &mut App) + 'static,
|
||||
) {
|
||||
let title = SharedString::from(title.to_string());
|
||||
let ok_label = SharedString::from(ok_label.to_string());
|
||||
let on_ok = std::rc::Rc::new(on_ok);
|
||||
|
||||
window.open_dialog(cx, move |dialog, _, _| {
|
||||
let on_ok = on_ok.clone();
|
||||
|
||||
dialog
|
||||
.confirm()
|
||||
.title(title.clone())
|
||||
.child(div().pb_4().child(message.clone()))
|
||||
.on_ok(move |_, window, cx| {
|
||||
on_ok(window, cx);
|
||||
false
|
||||
})
|
||||
.on_cancel(|_, _, _| true)
|
||||
.button_props(
|
||||
DialogButtonProps::default()
|
||||
.ok_text(ok_label.clone())
|
||||
.ok_variant(ok_variant),
|
||||
)
|
||||
});
|
||||
}
|
||||
|
||||
pub fn open_change_pin(
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
on_error: impl Fn(&str, &mut App) + 'static + Clone,
|
||||
on_confirm: impl Fn(String, String, &mut App) + 'static,
|
||||
) {
|
||||
let current_pin = cx.new(|cx| {
|
||||
InputState::new(window, cx)
|
||||
.placeholder("Enter current PIN")
|
||||
.masked(true)
|
||||
});
|
||||
let new_pin = cx.new(|cx| {
|
||||
InputState::new(window, cx)
|
||||
.placeholder("Enter new PIN")
|
||||
.masked(true)
|
||||
});
|
||||
let confirm_pin = cx.new(|cx| {
|
||||
InputState::new(window, cx)
|
||||
.placeholder("Confirm new PIN")
|
||||
.masked(true)
|
||||
});
|
||||
|
||||
let on_confirm = std::rc::Rc::new(on_confirm);
|
||||
|
||||
window.open_dialog(cx, move |dialog, _, _| {
|
||||
let current = current_pin.clone();
|
||||
let new = new_pin.clone();
|
||||
let confirm = confirm_pin.clone();
|
||||
let on_error = on_error.clone();
|
||||
let on_confirm = on_confirm.clone();
|
||||
|
||||
dialog
|
||||
.title("Change PIN")
|
||||
.child("Enter your current PIN and choose a new one.")
|
||||
.child(
|
||||
v_flex()
|
||||
.gap_4()
|
||||
.pb_4()
|
||||
.child("Current PIN")
|
||||
.child(Input::new(¤t))
|
||||
.child("New PIN")
|
||||
.child(Input::new(&new))
|
||||
.child("Confirm New PIN")
|
||||
.child(Input::new(&confirm)),
|
||||
)
|
||||
.footer(move |_, _window, _cx, _| {
|
||||
let current = current.clone();
|
||||
let new = new.clone();
|
||||
let confirm = confirm.clone();
|
||||
let on_error = on_error.clone();
|
||||
let on_confirm = on_confirm.clone();
|
||||
|
||||
vec![
|
||||
Button::new("cancel")
|
||||
.label("Cancel")
|
||||
.on_click(|_, window, cx| window.close_dialog(cx)),
|
||||
Button::new("confirm")
|
||||
.primary()
|
||||
.label("Confirm")
|
||||
.on_click(move |_, _, cx| {
|
||||
let current_val = current.read(cx).text().to_string();
|
||||
let new_val = new.read(cx).text().to_string();
|
||||
let confirm_val = confirm.read(cx).text().to_string();
|
||||
|
||||
if current_val.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
if new_val != confirm_val {
|
||||
on_error("PINs do not match", cx);
|
||||
return;
|
||||
}
|
||||
|
||||
if new_val.len() < 4 {
|
||||
on_error("PIN too short", cx);
|
||||
return;
|
||||
}
|
||||
|
||||
on_confirm(current_val, new_val, cx);
|
||||
}),
|
||||
]
|
||||
})
|
||||
});
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
pub mod button;
|
||||
pub mod card;
|
||||
pub mod dialog;
|
||||
pub mod page_view;
|
||||
pub mod sidebar;
|
||||
|
||||
+19
-51
@@ -1,11 +1,11 @@
|
||||
use crate::device::io;
|
||||
use crate::device::types::{AppConfigInput, FullDeviceStatus};
|
||||
use crate::ui::components::{card::Card, page_view::PageView};
|
||||
use crate::ui::components::{card::Card, dialog, page_view::PageView};
|
||||
use crate::ui::types::{LedDriverType, UsbIdentityPreset};
|
||||
use gpui::*;
|
||||
use gpui_component::button::{ButtonCustomVariant, ButtonVariants};
|
||||
use gpui_component::{
|
||||
ActiveTheme, Disableable, Icon, Theme, WindowExt,
|
||||
ActiveTheme, Disableable, Icon, Theme,
|
||||
button::Button,
|
||||
input::{Input, InputState},
|
||||
select::{Select, SelectItem, SelectState},
|
||||
@@ -274,57 +274,25 @@ impl ConfigView {
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
let pin_input = cx.new(|cx| {
|
||||
InputState::new(window, cx)
|
||||
.placeholder("Enter FIDO PIN")
|
||||
.masked(true)
|
||||
});
|
||||
let view_handle = cx.entity().downgrade();
|
||||
|
||||
window.open_dialog(cx, move |dialog, _, _| {
|
||||
let view_handle_for_footer = view_handle.clone();
|
||||
let pin_input_for_footer = pin_input.clone();
|
||||
let changes = changes.clone();
|
||||
|
||||
dialog
|
||||
.title("Authentication Required")
|
||||
.child(
|
||||
v_flex()
|
||||
.gap_4()
|
||||
.pb_4()
|
||||
.child("Enter your device PIN to apply changes.")
|
||||
.child(Input::new(&pin_input)),
|
||||
)
|
||||
.footer(move |_, _, _, _| {
|
||||
let view = view_handle_for_footer.clone();
|
||||
let input = pin_input_for_footer.clone();
|
||||
let changes = changes.clone();
|
||||
|
||||
vec![
|
||||
Button::new("cancel")
|
||||
.label("Cancel")
|
||||
.on_click(|_, window, cx| {
|
||||
window.close_dialog(cx);
|
||||
}),
|
||||
Button::new("confirm").primary().label("Confirm").on_click(
|
||||
move |_, window, cx| {
|
||||
let pin = input.read(cx).text().to_string();
|
||||
if !pin.is_empty() {
|
||||
window.close_dialog(cx);
|
||||
let _ = view.update(cx, |this, cx| {
|
||||
this.write_config_to_device(
|
||||
changes.clone(),
|
||||
crate::device::types::DeviceMethod::Fido,
|
||||
Some(pin),
|
||||
cx,
|
||||
);
|
||||
});
|
||||
}
|
||||
},
|
||||
),
|
||||
]
|
||||
})
|
||||
});
|
||||
dialog::open_pin_prompt(
|
||||
"Authentication Required",
|
||||
"Enter your device PIN to apply changes.",
|
||||
"Confirm",
|
||||
window,
|
||||
cx,
|
||||
move |pin, _, cx| {
|
||||
let _ = view_handle.update(cx, |this, cx| {
|
||||
this.write_config_to_device(
|
||||
changes.clone(),
|
||||
crate::device::types::DeviceMethod::Fido,
|
||||
Some(pin),
|
||||
cx,
|
||||
);
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
fn apply_changes(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
||||
|
||||
+41
-150
@@ -3,6 +3,7 @@ use crate::device::types::{FidoDeviceInfo, FullDeviceStatus, StoredCredential};
|
||||
use crate::ui::components::{
|
||||
button::{PFButton, PFIconButton},
|
||||
card::Card,
|
||||
dialog,
|
||||
page_view::PageView,
|
||||
};
|
||||
use gpui::*;
|
||||
@@ -172,49 +173,20 @@ impl PasskeysView {
|
||||
}
|
||||
|
||||
fn open_unlock_dialog(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
||||
let pin_input = cx.new(|cx| {
|
||||
InputState::new(window, cx)
|
||||
.placeholder("Enter FIDO PIN")
|
||||
.masked(true)
|
||||
});
|
||||
let view_handle = cx.entity().downgrade();
|
||||
|
||||
window.open_dialog(cx, move |dialog, _, _| {
|
||||
let view_handle_for_footer = view_handle.clone();
|
||||
let pin_input_for_footer = pin_input.clone();
|
||||
|
||||
dialog
|
||||
.title("Unlock Storage")
|
||||
.child(
|
||||
v_flex()
|
||||
.gap_4()
|
||||
.pb_4()
|
||||
.child("Enter your device PIN to view saved passkeys")
|
||||
.child(Input::new(&pin_input)),
|
||||
)
|
||||
.footer(move |_, _, _, _| {
|
||||
let view = view_handle_for_footer.clone();
|
||||
let input = pin_input_for_footer.clone();
|
||||
|
||||
vec![
|
||||
Button::new("cancel")
|
||||
.label("Cancel")
|
||||
.on_click(|_, window, cx| {
|
||||
window.close_dialog(cx);
|
||||
}),
|
||||
Button::new("unlock").primary().label("Unlock").on_click(
|
||||
move |_, _window, cx| {
|
||||
let pin = input.read(cx).text().to_string();
|
||||
if !pin.is_empty() {
|
||||
let _ = view.update(cx, |this, cx| {
|
||||
this.unlock_storage(pin, cx);
|
||||
});
|
||||
}
|
||||
},
|
||||
),
|
||||
]
|
||||
})
|
||||
});
|
||||
dialog::open_pin_prompt(
|
||||
"Unlock Storage",
|
||||
"Enter your device PIN to view saved passkeys",
|
||||
"Unlock",
|
||||
window,
|
||||
cx,
|
||||
move |pin, _, cx| {
|
||||
let _ = view_handle.update(cx, |this, cx| {
|
||||
this.unlock_storage(pin, cx);
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
fn open_delete_dialog(
|
||||
@@ -229,120 +201,39 @@ impl PasskeysView {
|
||||
let name = cred.rp_id.clone();
|
||||
let view_handle = cx.entity().downgrade();
|
||||
|
||||
window.open_dialog(cx, move |dialog, _, _| {
|
||||
let view_handle = view_handle.clone();
|
||||
let cred_id = cred_id.clone();
|
||||
let pin_str = pin_str.clone();
|
||||
|
||||
dialog
|
||||
.confirm()
|
||||
.title("Delete Passkey")
|
||||
.child(div().pb_4().child(format!(
|
||||
"Are you sure you want to delete the passkey for {}?",
|
||||
name
|
||||
)))
|
||||
.on_ok(move |_, _, cx| {
|
||||
let _ = view_handle.update(cx, |this, cx| {
|
||||
this.execute_delete(cred_id.clone(), pin_str.clone(), cx);
|
||||
});
|
||||
false
|
||||
})
|
||||
.on_cancel(|_, _, _| true)
|
||||
.button_props(
|
||||
gpui_component::dialog::DialogButtonProps::default()
|
||||
.ok_text("Delete")
|
||||
.ok_variant(ButtonVariant::Danger),
|
||||
)
|
||||
});
|
||||
dialog::open_confirm(
|
||||
"Delete Passkey",
|
||||
format!("Are you sure you want to delete the passkey for {}?", name),
|
||||
"Delete",
|
||||
ButtonVariant::Danger,
|
||||
window,
|
||||
cx,
|
||||
move |_, cx| {
|
||||
let _ = view_handle.update(cx, |this, cx| {
|
||||
this.execute_delete(cred_id.clone(), pin_str.clone(), cx);
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
fn open_change_pin_dialog(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
||||
let current_pin = cx.new(|cx| {
|
||||
InputState::new(window, cx)
|
||||
.placeholder("Enter current PIN")
|
||||
.masked(true)
|
||||
});
|
||||
let new_pin = cx.new(|cx| {
|
||||
InputState::new(window, cx)
|
||||
.placeholder("Enter new PIN")
|
||||
.masked(true)
|
||||
});
|
||||
let confirm_pin = cx.new(|cx| {
|
||||
InputState::new(window, cx)
|
||||
.placeholder("Confirm new PIN")
|
||||
.masked(true)
|
||||
});
|
||||
|
||||
let view_handle = cx.entity().downgrade();
|
||||
let view_for_error = cx.entity().downgrade();
|
||||
|
||||
window.open_dialog(cx, move |dialog, _, _| {
|
||||
let view = view_handle.clone();
|
||||
let current = current_pin.clone();
|
||||
let new = new_pin.clone();
|
||||
let confirm = confirm_pin.clone();
|
||||
|
||||
dialog
|
||||
.title("Change PIN")
|
||||
.child("Enter your current PIN and choose a new one.")
|
||||
.child(
|
||||
v_flex()
|
||||
.gap_4()
|
||||
.pb_4()
|
||||
.child("Current PIN")
|
||||
.child(Input::new(¤t))
|
||||
.child("New PIN")
|
||||
.child(Input::new(&new))
|
||||
.child("Confirm New PIN")
|
||||
.child(Input::new(&confirm)),
|
||||
)
|
||||
.footer(move |_, _window, _cx, _| {
|
||||
let view = view.clone();
|
||||
let current = current.clone();
|
||||
let new = new.clone();
|
||||
let confirm = confirm.clone();
|
||||
|
||||
vec![
|
||||
Button::new("cancel")
|
||||
.label("Cancel")
|
||||
.on_click(|_, window, cx| window.close_dialog(cx)),
|
||||
Button::new("confirm").primary().label("Confirm").on_click(
|
||||
move |_, _, cx| {
|
||||
let current_val = current.read(cx).text().to_string();
|
||||
let new_val = new.read(cx).text().to_string();
|
||||
let confirm_val = confirm.read(cx).text().to_string();
|
||||
|
||||
if current_val.is_empty() {
|
||||
return;
|
||||
// Todo: show error
|
||||
}
|
||||
|
||||
if new_val != confirm_val {
|
||||
// Todo: show validation error (toast)
|
||||
let _ = view.update(cx, |_, cx| {
|
||||
cx.emit(PasskeysEvent::Notification(
|
||||
"PINs do not match".to_string(),
|
||||
));
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if new_val.len() < 4 {
|
||||
let _ = view.update(cx, |_, cx| {
|
||||
cx.emit(PasskeysEvent::Notification(
|
||||
"PIN too short".to_string(),
|
||||
));
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
let _ = view.update(cx, |this, cx| {
|
||||
this.change_pin(current_val, new_val, cx);
|
||||
});
|
||||
},
|
||||
),
|
||||
]
|
||||
})
|
||||
});
|
||||
dialog::open_change_pin(
|
||||
window,
|
||||
cx,
|
||||
move |msg, cx| {
|
||||
let _ = view_for_error.update(cx, |_, cx| {
|
||||
cx.emit(PasskeysEvent::Notification(msg.to_string()));
|
||||
});
|
||||
},
|
||||
move |current, new, cx| {
|
||||
let _ = view_handle.update(cx, |this, cx| {
|
||||
this.change_pin(current, new, cx);
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
fn open_min_pin_length_dialog(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
||||
|
||||
Reference in New Issue
Block a user