Import/export feature for config and data
This commit is contained in:
@@ -115,9 +115,14 @@ impl Widget for Dashboard<'_> {
|
||||
Paragraph::new(chars_line).render(layout[4], buf);
|
||||
|
||||
let help = Paragraph::new(Line::from(vec![
|
||||
Span::styled(" [r] Retry ", Style::default().fg(colors.accent())),
|
||||
Span::styled(
|
||||
" [c/Enter/Space] Continue ",
|
||||
Style::default().fg(colors.accent()),
|
||||
),
|
||||
Span::styled("[r] Retry ", Style::default().fg(colors.accent())),
|
||||
Span::styled("[q] Menu ", Style::default().fg(colors.accent())),
|
||||
Span::styled("[s] Stats", Style::default().fg(colors.accent())),
|
||||
Span::styled("[s] Stats ", Style::default().fg(colors.accent())),
|
||||
Span::styled("[x] Delete", Style::default().fg(colors.accent())),
|
||||
]));
|
||||
help.render(layout[6], buf);
|
||||
}
|
||||
|
||||
@@ -382,13 +382,34 @@ impl KeyboardDiagram<'_> {
|
||||
}
|
||||
}
|
||||
|
||||
// Compute full keyboard width from rendered rows (including trailing modifier keys),
|
||||
// so the space bar centers relative to the keyboard, not the container.
|
||||
let keyboard_width = self
|
||||
.model
|
||||
.rows
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(row_idx, row)| {
|
||||
let offset = offsets.get(row_idx).copied().unwrap_or(0);
|
||||
let row_end = offset + row.len() as u16 * key_width;
|
||||
match row_idx {
|
||||
0 => row_end + 6, // [Bksp]
|
||||
2 => row_end + 7, // [Enter]
|
||||
3 => row_end + 6, // [Shft]
|
||||
_ => row_end,
|
||||
}
|
||||
})
|
||||
.max()
|
||||
.unwrap_or(0)
|
||||
.min(inner.width);
|
||||
|
||||
// Space bar row (row 4)
|
||||
let space_y = inner.y + 4;
|
||||
if space_y < inner.y + inner.height {
|
||||
let space_name = display::key_display_name(SPACE);
|
||||
let space_label = format!("[ {space_name} ]");
|
||||
let space_width = space_label.len() as u16;
|
||||
let space_x = inner.x + (inner.width.saturating_sub(space_width)) / 2;
|
||||
let space_x = inner.x + (keyboard_width.saturating_sub(space_width)) / 2;
|
||||
if space_x + space_width <= inner.x + inner.width {
|
||||
let is_dep = self.depressed_keys.contains(&SPACE);
|
||||
let is_next = self.next_key == Some(SPACE);
|
||||
|
||||
@@ -966,7 +966,8 @@ impl StatsDashboard<'_> {
|
||||
if y >= inner.y + inner.height {
|
||||
break;
|
||||
}
|
||||
let label = format!(" {ch} {time:>4.0}ms ");
|
||||
let key_name = display_key_short_fixed(*ch);
|
||||
let label = format!(" {key_name} {time:>4.0}ms ");
|
||||
let label_len = label.len() as u16;
|
||||
buf.set_string(inner.x, y, &label, Style::default().fg(colors.error()));
|
||||
let bar_space = inner.width.saturating_sub(label_len) as usize;
|
||||
@@ -1013,7 +1014,8 @@ impl StatsDashboard<'_> {
|
||||
if y >= inner.y + inner.height {
|
||||
break;
|
||||
}
|
||||
let label = format!(" {ch} {time:>4.0}ms ");
|
||||
let key_name = display_key_short_fixed(*ch);
|
||||
let label = format!(" {key_name} {time:>4.0}ms ");
|
||||
let label_len = label.len() as u16;
|
||||
buf.set_string(inner.x, y, &label, Style::default().fg(colors.success()));
|
||||
let bar_space = inner.width.saturating_sub(label_len) as usize;
|
||||
@@ -1056,6 +1058,7 @@ impl StatsDashboard<'_> {
|
||||
all_keys.insert(SPACE);
|
||||
all_keys.insert(TAB);
|
||||
all_keys.insert(ENTER);
|
||||
all_keys.insert(BACKSPACE);
|
||||
|
||||
let mut key_accuracies: Vec<(char, f64)> = all_keys
|
||||
.into_iter()
|
||||
@@ -1091,7 +1094,8 @@ impl StatsDashboard<'_> {
|
||||
if y >= inner.y + inner.height {
|
||||
break;
|
||||
}
|
||||
let label = format!(" {ch} {acc:>5.1}% ");
|
||||
let key_name = display_key_short_fixed(*ch);
|
||||
let label = format!(" {key_name} {acc:>5.1}% ");
|
||||
let label_len = label.len() as u16;
|
||||
let color = if *acc >= 95.0 {
|
||||
colors.warning()
|
||||
@@ -1132,6 +1136,7 @@ impl StatsDashboard<'_> {
|
||||
all_keys.insert(SPACE);
|
||||
all_keys.insert(TAB);
|
||||
all_keys.insert(ENTER);
|
||||
all_keys.insert(BACKSPACE);
|
||||
|
||||
let mut key_accuracies: Vec<(char, f64)> = all_keys
|
||||
.into_iter()
|
||||
@@ -1166,7 +1171,8 @@ impl StatsDashboard<'_> {
|
||||
if y >= inner.y + inner.height {
|
||||
break;
|
||||
}
|
||||
let label = format!(" {ch} {acc:>5.1}% ");
|
||||
let key_name = display_key_short_fixed(*ch);
|
||||
let label = format!(" {key_name} {acc:>5.1}% ");
|
||||
let label_len = label.len() as u16;
|
||||
let color = if *acc >= 98.0 {
|
||||
colors.success()
|
||||
@@ -1308,6 +1314,16 @@ fn required_kbd_width(key_width: u16, key_step: u16) -> u16 {
|
||||
max_offset + 12 * key_step + key_width
|
||||
}
|
||||
|
||||
fn display_key_short_fixed(ch: char) -> String {
|
||||
let special = display::key_short_label(ch);
|
||||
let raw = if special.is_empty() {
|
||||
ch.to_string()
|
||||
} else {
|
||||
special.to_string()
|
||||
};
|
||||
format!("{raw:<4}")
|
||||
}
|
||||
|
||||
fn compute_streaks(active_days: &BTreeSet<chrono::NaiveDate>) -> (usize, usize) {
|
||||
if active_days.is_empty() {
|
||||
return (0, 0);
|
||||
|
||||
@@ -12,6 +12,7 @@ pub struct StatsSidebar<'a> {
|
||||
drill: &'a DrillState,
|
||||
last_result: Option<&'a DrillResult>,
|
||||
history: &'a [DrillResult],
|
||||
target_wpm: u32,
|
||||
theme: &'a Theme,
|
||||
}
|
||||
|
||||
@@ -20,12 +21,14 @@ impl<'a> StatsSidebar<'a> {
|
||||
drill: &'a DrillState,
|
||||
last_result: Option<&'a DrillResult>,
|
||||
history: &'a [DrillResult],
|
||||
target_wpm: u32,
|
||||
theme: &'a Theme,
|
||||
) -> Self {
|
||||
Self {
|
||||
drill,
|
||||
last_result,
|
||||
history,
|
||||
target_wpm,
|
||||
theme,
|
||||
}
|
||||
}
|
||||
@@ -82,6 +85,13 @@ impl Widget for StatsSidebar<'_> {
|
||||
Span::styled("WPM: ", Style::default().fg(colors.fg())),
|
||||
Span::styled(wpm_str, Style::default().fg(colors.accent())),
|
||||
]),
|
||||
Line::from(vec![
|
||||
Span::styled("Target: ", Style::default().fg(colors.fg())),
|
||||
Span::styled(
|
||||
format!("{} WPM", self.target_wpm),
|
||||
Style::default().fg(colors.text_pending()),
|
||||
),
|
||||
]),
|
||||
Line::from(""),
|
||||
Line::from(vec![
|
||||
Span::styled("Accuracy: ", Style::default().fg(colors.fg())),
|
||||
|
||||
Reference in New Issue
Block a user