This commit is contained in:
Alex 2026-07-25 22:59:25 +02:00
commit 009173a97d
Signed by: alex
SSH key fingerprint: SHA256:D1+Ub8o0v4K5y1JNivW8IxEOelqLSvPmUzBbDIoZkRQ
49 changed files with 1788 additions and 389 deletions

View file

@ -29,7 +29,13 @@ impl Screen for DaemonStartingScreen {
fn as_any_mut(&mut self) -> &mut dyn Any {
self
}
fn render(&self, frame: &mut Frame, area: Rect, context: &RenderContext<'_>, _hits: &mut HitMap) {
fn render(
&self,
frame: &mut Frame,
area: Rect,
context: &RenderContext<'_>,
_hits: &mut HitMap,
) {
let popup = crate::layout::fit::centered_rect(
area,
crate::layout::fit::RequiredSize {
@ -191,7 +197,13 @@ impl Screen for DaemonSetupScreen {
fn as_any_mut(&mut self) -> &mut dyn Any {
self
}
fn render(&self, frame: &mut Frame, area: Rect, context: &RenderContext<'_>, _hits: &mut HitMap) {
fn render(
&self,
frame: &mut Frame,
area: Rect,
context: &RenderContext<'_>,
_hits: &mut HitMap,
) {
let popup = crate::layout::fit::centered_rect(
area,
crate::layout::fit::RequiredSize {
@ -267,7 +279,9 @@ impl Screen for DaemonSetupScreen {
);
}
fn handle_event(&mut self, event: UiEvent) -> InteractionResult {
let UiEvent::Key(event) = event else { return InteractionResult::Unhandled; };
let UiEvent::Key(event) = event else {
return InteractionResult::Unhandled;
};
match event.code {
KeyCode::Esc => {
self.complete(DaemonSetupDecision::Exit);

View file

@ -212,9 +212,7 @@ impl MainScreen {
let mut seen: Vec<Option<usize>> = Vec::new();
for (y, row) in self.nav_grid.iter().enumerate() {
for (x, elem_opt) in row.iter().enumerate() {
if x == 1
&& (!self.graphs_open || self.layout_width.load(Ordering::Relaxed) < 70)
{
if x == 1 && (!self.graphs_open || self.layout_width.load(Ordering::Relaxed) < 70) {
continue;
}
if elem_opt.is_some() && !seen.contains(elem_opt) {
@ -511,25 +509,64 @@ impl Screen for MainScreen {
fn key_hints(&self) -> Vec<KeyHint> {
if self.selected_coords == (2, 0) {
vec![
KeyHint { keys: "Enter", action: "Send" },
KeyHint { keys: "Up/Down", action: "History" },
KeyHint { keys: "Tab", action: "Complete" },
KeyHint { keys: "F6", action: "Header" },
KeyHint {
keys: "Enter",
action: "Send",
},
KeyHint {
keys: "Up/Down",
action: "History",
},
KeyHint {
keys: "Tab",
action: "Complete",
},
KeyHint {
keys: "F6",
action: "Header",
},
]
} else if self.selected_coords == (0, 0) {
vec![
KeyHint { keys: "J/K", action: "Scroll logs" },
KeyHint { keys: "Enter", action: "Lock scroll" },
KeyHint { keys: "/", action: "Filter" },
KeyHint { keys: "M", action: "Metrics screen" },
KeyHint { keys: "Tab", action: "Next panel" },
KeyHint { keys: "F6", action: "Header" },
KeyHint {
keys: "J/K",
action: "Scroll logs",
},
KeyHint {
keys: "Enter",
action: "Lock scroll",
},
KeyHint {
keys: "/",
action: "Filter",
},
KeyHint {
keys: "M",
action: "Metrics screen",
},
KeyHint {
keys: "Tab",
action: "Next panel",
},
KeyHint {
keys: "F6",
action: "Header",
},
]
} else {
vec![
KeyHint { keys: "Enter", action: "Toggle metrics" },
KeyHint { keys: "Tab", action: "Next panel" },
KeyHint { keys: "F6", action: "Header" },
KeyHint {
keys: "Enter",
action: "Toggle metrics",
},
KeyHint {
keys: "Tab",
action: "Next panel",
},
KeyHint {
keys: "F6",
action: "Header",
},
]
}
}

View file

@ -34,7 +34,9 @@ impl Screen for FileViewer {
}
fn handle_event(&mut self, event: UiEvent) -> InteractionResult {
let UiEvent::Key(event) = event else { return InteractionResult::Unhandled; };
let UiEvent::Key(event) = event else {
return InteractionResult::Unhandled;
};
match event.code {
KeyCode::Char('q') | KeyCode::Esc => {
return InteractionResult::CloseScreen;

View file

@ -63,7 +63,13 @@ impl Screen for MetricsScreen {
self
}
fn render(&self, frame: &mut Frame, area: Rect, context: &RenderContext<'_>, hits: &mut HitMap) {
fn render(
&self,
frame: &mut Frame,
area: Rect,
context: &RenderContext<'_>,
hits: &mut HitMap,
) {
let block = Block::default()
.title(" Metrics ")
.borders(Borders::ALL)
@ -80,8 +86,7 @@ impl Screen for MetricsScreen {
frame.render_widget(
Paragraph::new(format!(
"Range: {} ({} samples) Left/Right to change",
RANGES[self.range_index].1,
RANGES[self.range_index].0
RANGES[self.range_index].1, RANGES[self.range_index].0
))
.style(context.theme.text.heading),
rows[0],

View file

@ -53,17 +53,11 @@ impl OverviewScreen {
let mut lines = Vec::new();
lines.push(Line::from(Span::styled(
"Connection",
theme.text.heading,
)));
lines.push(Line::from(Span::styled("Connection", theme.text.heading)));
lines.push(Line::from(format!(" State: {}", connection_label(&conn))));
lines.push(Line::from(""));
lines.push(Line::from(Span::styled(
"Daemon",
theme.text.heading,
)));
lines.push(Line::from(Span::styled("Daemon", theme.text.heading)));
lines.push(Line::from(format!(
" Version: {}",
version_or_unknown(&daemon.version)
@ -113,10 +107,7 @@ impl OverviewScreen {
if !daemon.components.is_empty() {
lines.push(Line::from(""));
lines.push(Line::from(Span::styled(
"Components",
theme.text.heading,
)));
lines.push(Line::from(Span::styled("Components", theme.text.heading)));
for (id, health) in &daemon.components {
let status_str = match health.status {
iota_ipc::HealthStatus::Healthy => "[OK] healthy",
@ -187,7 +178,13 @@ impl Screen for OverviewScreen {
.borders(Borders::ALL)
.border_style(context.theme.borders.normal)
.title_style(context.theme.borders.title);
let inner = if matches!(context.theme.chrome, crate::theme::ChromeMode::Surfaces) { crate::controls::panel::render_panel(f, rect, "Overview", false, context.theme) } else { let inner = block.inner(rect); f.render_widget(block, rect); inner };
let inner = if matches!(context.theme.chrome, crate::theme::ChromeMode::Surfaces) {
crate::controls::panel::render_panel(f, rect, "Overview", false, context.theme)
} else {
let inner = block.inner(rect);
f.render_widget(block, rect);
inner
};
let rows = ratatui::layout::Layout::vertical([
ratatui::layout::Constraint::Min(1),
ratatui::layout::Constraint::Length(1),
@ -273,10 +270,22 @@ impl Screen for OverviewScreen {
}
fn key_hints(&self) -> Vec<KeyHint> {
vec![
KeyHint { keys: "Up/Down", action: "Scroll" },
KeyHint { keys: "PgUp/PgDn", action: "Page" },
KeyHint { keys: "Esc/B", action: "Back" },
KeyHint { keys: "F6", action: "Header" },
KeyHint {
keys: "Up/Down",
action: "Scroll",
},
KeyHint {
keys: "PgUp/PgDn",
action: "Page",
},
KeyHint {
keys: "Esc/B",
action: "Back",
},
KeyHint {
keys: "F6",
action: "Header",
},
]
}
}

View file

@ -49,8 +49,12 @@ impl SettingsScreen {
selected,
saved: current,
message: "Left/Right previews. Enter saves.".into(),
color: UiConfig::load().map(|config| config.color).unwrap_or_default(),
unicode: UiConfig::load().map(|config| config.unicode).unwrap_or_default(),
color: UiConfig::load()
.map(|config| config.color)
.unwrap_or_default(),
unicode: UiConfig::load()
.map(|config| config.unicode)
.unwrap_or_default(),
focus: Focus::Theme,
dialog: None,
pending: false,
@ -64,14 +68,16 @@ impl SettingsScreen {
fn apply(&self, persist: bool) -> InteractionResult {
let theme = self.selected_theme();
InteractionResult::AppTask {
task: Box::pin(async move {
UiEvent::App(AppEvent::ApplyTheme { theme, persist })
}),
task: Box::pin(async move { UiEvent::App(AppEvent::ApplyTheme { theme, persist }) }),
}
}
fn next_policy(policy: TerminalPolicy) -> TerminalPolicy {
match policy { TerminalPolicy::Auto => TerminalPolicy::Always, TerminalPolicy::Always => TerminalPolicy::Never, TerminalPolicy::Never => TerminalPolicy::Auto }
match policy {
TerminalPolicy::Auto => TerminalPolicy::Always,
TerminalPolicy::Always => TerminalPolicy::Never,
TerminalPolicy::Never => TerminalPolicy::Auto,
}
}
fn next_focus(&mut self) {
@ -100,9 +106,7 @@ impl SettingsScreen {
self.pending = true;
self.message = "Regenerating keys…".into();
return InteractionResult::AppTask {
task: Box::pin(async {
UiEvent::App(AppEvent::RegenerateKeysRequested)
}),
task: Box::pin(async { UiEvent::App(AppEvent::RegenerateKeysRequested) }),
};
}
}
@ -113,7 +117,15 @@ impl SettingsScreen {
let theme = self.selected_theme();
let color = self.color;
let unicode = self.unicode;
InteractionResult::AppTask { task: Box::pin(async move { UiEvent::App(AppEvent::SaveSettings { theme, color, unicode }) }) }
InteractionResult::AppTask {
task: Box::pin(async move {
UiEvent::App(AppEvent::SaveSettings {
theme,
color,
unicode,
})
}),
}
}
Focus::RegenerateKeys => {
self.dialog = Some(Dialog::ConfirmRegenerateKeys);
@ -146,8 +158,7 @@ impl Screen for SettingsScreen {
.border_style(context.theme.borders.focused);
let inner = block.inner(area);
frame.render_widget(block, area);
let rows =
Layout::vertical([Constraint::Length(2), Constraint::Min(1)]).split(inner);
let rows = Layout::vertical([Constraint::Length(2), Constraint::Min(1)]).split(inner);
frame.render_widget(
Paragraph::new(format!(
"Theme: < {} >{}\nColor: {:?} (C) Unicode: {:?} (U)",
@ -156,7 +167,9 @@ impl Screen for SettingsScreen {
" [saved]"
} else {
" [preview]"
}, self.color, self.unicode
},
self.color,
self.unicode
))
.style(context.theme.text.heading),
rows[0],
@ -331,8 +344,14 @@ impl Screen for SettingsScreen {
self.prev_focus();
InteractionResult::Handled
}
KeyCode::Char('c') | KeyCode::Char('C') => { self.color = Self::next_policy(self.color); InteractionResult::Handled }
KeyCode::Char('u') | KeyCode::Char('U') => { self.unicode = Self::next_policy(self.unicode); InteractionResult::Handled }
KeyCode::Char('c') | KeyCode::Char('C') => {
self.color = Self::next_policy(self.color);
InteractionResult::Handled
}
KeyCode::Char('u') | KeyCode::Char('U') => {
self.unicode = Self::next_policy(self.unicode);
InteractionResult::Handled
}
KeyCode::Esc | KeyCode::Char('b') | KeyCode::Char('B') => {
InteractionResult::CloseScreen
}
@ -378,8 +397,14 @@ impl Screen for SettingsScreen {
keys: "Enter",
action: "Save/Activate",
},
KeyHint { keys: "Tab", action: "Move focus" },
KeyHint { keys: "C/U", action: "Color/Unicode" },
KeyHint {
keys: "Tab",
action: "Move focus",
},
KeyHint {
keys: "C/U",
action: "Color/Unicode",
},
KeyHint {
keys: "Esc/B",
action: "Back",

View file

@ -2,7 +2,10 @@ use crate::{
controls::choice::{ChoiceKind, ChoiceVisualState, render_choice_line},
interaction_result::InteractionResult,
render_context::RenderContext,
screens::{md_viewer::FileViewer, screens::{HitMap, Screen, UiEvent}},
screens::{
md_viewer::FileViewer,
screens::{HitMap, Screen, UiEvent},
},
util::{buttons::draw_buttons, terms_focus::Focus},
};
use crossterm::event::KeyCode;
@ -271,7 +274,9 @@ impl Screen for TermsCheckerScreen {
}
fn handle_event(&mut self, event: UiEvent) -> InteractionResult {
let UiEvent::Key(event) = event else { return InteractionResult::Unhandled; };
let UiEvent::Key(event) = event else {
return InteractionResult::Unhandled;
};
let mut possible_states = vec![Focus::Eula, Focus::Tos, Focus::Pp, Focus::Cancel];
if self.eula {

View file

@ -3,7 +3,10 @@ use crate::{
controls::choice::{ChoiceKind, ChoiceVisualState, render_choice_line},
interaction_result::InteractionResult,
render_context::RenderContext,
screens::{md_viewer::FileViewer, screens::{HitMap, Screen, UiEvent}},
screens::{
md_viewer::FileViewer,
screens::{HitMap, Screen, UiEvent},
},
util::{buttons::draw_buttons, terms_focus::Focus},
};
use chrono::{Local, TimeZone, Utc};
@ -594,7 +597,9 @@ impl Screen for TermsUpdaterScreen {
}
fn handle_event(&mut self, event: UiEvent) -> InteractionResult {
let UiEvent::Key(event) = event else { return InteractionResult::Unhandled; };
let UiEvent::Key(event) = event else {
return InteractionResult::Unhandled;
};
let mut possible_states = Vec::new();
if self.eula_needed {

View file

@ -89,7 +89,12 @@ impl UsersScreen {
let title = if self.filter.is_empty() {
format!("Users ({})", self.users.len())
} else {
format!("Users ({}/{}) filter: {}", visible_indices.len(), self.users.len(), self.filter)
format!(
"Users ({}/{}) filter: {}",
visible_indices.len(),
self.users.len(),
self.filter
)
};
let inner = if matches!(context.theme.chrome, crate::theme::ChromeMode::Surfaces) {
crate::controls::panel::render_panel(
@ -124,14 +129,18 @@ impl UsersScreen {
}
let mut lines = Vec::new();
self.viewport_height.store(inner.height as usize, Ordering::Relaxed);
self.viewport_height
.store(inner.height as usize, Ordering::Relaxed);
let labels: Vec<(usize, String)> = visible_indices
.iter()
.skip(self.scroll_offset)
.take(inner.height as usize)
.map(|user_index| {
let user = &self.users[*user_index];
(*user_index, format!("{:>6} {}", user.user_id, user.username))
(
*user_index,
format!("{:>6} {}", user.user_id, user.username),
)
})
.collect();
for (user_index, label) in &labels {
@ -291,7 +300,10 @@ impl UsersScreen {
fn keep_focused_user_visible(&mut self) {
let indices = self.filtered_indices();
let Some(position) = indices.iter().position(|index| *index == self.focused_index) else {
let Some(position) = indices
.iter()
.position(|index| *index == self.focused_index)
else {
self.scroll_offset = 0;
return;
};