mirror of
https://github.com/librekeys/picoforge.git
synced 2026-07-28 08:01:19 -07:00
feat(ui): implement config loading from the device module
This commit is contained in:
+9
-3
@@ -151,9 +151,15 @@ impl Render for ApplicationRoot {
|
||||
ActiveView::Passkeys => {
|
||||
PasskeysView::build(cx.theme()).into_any_element()
|
||||
}
|
||||
ActiveView::Configuration => {
|
||||
cx.new(|cx| ConfigView::new(window, cx)).into_any_element()
|
||||
}
|
||||
ActiveView::Configuration => cx
|
||||
.new(|cx| {
|
||||
ConfigView::new(
|
||||
window,
|
||||
cx,
|
||||
self.state.device_status.clone(),
|
||||
)
|
||||
})
|
||||
.into_any_element(),
|
||||
ActiveView::Security => {
|
||||
SecurityView::build(cx).into_any_element()
|
||||
}
|
||||
|
||||
+201
-14
@@ -1,3 +1,5 @@
|
||||
use crate::device::io;
|
||||
use crate::device::types::{AppConfigInput, FullDeviceStatus};
|
||||
use crate::ui::components::page_view::PageView;
|
||||
use gpui::*;
|
||||
use gpui_component::{
|
||||
@@ -60,10 +62,17 @@ pub struct ConfigView {
|
||||
power_cycle: bool,
|
||||
enable_secp256k1: bool,
|
||||
loading: bool,
|
||||
device_status: Option<FullDeviceStatus>,
|
||||
}
|
||||
|
||||
impl ConfigView {
|
||||
pub fn new(window: &mut Window, cx: &mut Context<Self>) -> Self {
|
||||
pub fn new(
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
device_status: Option<FullDeviceStatus>,
|
||||
) -> Self {
|
||||
let config = device_status.as_ref().map(|s| &s.config);
|
||||
|
||||
let vendors = vec![
|
||||
VendorItem {
|
||||
value: "custom".into(),
|
||||
@@ -107,11 +116,37 @@ impl ConfigView {
|
||||
)
|
||||
});
|
||||
|
||||
let vid_input = cx.new(|cx| InputState::new(window, cx).default_value("CAFE"));
|
||||
let pid_input = cx.new(|cx| InputState::new(window, cx).default_value("4242"));
|
||||
let product_name_input = cx.new(|cx| InputState::new(window, cx).default_value("My Key"));
|
||||
let vid_input = cx.new(|cx| {
|
||||
InputState::new(window, cx).default_value(
|
||||
config
|
||||
.map(|c| c.vid.clone())
|
||||
.unwrap_or_else(|| "CAFE".into()),
|
||||
)
|
||||
});
|
||||
let pid_input = cx.new(|cx| {
|
||||
InputState::new(window, cx).default_value(
|
||||
config
|
||||
.map(|c| c.pid.clone())
|
||||
.unwrap_or_else(|| "4242".into()),
|
||||
)
|
||||
});
|
||||
let product_name_input = cx.new(|cx| {
|
||||
InputState::new(window, cx).default_value(
|
||||
config
|
||||
.map(|c| c.product_name.clone())
|
||||
.unwrap_or_else(|| "My Key".into()),
|
||||
)
|
||||
});
|
||||
|
||||
let led_gpio_input = cx.new(|cx| InputState::new(window, cx).default_value("25"));
|
||||
let led_gpio_input = cx.new(|cx| {
|
||||
InputState::new(window, cx).default_value(
|
||||
config
|
||||
.map(|c| c.led_gpio.to_string())
|
||||
.unwrap_or_else(|| "25".into()),
|
||||
)
|
||||
});
|
||||
|
||||
let _initial_driver_idx = config.and_then(|c| c.led_driver).unwrap_or(0) as usize;
|
||||
let led_driver_select = cx.new(|cx| {
|
||||
SelectState::new(
|
||||
drivers,
|
||||
@@ -121,15 +156,26 @@ impl ConfigView {
|
||||
)
|
||||
});
|
||||
|
||||
// Set initial selection for driver?
|
||||
// Note: SelectState currently defaults to first item if index path is default.
|
||||
// We'd strictly need to set the index path based on value.
|
||||
// For now trusting default or user interaction.
|
||||
|
||||
let led_brightness_slider = cx.new(|_| {
|
||||
SliderState::new()
|
||||
.min(0.0)
|
||||
.max(15.0)
|
||||
.step(1.0)
|
||||
.default_value(8.0)
|
||||
.default_value(config.map(|c| c.led_brightness as f32).unwrap_or(8.0))
|
||||
});
|
||||
|
||||
let touch_timeout_input = cx.new(|cx| InputState::new(window, cx).default_value("10"));
|
||||
let touch_timeout_input = cx.new(|cx| {
|
||||
InputState::new(window, cx).default_value(
|
||||
config
|
||||
.map(|c| c.touch_timeout.to_string())
|
||||
.unwrap_or_else(|| "10".into()),
|
||||
)
|
||||
});
|
||||
|
||||
Self {
|
||||
vendor_select,
|
||||
@@ -139,15 +185,134 @@ impl ConfigView {
|
||||
led_gpio_input,
|
||||
led_driver_select,
|
||||
led_brightness_slider,
|
||||
led_dimmable: true,
|
||||
led_steady: false,
|
||||
led_dimmable: config.map(|c| c.led_dimmable).unwrap_or(true),
|
||||
led_steady: config.map(|c| c.led_steady).unwrap_or(false),
|
||||
touch_timeout_input,
|
||||
power_cycle: false,
|
||||
enable_secp256k1: true,
|
||||
power_cycle: config.map(|c| c.power_cycle_on_reset).unwrap_or(false),
|
||||
enable_secp256k1: config.map(|c| c.enable_secp256k1).unwrap_or(true),
|
||||
loading: false,
|
||||
device_status,
|
||||
}
|
||||
}
|
||||
|
||||
fn apply_changes(&mut self, cx: &mut Context<Self>) {
|
||||
let status = if let Some(s) = &self.device_status {
|
||||
s
|
||||
} else {
|
||||
return;
|
||||
};
|
||||
|
||||
let current_config = &status.config;
|
||||
let mut changes = AppConfigInput {
|
||||
vid: None,
|
||||
pid: None,
|
||||
product_name: None,
|
||||
led_gpio: None,
|
||||
led_brightness: None,
|
||||
touch_timeout: None,
|
||||
led_driver: None,
|
||||
led_dimmable: None,
|
||||
power_cycle_on_reset: None,
|
||||
led_steady: None,
|
||||
enable_secp256k1: None,
|
||||
};
|
||||
|
||||
let vid = self.vid_input.read(cx).text().to_string();
|
||||
if vid != current_config.vid {
|
||||
changes.vid = Some(vid);
|
||||
}
|
||||
|
||||
let pid = self.pid_input.read(cx).text().to_string();
|
||||
if pid != current_config.pid {
|
||||
changes.pid = Some(pid);
|
||||
}
|
||||
|
||||
let product_name = self.product_name_input.read(cx).text().to_string();
|
||||
if product_name != current_config.product_name {
|
||||
changes.product_name = Some(product_name);
|
||||
}
|
||||
|
||||
let led_gpio_str = self.led_gpio_input.read(cx).text().to_string();
|
||||
if let Ok(val) = led_gpio_str.parse::<u8>() {
|
||||
if val != current_config.led_gpio {
|
||||
changes.led_gpio = Some(val);
|
||||
}
|
||||
}
|
||||
|
||||
let driver_idx = self.led_driver_select.read(cx).selected_index(cx);
|
||||
if let Some(idx) = driver_idx {
|
||||
// Assuming values are 0, 1, 2 matches index
|
||||
let val = idx.row as u8;
|
||||
if Some(val) != current_config.led_driver {
|
||||
changes.led_driver = Some(val);
|
||||
}
|
||||
}
|
||||
|
||||
let brightness = self.led_brightness_slider.read(cx).value().start() as u8;
|
||||
if brightness != current_config.led_brightness {
|
||||
changes.led_brightness = Some(brightness);
|
||||
}
|
||||
|
||||
let touch_timeout_str = self.touch_timeout_input.read(cx).text().to_string();
|
||||
if let Ok(val) = touch_timeout_str.parse::<u8>() {
|
||||
if val != current_config.touch_timeout {
|
||||
changes.touch_timeout = Some(val);
|
||||
}
|
||||
}
|
||||
|
||||
if self.led_dimmable != current_config.led_dimmable {
|
||||
changes.led_dimmable = Some(self.led_dimmable);
|
||||
}
|
||||
|
||||
if self.led_steady != current_config.led_steady {
|
||||
changes.led_steady = Some(self.led_steady);
|
||||
}
|
||||
|
||||
if self.power_cycle != current_config.power_cycle_on_reset {
|
||||
changes.power_cycle_on_reset = Some(self.power_cycle);
|
||||
}
|
||||
|
||||
if self.enable_secp256k1 != current_config.enable_secp256k1 {
|
||||
changes.enable_secp256k1 = Some(self.enable_secp256k1);
|
||||
}
|
||||
|
||||
// Check if we have any changes
|
||||
let has_changes = changes.vid.is_some()
|
||||
|| changes.pid.is_some()
|
||||
|| changes.product_name.is_some()
|
||||
|| changes.led_gpio.is_some()
|
||||
|| changes.led_brightness.is_some()
|
||||
|| changes.touch_timeout.is_some()
|
||||
|| changes.led_driver.is_some()
|
||||
|| changes.led_dimmable.is_some()
|
||||
|| changes.power_cycle_on_reset.is_some()
|
||||
|| changes.led_steady.is_some()
|
||||
|| changes.enable_secp256k1.is_some();
|
||||
|
||||
if !has_changes {
|
||||
println!("No changes detected");
|
||||
return;
|
||||
}
|
||||
|
||||
self.loading = true;
|
||||
cx.notify();
|
||||
|
||||
let result = io::write_config(changes, status.method.clone(), None);
|
||||
|
||||
self.loading = false;
|
||||
|
||||
match result {
|
||||
Ok(msg) => {
|
||||
println!("Success: {}", msg);
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("Error saving config: {}", e);
|
||||
}
|
||||
}
|
||||
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
fn render_identity_card(&self, theme: &Theme) -> impl IntoElement {
|
||||
let content = v_flex()
|
||||
.gap_4()
|
||||
@@ -408,6 +573,27 @@ impl ConfigView {
|
||||
|
||||
impl Render for ConfigView {
|
||||
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
let theme = cx.theme();
|
||||
|
||||
// If not connected, show placeholder
|
||||
if self.device_status.is_none() {
|
||||
return PageView::build(
|
||||
"Configuration",
|
||||
"Customize device settings and behavior.",
|
||||
div()
|
||||
.flex()
|
||||
.items_center()
|
||||
.justify_center()
|
||||
.h_64()
|
||||
.border_1()
|
||||
.border_color(theme.border)
|
||||
.rounded_xl()
|
||||
.child(div().text_color(theme.muted_foreground).child("No Content")),
|
||||
theme,
|
||||
)
|
||||
.into_any_element();
|
||||
}
|
||||
|
||||
// I need to call mutable methods first.
|
||||
let led_card = self.render_led_card(cx).into_any_element();
|
||||
let options_card = self.render_options_card(cx).into_any_element();
|
||||
@@ -439,12 +625,13 @@ impl Render for ConfigView {
|
||||
.icon(Icon::default().path("icons/save.svg"))
|
||||
.child("Apply Changes")
|
||||
.disabled(self.loading)
|
||||
.on_click(|_, _, _| {
|
||||
println!("Save clicked");
|
||||
}),
|
||||
.on_click(cx.listener(|this, _, _, cx| {
|
||||
this.apply_changes(cx);
|
||||
})),
|
||||
),
|
||||
),
|
||||
&theme,
|
||||
)
|
||||
.into_any_element()
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user