mirror of
https://github.com/librekeys/picoforge.git
synced 2026-07-28 08:01:19 -07:00
refactor: extract screens into view/view_model dirs, add AppModels DI, switch to Entity<DeviceRepo>
- Decompose flat about.rs/home.rs/security.rs/config.rs/passkeys.rs into per-screen directories with mod.rs, view.rs, view_model.rs - Extract ApplicationRoot, LayoutState, ActiveView, ViewModelStore into new app.rs - Replace DeviceConnectionState (snapshot-based) with reactive Entity<DeviceRepo> - Replace ViewCache with ViewModelStore using uniform get_or_insert_with lazy init - Add AppModels DI bag shared across all view constructors - Move side types (UsbIdentityPreset, LedDriverType, etc.) into local view_model files - Delete src/ui/types.rs
This commit is contained in:
+2
-2
@@ -819,7 +819,7 @@ use std::rc::Rc;
|
||||
use gpui::*;
|
||||
use gpui_component::Root;
|
||||
use gpui_component::{Theme, ThemeMode, ThemeSet};
|
||||
use ui::rootview::ApplicationRoot;
|
||||
use ui::app::ApplicationRoot;
|
||||
|
||||
pub mod error;
|
||||
mod hal;
|
||||
@@ -837,7 +837,7 @@ fn main() {
|
||||
// Register sidebar toggle keybinding
|
||||
cx.bind_keys([gpui::KeyBinding::new(
|
||||
"ctrl-shift-d",
|
||||
ui::rootview::ToggleSidebar,
|
||||
ui::app::ToggleSidebar,
|
||||
None,
|
||||
)]);
|
||||
|
||||
|
||||
+170
@@ -0,0 +1,170 @@
|
||||
use crate::hal::io;
|
||||
use crate::hal::types::{DeviceMethod, FirmwareType};
|
||||
use crate::ui::models::device::DeviceRepo;
|
||||
use crate::ui::screens::{
|
||||
about::AboutViewModel, config::ConfigView, home::HomeViewModel, passkeys::PasskeysView,
|
||||
security::SecurityViewModel,
|
||||
};
|
||||
use gpui::*;
|
||||
|
||||
gpui::actions!(picoforge, [ToggleSidebar]);
|
||||
|
||||
pub struct AppModels {
|
||||
pub device: Entity<DeviceRepo>,
|
||||
}
|
||||
|
||||
pub struct ViewModelStore {
|
||||
pub home: Option<Entity<HomeViewModel>>,
|
||||
pub about: Option<Entity<AboutViewModel>>,
|
||||
pub security: Option<Entity<SecurityViewModel>>,
|
||||
pub passkeys: Option<Entity<PasskeysView>>,
|
||||
pub config: Option<Entity<ConfigView>>,
|
||||
}
|
||||
|
||||
impl ViewModelStore {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
home: None,
|
||||
about: None,
|
||||
security: None,
|
||||
passkeys: None,
|
||||
config: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Debug)]
|
||||
pub enum ActiveView {
|
||||
Home,
|
||||
Passkeys,
|
||||
Configuration,
|
||||
Security,
|
||||
About,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct LayoutState {
|
||||
pub active_view: ActiveView,
|
||||
pub is_sidebar_collapsed: bool,
|
||||
pub sidebar_toggle_hovered: bool,
|
||||
pub sidebar_width: Pixels,
|
||||
}
|
||||
|
||||
impl LayoutState {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
active_view: ActiveView::Home,
|
||||
is_sidebar_collapsed: false,
|
||||
sidebar_toggle_hovered: false,
|
||||
sidebar_width: px(255.),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ApplicationRoot {
|
||||
pub models: AppModels,
|
||||
pub view_state: LayoutState,
|
||||
pub views_store: ViewModelStore,
|
||||
pub focus_handle: FocusHandle,
|
||||
}
|
||||
|
||||
impl ApplicationRoot {
|
||||
pub fn new(cx: &mut Context<Self>) -> Self {
|
||||
let device = cx.new(|_| DeviceRepo::new());
|
||||
|
||||
let mut this = Self {
|
||||
models: AppModels { device },
|
||||
view_state: LayoutState::new(),
|
||||
views_store: ViewModelStore::new(),
|
||||
focus_handle: cx.focus_handle(),
|
||||
};
|
||||
|
||||
this.refresh_device_status(None, cx);
|
||||
this
|
||||
}
|
||||
|
||||
pub fn focus_handle(&self) -> FocusHandle {
|
||||
self.focus_handle.clone()
|
||||
}
|
||||
|
||||
pub(crate) fn refresh_device_status(
|
||||
&mut self,
|
||||
window: Option<&mut Window>,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
if self.models.device.read(cx).is_loading() {
|
||||
return;
|
||||
}
|
||||
|
||||
self.models.device.update(cx, |repo, _| repo.begin_load());
|
||||
cx.notify();
|
||||
|
||||
match io::read_device_details() {
|
||||
Ok(status) => {
|
||||
let device_changed = self
|
||||
.models
|
||||
.device
|
||||
.update(cx, |repo, _| repo.set_status(status.clone()));
|
||||
|
||||
let firmware_type = status.firmware_type;
|
||||
let method = status.method;
|
||||
|
||||
if device_changed {
|
||||
self.views_store.passkeys = None;
|
||||
} else if let Some(passkeys_view) = &self.views_store.passkeys {
|
||||
passkeys_view.update(cx, |view, cx| {
|
||||
view.refresh_if_unlocked(cx);
|
||||
});
|
||||
}
|
||||
|
||||
match io::get_fido_info() {
|
||||
Ok(fido) => {
|
||||
self.models
|
||||
.device
|
||||
.update(cx, |repo, _| repo.set_fido_info(Some(fido)));
|
||||
}
|
||||
Err(e) => {
|
||||
log::error!("FIDO Info fetch failed: {}", e);
|
||||
self.models
|
||||
.device
|
||||
.update(cx, |repo, _| repo.set_fido_info(None));
|
||||
}
|
||||
}
|
||||
|
||||
if firmware_type == FirmwareType::RSKey && method == DeviceMethod::Rescue {
|
||||
let led = io::read_led_config().ok();
|
||||
let mgmt = io::read_management_config().ok();
|
||||
self.models
|
||||
.device
|
||||
.update(cx, |repo, _| repo.set_auxiliary_data(led, mgmt));
|
||||
} else {
|
||||
self.models
|
||||
.device
|
||||
.update(cx, |repo, _| repo.clear_auxiliary_data());
|
||||
}
|
||||
|
||||
if let Some(config_view) = &self.views_store.config
|
||||
&& let Some(window) = window
|
||||
{
|
||||
config_view.update(cx, |view, cx| {
|
||||
view.sync_from_device(window, cx);
|
||||
});
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
self.models
|
||||
.device
|
||||
.update(cx, |repo, _| repo.set_error(format!("{}", e)));
|
||||
self.views_store.passkeys = None;
|
||||
}
|
||||
}
|
||||
|
||||
self.models.device.update(cx, |repo, _| repo.end_load());
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
pub fn toggle_sidebar(&mut self, cx: &mut Context<Self>) {
|
||||
self.view_state.is_sidebar_collapsed = !self.view_state.is_sidebar_collapsed;
|
||||
cx.notify();
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
use crate::hal::types::DeviceMethod;
|
||||
use crate::ui::app::{ActiveView, AppModels};
|
||||
use crate::ui::components::button::PFIconButton;
|
||||
use crate::ui::types::{ActiveView, DeviceConnectionState};
|
||||
use crate::ui::models::device::DeviceRepo;
|
||||
use gpui::*;
|
||||
use gpui_component::{
|
||||
ActiveTheme, Icon, IconName, Side,
|
||||
@@ -18,7 +19,7 @@ pub struct AppSidebar<V: 'static> {
|
||||
active_view: ActiveView,
|
||||
width: Pixels,
|
||||
collapsed: bool,
|
||||
state: DeviceConnectionState,
|
||||
device: Entity<DeviceRepo>,
|
||||
on_select: SelectHandler<V>,
|
||||
on_refresh: RefreshHandler<V>,
|
||||
}
|
||||
@@ -28,13 +29,13 @@ impl<V: 'static> AppSidebar<V> {
|
||||
active_view: ActiveView,
|
||||
width: Pixels,
|
||||
collapsed: bool,
|
||||
state: DeviceConnectionState,
|
||||
models: &AppModels,
|
||||
) -> Self {
|
||||
Self {
|
||||
active_view,
|
||||
width,
|
||||
collapsed,
|
||||
state,
|
||||
device: models.device.clone(),
|
||||
on_select: None,
|
||||
on_refresh: None,
|
||||
}
|
||||
@@ -59,7 +60,9 @@ impl<V: 'static> AppSidebar<V> {
|
||||
pub fn render(self, cx: &mut Context<V>) -> impl IntoElement {
|
||||
let width = self.width;
|
||||
let collapsed = self.collapsed;
|
||||
let state = self.state.clone();
|
||||
let state = self.device.read(cx);
|
||||
let status_owned = state.status.clone();
|
||||
let error_owned = state.error.clone();
|
||||
|
||||
let sidebar_bg = cx.theme().sidebar;
|
||||
let sidebar_fg = cx.theme().sidebar_foreground;
|
||||
@@ -191,13 +194,13 @@ impl<V: 'static> AppSidebar<V> {
|
||||
.w_full(),
|
||||
)
|
||||
.child(div().w(px(8.)).h(px(8.)).rounded_full().bg(
|
||||
if let Some(status) = &state.status {
|
||||
if let Some(status) = &status_owned {
|
||||
if status.method == DeviceMethod::Fido {
|
||||
rgb(0xf59e0b)
|
||||
} else {
|
||||
rgb(0x22c55e)
|
||||
}
|
||||
} else if state.error.is_some() {
|
||||
} else if error_owned.is_some() {
|
||||
rgb(0xf59e0b)
|
||||
} else {
|
||||
rgb(0xef4444)
|
||||
@@ -220,7 +223,7 @@ impl<V: 'static> AppSidebar<V> {
|
||||
)
|
||||
.child({
|
||||
let (text, color_bg, color_text) = if let Some(status) =
|
||||
&state.status
|
||||
&status_owned
|
||||
{
|
||||
let is_rskey = status.firmware_type
|
||||
== crate::hal::types::FirmwareType::RSKey;
|
||||
@@ -240,7 +243,7 @@ impl<V: 'static> AppSidebar<V> {
|
||||
rgb(0xffffff),
|
||||
)
|
||||
}
|
||||
} else if state.error.is_some() {
|
||||
} else if error_owned.is_some() {
|
||||
("Error".to_string(), rgb(0xd97706), rgb(0xffffff))
|
||||
} else {
|
||||
("Offline".to_string(), rgb(0xef4444), rgb(0xffffff))
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
pub mod app;
|
||||
pub mod assets;
|
||||
pub mod colors;
|
||||
pub mod components;
|
||||
pub mod models;
|
||||
pub mod rootview;
|
||||
pub mod screens;
|
||||
pub mod types;
|
||||
|
||||
+55
-2
@@ -1,14 +1,13 @@
|
||||
use crate::hal::types::{FidoDeviceInfo, FullDeviceStatus, LedStatusConfig, ManagementAppConfig};
|
||||
use gpui::*;
|
||||
|
||||
// Repo Events:
|
||||
#[allow(dead_code)]
|
||||
pub enum DeviceEvent {
|
||||
Updated,
|
||||
}
|
||||
|
||||
impl EventEmitter<DeviceEvent> for DeviceRepo {}
|
||||
|
||||
// Repo Definition:
|
||||
pub struct DeviceRepo {
|
||||
pub status: Option<FullDeviceStatus>,
|
||||
pub fido_info: Option<FidoDeviceInfo>,
|
||||
@@ -29,4 +28,58 @@ impl DeviceRepo {
|
||||
loading: false,
|
||||
}
|
||||
}
|
||||
|
||||
// --- State lifecycle ---
|
||||
|
||||
pub fn begin_load(&mut self) {
|
||||
self.loading = true;
|
||||
self.error = None;
|
||||
}
|
||||
|
||||
pub fn end_load(&mut self) {
|
||||
self.loading = false;
|
||||
}
|
||||
|
||||
pub fn is_loading(&self) -> bool {
|
||||
self.loading
|
||||
}
|
||||
|
||||
// --- Field setters ---
|
||||
|
||||
pub fn set_status(&mut self, status: FullDeviceStatus) -> bool {
|
||||
let changed = self
|
||||
.status
|
||||
.as_ref()
|
||||
.map(|s| s.info.serial != status.info.serial)
|
||||
.unwrap_or(true);
|
||||
self.status = Some(status);
|
||||
changed
|
||||
}
|
||||
|
||||
pub fn set_fido_info(&mut self, fido: Option<FidoDeviceInfo>) {
|
||||
self.fido_info = fido;
|
||||
}
|
||||
|
||||
pub fn set_auxiliary_data(
|
||||
&mut self,
|
||||
led: Option<LedStatusConfig>,
|
||||
mgmt: Option<ManagementAppConfig>,
|
||||
) {
|
||||
self.led_status = led;
|
||||
self.management_apps = mgmt;
|
||||
}
|
||||
|
||||
pub fn clear_auxiliary_data(&mut self) {
|
||||
self.led_status = None;
|
||||
self.management_apps = None;
|
||||
}
|
||||
|
||||
pub fn set_error(&mut self, error: String) {
|
||||
self.status = None;
|
||||
self.fido_info = None;
|
||||
self.led_status = None;
|
||||
self.management_apps = None;
|
||||
self.loading = false;
|
||||
self.error = Some(error);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1 +1 @@
|
||||
mod device;
|
||||
pub mod device;
|
||||
|
||||
+40
-137
@@ -1,10 +1,9 @@
|
||||
use crate::hal::io;
|
||||
use crate::ui::app::{ActiveView, ApplicationRoot, ToggleSidebar};
|
||||
use crate::ui::components::sidebar::AppSidebar;
|
||||
use crate::ui::screens::{
|
||||
about::AboutView, config::ConfigView, home::HomeView, passkeys::PasskeysEvent,
|
||||
passkeys::PasskeysView, security::SecurityView,
|
||||
about::AboutViewModel, config::ConfigView, home::HomeViewModel, passkeys::PasskeysEvent,
|
||||
passkeys::PasskeysView, security::SecurityViewModel,
|
||||
};
|
||||
use crate::ui::types::{ActiveView, DeviceConnectionState, LayoutState, ViewCache};
|
||||
use gpui::prelude::*;
|
||||
use gpui::*;
|
||||
use gpui_component::Root;
|
||||
@@ -12,114 +11,11 @@ use gpui_component::{
|
||||
ActiveTheme, Icon, TitleBar, WindowExt, h_flex, scroll::ScrollableElement, v_flex,
|
||||
};
|
||||
|
||||
gpui::actions!(picoforge, [ToggleSidebar]);
|
||||
|
||||
pub struct ApplicationRoot {
|
||||
pub device: DeviceConnectionState,
|
||||
pub layout: LayoutState,
|
||||
pub views: ViewCache,
|
||||
pub focus_handle: FocusHandle,
|
||||
}
|
||||
|
||||
impl ApplicationRoot {
|
||||
pub fn new(cx: &mut Context<Self>) -> Self {
|
||||
let mut this = Self {
|
||||
device: DeviceConnectionState::new(),
|
||||
layout: LayoutState::new(),
|
||||
views: ViewCache::new(),
|
||||
focus_handle: cx.focus_handle(),
|
||||
};
|
||||
|
||||
this.refresh_device_status(None, cx);
|
||||
this
|
||||
}
|
||||
|
||||
pub fn focus_handle(&self) -> FocusHandle {
|
||||
self.focus_handle.clone()
|
||||
}
|
||||
|
||||
fn refresh_device_status(&mut self, window: Option<&mut Window>, cx: &mut Context<Self>) {
|
||||
if self.device.loading {
|
||||
return;
|
||||
}
|
||||
self.device.loading = true;
|
||||
self.device.error = None;
|
||||
cx.notify();
|
||||
|
||||
match io::read_device_details() {
|
||||
Ok(status) => {
|
||||
let device_changed = self
|
||||
.device
|
||||
.status
|
||||
.as_ref()
|
||||
.map(|s| s.info.serial != status.info.serial)
|
||||
.unwrap_or(true);
|
||||
|
||||
self.device.status = Some(status.clone());
|
||||
self.device.error = None;
|
||||
|
||||
if device_changed {
|
||||
self.views.passkeys = None;
|
||||
} else if let Some(passkeys_view) = &self.views.passkeys {
|
||||
passkeys_view.update(cx, |view, cx| {
|
||||
view.refresh_if_unlocked(cx);
|
||||
});
|
||||
}
|
||||
|
||||
match io::get_fido_info() {
|
||||
Ok(fido) => {
|
||||
self.device.fido_info = Some(fido);
|
||||
}
|
||||
Err(e) => {
|
||||
log::error!("FIDO Info fetch failed: {}", e);
|
||||
self.device.fido_info = None;
|
||||
}
|
||||
}
|
||||
|
||||
if status.firmware_type == crate::hal::types::FirmwareType::RSKey
|
||||
&& status.method == crate::hal::types::DeviceMethod::Rescue
|
||||
{
|
||||
self.device.led_status = io::read_led_config().ok();
|
||||
self.device.management_apps = io::read_management_config().ok();
|
||||
} else {
|
||||
self.device.led_status = None;
|
||||
self.device.management_apps = None;
|
||||
}
|
||||
|
||||
if let Some(config_view) = &self.views.config
|
||||
&& let Some(window) = window
|
||||
{
|
||||
let device = self.device.clone();
|
||||
config_view.update(cx, |view, cx| {
|
||||
view.sync_from_device(&device, window, cx);
|
||||
});
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
self.device.status = None;
|
||||
self.device.error = Some(format!("{}", e));
|
||||
self.device.fido_info = None;
|
||||
self.device.led_status = None;
|
||||
self.device.management_apps = None;
|
||||
// Drop cached passkeys view (and cached PIN) on disconnect.
|
||||
self.views.passkeys = None;
|
||||
}
|
||||
}
|
||||
self.device.loading = false;
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
pub fn toggle_sidebar(&mut self, cx: &mut Context<Self>) {
|
||||
self.layout.is_sidebar_collapsed = !self.layout.is_sidebar_collapsed;
|
||||
cx.notify();
|
||||
}
|
||||
}
|
||||
|
||||
impl Render for ApplicationRoot {
|
||||
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
let window_width = window.bounds().size.width;
|
||||
let is_window_wide = window_width > px(800.0);
|
||||
let is_sidebar_collapsed = self.layout.is_sidebar_collapsed || !is_window_wide;
|
||||
let is_sidebar_collapsed = self.view_state.is_sidebar_collapsed || !is_window_wide;
|
||||
|
||||
let target_width = if is_sidebar_collapsed {
|
||||
px(48.)
|
||||
@@ -127,12 +23,12 @@ impl Render for ApplicationRoot {
|
||||
px(255.)
|
||||
};
|
||||
|
||||
if (self.layout.sidebar_width - target_width).abs() > px(0.1) {
|
||||
self.layout.sidebar_width =
|
||||
self.layout.sidebar_width + (target_width - self.layout.sidebar_width) * 0.2;
|
||||
if (self.view_state.sidebar_width - target_width).abs() > px(0.1) {
|
||||
self.view_state.sidebar_width = self.view_state.sidebar_width
|
||||
+ (target_width - self.view_state.sidebar_width) * 0.2;
|
||||
window.request_animation_frame();
|
||||
} else {
|
||||
self.layout.sidebar_width = target_width;
|
||||
self.view_state.sidebar_width = target_width;
|
||||
}
|
||||
|
||||
let dialog_layer = Root::render_dialog_layer(window, cx);
|
||||
@@ -158,15 +54,16 @@ impl Render for ApplicationRoot {
|
||||
.overflow_y_scrollbar()
|
||||
.flex_grow()
|
||||
.bg(cx.theme().background)
|
||||
.child(match self.layout.active_view {
|
||||
.child(match self.view_state.active_view {
|
||||
ActiveView::Home => {
|
||||
HomeView::build(&self.device, cx.theme(), window.bounds().size.width)
|
||||
.into_any_element()
|
||||
let view = self.views_store.home.get_or_insert_with(|| {
|
||||
cx.new(|cx| HomeViewModel::new(window, cx, &self.models))
|
||||
});
|
||||
view.clone().into_any_element()
|
||||
}
|
||||
ActiveView::Passkeys => {
|
||||
let view = self.views.passkeys.get_or_insert_with(|| {
|
||||
let root = cx.entity().downgrade();
|
||||
let view = cx.new(|cx| PasskeysView::new(window, cx, root));
|
||||
let view = self.views_store.passkeys.get_or_insert_with(|| {
|
||||
let view = cx.new(|cx| PasskeysView::new(window, cx, &self.models));
|
||||
cx.subscribe_in(
|
||||
&view,
|
||||
window,
|
||||
@@ -182,38 +79,43 @@ impl Render for ApplicationRoot {
|
||||
view.clone().into_any_element()
|
||||
}
|
||||
ActiveView::Configuration => {
|
||||
if self.views.config.is_none() {
|
||||
let root = cx.entity().downgrade();
|
||||
let device = self.device.clone();
|
||||
self.views.config =
|
||||
Some(cx.new(|cx| ConfigView::new(window, cx, root, device)));
|
||||
}
|
||||
self.views.config.clone().unwrap().into_any_element()
|
||||
let view = self.views_store.config.get_or_insert_with(|| {
|
||||
cx.new(|cx| ConfigView::new(window, cx, &self.models))
|
||||
});
|
||||
view.clone().into_any_element()
|
||||
}
|
||||
ActiveView::Security => {
|
||||
let view = self.views_store.security.get_or_insert_with(|| {
|
||||
cx.new(|cx| SecurityViewModel::new(window, cx, &self.models))
|
||||
});
|
||||
view.clone().into_any_element()
|
||||
}
|
||||
ActiveView::About => {
|
||||
let view = self.views_store.about.get_or_insert_with(|| {
|
||||
cx.new(|cx| AboutViewModel::new(window, cx, &self.models))
|
||||
});
|
||||
view.clone().into_any_element()
|
||||
}
|
||||
ActiveView::Security => SecurityView::build(cx).into_any_element(),
|
||||
ActiveView::About => AboutView::build(cx.theme()).into_any_element(),
|
||||
});
|
||||
|
||||
let sidebar = AppSidebar::new(
|
||||
self.layout.active_view,
|
||||
self.layout.sidebar_width,
|
||||
self.view_state.active_view,
|
||||
self.view_state.sidebar_width,
|
||||
is_sidebar_collapsed,
|
||||
self.device.clone(),
|
||||
&self.models,
|
||||
)
|
||||
.on_select(|this: &mut Self, view, _, _| {
|
||||
this.layout.active_view = view;
|
||||
this.view_state.active_view = view;
|
||||
})
|
||||
.on_refresh(|this, window, cx| {
|
||||
this.refresh_device_status(Some(window), cx);
|
||||
});
|
||||
|
||||
// Toggle button absolutely positioned at the sidebar's right edge.
|
||||
// It fades in on hover when the sidebar is collapsed.
|
||||
let sidebar_bg = cx.theme().sidebar;
|
||||
let border_color = cx.theme().sidebar_border;
|
||||
let sidebar_fg = cx.theme().sidebar_foreground;
|
||||
let is_toggle_visible = !is_sidebar_collapsed || self.layout.sidebar_toggle_hovered;
|
||||
let sidebar_width = self.layout.sidebar_width;
|
||||
let is_toggle_visible = !is_sidebar_collapsed || self.view_state.sidebar_toggle_hovered;
|
||||
let sidebar_width = self.view_state.sidebar_width;
|
||||
let toggle_icon = if is_sidebar_collapsed {
|
||||
"icons/chevron-right.svg"
|
||||
} else {
|
||||
@@ -235,7 +137,7 @@ impl Render for ApplicationRoot {
|
||||
.items_center()
|
||||
.justify_center()
|
||||
.on_hover(cx.listener(|this, hovered, _, cx| {
|
||||
this.layout.sidebar_toggle_hovered = *hovered;
|
||||
this.view_state.sidebar_toggle_hovered = *hovered;
|
||||
cx.notify();
|
||||
}))
|
||||
.child(
|
||||
@@ -258,7 +160,8 @@ impl Render for ApplicationRoot {
|
||||
.build(window, cx)
|
||||
})
|
||||
.on_click(cx.listener(|this, _, _, _| {
|
||||
this.layout.is_sidebar_collapsed = !this.layout.is_sidebar_collapsed;
|
||||
this.view_state.is_sidebar_collapsed =
|
||||
!this.view_state.is_sidebar_collapsed;
|
||||
}))
|
||||
.child(Icon::default().path(toggle_icon).text_color(sidebar_fg)),
|
||||
);
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
pub mod view;
|
||||
pub mod view_model;
|
||||
pub use view_model::AboutViewModel;
|
||||
@@ -1,12 +1,11 @@
|
||||
// src/views/about.rs
|
||||
use crate::ui::components::{card::Card, page_view::PageView, tag::Tag};
|
||||
use crate::ui::screens::about::view_model::AboutViewModel;
|
||||
use gpui::*;
|
||||
use gpui_component::{Icon, StyledExt, Theme, button::Button, h_flex, v_flex};
|
||||
use gpui_component::{ActiveTheme, Icon, StyledExt, button::Button, h_flex, v_flex};
|
||||
|
||||
pub struct AboutView;
|
||||
|
||||
impl AboutView {
|
||||
pub fn build(theme: &Theme) -> impl IntoElement {
|
||||
impl Render for AboutViewModel {
|
||||
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
let theme = cx.theme();
|
||||
PageView::build(
|
||||
"About",
|
||||
"Information about the application and its development.",
|
||||
@@ -0,0 +1,10 @@
|
||||
use crate::ui::app::AppModels;
|
||||
use gpui::*;
|
||||
|
||||
pub struct AboutViewModel;
|
||||
|
||||
impl AboutViewModel {
|
||||
pub fn new(_window: &mut Window, _cx: &mut Context<Self>, _models: &AppModels) -> Self {
|
||||
Self
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,3 @@
|
||||
pub mod view;
|
||||
pub mod view_model;
|
||||
pub use view_model::ConfigView;
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,3 @@
|
||||
pub mod view;
|
||||
pub mod view_model;
|
||||
pub use view_model::HomeViewModel;
|
||||
@@ -1,59 +1,12 @@
|
||||
use crate::hal::types::DeviceMethod;
|
||||
use crate::hal::types::{DeviceMethod, FidoDeviceInfo, FullDeviceStatus};
|
||||
use crate::ui::components::{card::Card, page_view::PageView, tag::Tag};
|
||||
use crate::ui::types::DeviceConnectionState;
|
||||
use crate::ui::screens::home::view_model::HomeViewModel;
|
||||
use gpui::prelude::FluentBuilder;
|
||||
use gpui::*;
|
||||
use gpui_component::StyledExt;
|
||||
use gpui_component::{ActiveTheme, StyledExt};
|
||||
use gpui_component::{Icon, IconName, Theme, h_flex, progress::Progress, v_flex};
|
||||
|
||||
pub struct HomeView;
|
||||
|
||||
impl HomeView {
|
||||
pub fn build(
|
||||
state: &DeviceConnectionState,
|
||||
theme: &Theme,
|
||||
window_width: Pixels,
|
||||
) -> impl IntoElement {
|
||||
let connected = state.status.is_some();
|
||||
let is_wide = window_width > px(1100.0);
|
||||
let columns = if is_wide { 2 } else { 1 };
|
||||
|
||||
PageView::build(
|
||||
"Device Overview",
|
||||
"Quick view of your device status and specifications.",
|
||||
if !connected {
|
||||
// No Device Status Placeholder
|
||||
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 Device Connected"),
|
||||
)
|
||||
.into_any_element()
|
||||
} else {
|
||||
// Card Grid
|
||||
div()
|
||||
.grid()
|
||||
.grid_cols(columns)
|
||||
.gap_6()
|
||||
.child(Self::render_device_info(state, theme))
|
||||
.child(Self::render_fido_info(state, theme))
|
||||
.child(Self::render_led_config(state, theme))
|
||||
.child(Self::render_security_status(state, theme))
|
||||
.into_any_element()
|
||||
},
|
||||
theme,
|
||||
)
|
||||
}
|
||||
|
||||
// Helper for Key-Value pairs
|
||||
impl HomeViewModel {
|
||||
fn render_kv(
|
||||
label: &str,
|
||||
value: impl IntoElement,
|
||||
@@ -82,8 +35,7 @@ impl HomeView {
|
||||
)
|
||||
}
|
||||
|
||||
fn render_device_info(state: &DeviceConnectionState, theme: &Theme) -> impl IntoElement {
|
||||
let status = state.status.as_ref().unwrap();
|
||||
fn render_device_info(status: &FullDeviceStatus, theme: &Theme) -> impl IntoElement {
|
||||
let info = &status.info;
|
||||
let config = &status.config;
|
||||
|
||||
@@ -165,15 +117,14 @@ impl HomeView {
|
||||
)
|
||||
}
|
||||
|
||||
fn render_fido_info(state: &DeviceConnectionState, theme: &Theme) -> impl IntoElement {
|
||||
fn render_fido_info(fido: Option<&FidoDeviceInfo>, theme: &Theme) -> impl IntoElement {
|
||||
Card::new()
|
||||
.title("FIDO2 Information")
|
||||
.icon(Icon::default().path("icons/shield.svg"))
|
||||
.child(if let Some(fido) = &state.fido_info {
|
||||
.child(if let Some(fido) = fido {
|
||||
v_flex()
|
||||
.gap_3()
|
||||
.text_sm()
|
||||
// AAGUID
|
||||
.child(
|
||||
h_flex()
|
||||
.justify_between()
|
||||
@@ -188,7 +139,6 @@ impl HomeView {
|
||||
.child(fido.aaguid.clone()),
|
||||
),
|
||||
)
|
||||
// FIDO Versions
|
||||
.child(
|
||||
h_flex()
|
||||
.justify_between()
|
||||
@@ -209,7 +159,6 @@ impl HomeView {
|
||||
)),
|
||||
)
|
||||
.child(div().h_px().bg(theme.border))
|
||||
// PIN Set
|
||||
.child(
|
||||
h_flex()
|
||||
.justify_between()
|
||||
@@ -221,7 +170,6 @@ impl HomeView {
|
||||
Tag::new(if pin_set { "Set" } else { "Not Set" }).active(pin_set)
|
||||
}),
|
||||
)
|
||||
// Resident Keys
|
||||
.child(
|
||||
h_flex()
|
||||
.justify_between()
|
||||
@@ -236,7 +184,6 @@ impl HomeView {
|
||||
Tag::new(if rk { "Supported" } else { "Not Supported" }).active(rk)
|
||||
}),
|
||||
)
|
||||
// Min PIN Length
|
||||
.child(
|
||||
h_flex()
|
||||
.justify_between()
|
||||
@@ -253,7 +200,6 @@ impl HomeView {
|
||||
.child(fido.min_pin_length.to_string()),
|
||||
),
|
||||
)
|
||||
// Enterprise attestation
|
||||
.child(
|
||||
h_flex()
|
||||
.justify_between()
|
||||
@@ -268,7 +214,6 @@ impl HomeView {
|
||||
Tag::new(if ep_set { "Set" } else { "Not Set" }).active(ep_set)
|
||||
})),
|
||||
)
|
||||
// Remaining Credentials
|
||||
.when(fido.remaining_discoverable_credentials.is_some(), |this| {
|
||||
this.child(
|
||||
h_flex()
|
||||
@@ -298,8 +243,7 @@ impl HomeView {
|
||||
})
|
||||
}
|
||||
|
||||
fn render_led_config(state: &DeviceConnectionState, theme: &Theme) -> impl IntoElement {
|
||||
let status = state.status.as_ref().unwrap();
|
||||
fn render_led_config(status: &FullDeviceStatus, theme: &Theme) -> impl IntoElement {
|
||||
let config = &status.config;
|
||||
Card::new()
|
||||
.title("LED Configuration")
|
||||
@@ -386,8 +330,7 @@ impl HomeView {
|
||||
})
|
||||
}
|
||||
|
||||
fn render_security_status(state: &DeviceConnectionState, theme: &Theme) -> impl IntoElement {
|
||||
let status = state.status.as_ref().unwrap();
|
||||
fn render_security_status(status: &FullDeviceStatus, theme: &Theme) -> impl IntoElement {
|
||||
Card::new()
|
||||
.title("Security Status")
|
||||
.icon(Icon::default().path("icons/shield-check.svg"))
|
||||
@@ -463,3 +406,48 @@ impl HomeView {
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl Render for HomeViewModel {
|
||||
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
let device = self.device.read(cx);
|
||||
let connected = device.status.is_some();
|
||||
let is_wide = window.bounds().size.width > px(1100.0);
|
||||
let columns = if is_wide { 2 } else { 1 };
|
||||
|
||||
PageView::build(
|
||||
"Device Overview",
|
||||
"Quick view of your device status and specifications.",
|
||||
if !connected {
|
||||
div()
|
||||
.flex()
|
||||
.items_center()
|
||||
.justify_center()
|
||||
.h_64()
|
||||
.border_1()
|
||||
.border_color(cx.theme().border)
|
||||
.rounded_xl()
|
||||
.child(
|
||||
div()
|
||||
.text_color(cx.theme().muted_foreground)
|
||||
.child("No Device Connected"),
|
||||
)
|
||||
.into_any_element()
|
||||
} else {
|
||||
let status = device.status.as_ref().unwrap();
|
||||
div()
|
||||
.grid()
|
||||
.grid_cols(columns)
|
||||
.gap_6()
|
||||
.child(Self::render_device_info(status, cx.theme()))
|
||||
.child(Self::render_fido_info(
|
||||
device.fido_info.as_ref(),
|
||||
cx.theme(),
|
||||
))
|
||||
.child(Self::render_led_config(status, cx.theme()))
|
||||
.child(Self::render_security_status(status, cx.theme()))
|
||||
.into_any_element()
|
||||
},
|
||||
cx.theme(),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
use crate::ui::app::AppModels;
|
||||
use crate::ui::models::device::{DeviceEvent, DeviceRepo};
|
||||
use gpui::*;
|
||||
|
||||
pub struct HomeViewModel {
|
||||
pub device: Entity<DeviceRepo>,
|
||||
}
|
||||
|
||||
impl HomeViewModel {
|
||||
pub fn new(_window: &mut Window, cx: &mut Context<Self>, models: &AppModels) -> Self {
|
||||
let device = models.device.clone();
|
||||
cx.subscribe(&device, |_, _, _: &DeviceEvent, cx| cx.notify())
|
||||
.detach();
|
||||
Self { device }
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,3 @@
|
||||
pub mod view;
|
||||
pub mod view_model;
|
||||
pub use view_model::{PasskeysEvent, PasskeysView};
|
||||
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user