Add items
This commit is contained in:
parent
045e11cd63
commit
b1570a5e11
3 changed files with 304 additions and 87 deletions
114
src/app.rs
114
src/app.rs
|
|
@ -1,4 +1,5 @@
|
||||||
use std::process::Command;
|
use std::process::Command;
|
||||||
|
use chrono::Utc;
|
||||||
use crossterm::event::{Event, KeyCode, KeyEvent, KeyModifiers};
|
use crossterm::event::{Event, KeyCode, KeyEvent, KeyModifiers};
|
||||||
use crate::state::{AppState, TimeItem};
|
use crate::state::{AppState, TimeItem};
|
||||||
use crate::config::Config;
|
use crate::config::Config;
|
||||||
|
|
@ -7,6 +8,7 @@ pub enum Screen {
|
||||||
Main,
|
Main,
|
||||||
Help,
|
Help,
|
||||||
ConfigHelp,
|
ConfigHelp,
|
||||||
|
NewEntry,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct App {
|
pub struct App {
|
||||||
|
|
@ -14,6 +16,15 @@ pub struct App {
|
||||||
pub config: Config,
|
pub config: Config,
|
||||||
pub current_screen: Screen,
|
pub current_screen: Screen,
|
||||||
pub needs_redraw: bool,
|
pub needs_redraw: bool,
|
||||||
|
pub new_entry_buffer: String,
|
||||||
|
pub new_entry_project: String,
|
||||||
|
pub new_entry_cursor: usize,
|
||||||
|
pub new_entry_mode: NewEntryMode, // Task or Project
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum NewEntryMode {
|
||||||
|
Task,
|
||||||
|
Project,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl App {
|
impl App {
|
||||||
|
|
@ -23,6 +34,10 @@ impl App {
|
||||||
config: Config::load()?,
|
config: Config::load()?,
|
||||||
current_screen: Screen::Main,
|
current_screen: Screen::Main,
|
||||||
needs_redraw: true,
|
needs_redraw: true,
|
||||||
|
new_entry_buffer: String::new(),
|
||||||
|
new_entry_project: String::new(),
|
||||||
|
new_entry_cursor: 0,
|
||||||
|
new_entry_mode: NewEntryMode::Task,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -31,6 +46,7 @@ impl App {
|
||||||
Screen::Main => self.handle_main_event(event),
|
Screen::Main => self.handle_main_event(event),
|
||||||
Screen::Help => self.handle_help_event(event),
|
Screen::Help => self.handle_help_event(event),
|
||||||
Screen::ConfigHelp => self.handle_config_help_event(event),
|
Screen::ConfigHelp => self.handle_config_help_event(event),
|
||||||
|
Screen::NewEntry => self.handle_new_entry_event(event),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -46,10 +62,13 @@ impl App {
|
||||||
(KeyCode::Char('k'), _) | (KeyCode::Up, _) => self.move_selection(-1),
|
(KeyCode::Char('k'), _) | (KeyCode::Up, _) => self.move_selection(-1),
|
||||||
(KeyCode::Char('h'), _) | (KeyCode::Left, _) => self.change_pane(-1),
|
(KeyCode::Char('h'), _) | (KeyCode::Left, _) => self.change_pane(-1),
|
||||||
(KeyCode::Char('l'), _) | (KeyCode::Right, _) => self.change_pane(1),
|
(KeyCode::Char('l'), _) | (KeyCode::Right, _) => self.change_pane(1),
|
||||||
|
(KeyCode::Char('n'), KeyModifiers::CONTROL) => self.change_pane(1),
|
||||||
|
(KeyCode::Char('p'), KeyModifiers::CONTROL) => self.change_pane(-1),
|
||||||
(KeyCode::Enter, _) => self.toggle_current_item()?,
|
(KeyCode::Enter, _) => self.toggle_current_item()?,
|
||||||
(KeyCode::Char('e'), KeyModifiers::CONTROL) => self.edit_config()?,
|
(KeyCode::Char('e'), KeyModifiers::CONTROL) => self.edit_config()?,
|
||||||
(KeyCode::Char('?'), _) => self.current_screen = Screen::Help,
|
(KeyCode::Char('?'), _) => self.current_screen = Screen::Help,
|
||||||
(KeyCode::Char('c'), _) => self.edit_app_config()?,
|
(KeyCode::Char('c'), _) => self.edit_app_config()?,
|
||||||
|
(KeyCode::Char('n'), _) => self.start_new_entry(),
|
||||||
_ => {}
|
_ => {}
|
||||||
},
|
},
|
||||||
_ => {}
|
_ => {}
|
||||||
|
|
@ -57,6 +76,93 @@ impl App {
|
||||||
Ok(false)
|
Ok(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn handle_new_entry_event(&mut self, event: Event) -> anyhow::Result<bool> {
|
||||||
|
match event {
|
||||||
|
Event::Key(KeyEvent {
|
||||||
|
code,
|
||||||
|
modifiers,
|
||||||
|
..
|
||||||
|
}) => {
|
||||||
|
match (code, modifiers) {
|
||||||
|
(KeyCode::Esc, _) => {
|
||||||
|
self.current_screen = Screen::Main;
|
||||||
|
self.new_entry_buffer.clear();
|
||||||
|
self.new_entry_project.clear();
|
||||||
|
self.new_entry_mode = NewEntryMode::Task;
|
||||||
|
}
|
||||||
|
(KeyCode::Enter, _) => {
|
||||||
|
match self.new_entry_mode {
|
||||||
|
NewEntryMode::Task => {
|
||||||
|
if !self.new_entry_buffer.is_empty() {
|
||||||
|
self.new_entry_mode = NewEntryMode::Project;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
NewEntryMode::Project => {
|
||||||
|
if self.new_entry_project.is_empty() || self.config.is_valid_project(&self.new_entry_project) {
|
||||||
|
// Create new time item
|
||||||
|
let item = TimeItem {
|
||||||
|
name: self.new_entry_buffer.clone(),
|
||||||
|
tags: if self.new_entry_project.is_empty() {
|
||||||
|
vec![]
|
||||||
|
} else {
|
||||||
|
vec![self.new_entry_project.clone()]
|
||||||
|
},
|
||||||
|
last_used: Some(Utc::now()),
|
||||||
|
};
|
||||||
|
// Add to current pane (or recurring if in recent)
|
||||||
|
match self.state.current_pane {
|
||||||
|
0 => self.state.permanent_items.push(item), // Permanent Items
|
||||||
|
1 => self.state.recurring_items.push(item), // Recurring Items
|
||||||
|
2 => self.state.recent_items.push(item), // Ad-Hoc Items
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
self.state.save()?;
|
||||||
|
|
||||||
|
// Clear and return to main screen
|
||||||
|
self.current_screen = Screen::Main;
|
||||||
|
self.new_entry_buffer.clear();
|
||||||
|
self.new_entry_project.clear();
|
||||||
|
self.new_entry_mode = NewEntryMode::Task;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(KeyCode::Backspace, _) => {
|
||||||
|
match self.new_entry_mode {
|
||||||
|
NewEntryMode::Task => {
|
||||||
|
if self.new_entry_cursor > 0 {
|
||||||
|
self.new_entry_buffer.remove(self.new_entry_cursor - 1);
|
||||||
|
self.new_entry_cursor -= 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
NewEntryMode::Project => {
|
||||||
|
if self.new_entry_cursor > 0 {
|
||||||
|
self.new_entry_project.remove(self.new_entry_cursor - 1);
|
||||||
|
self.new_entry_cursor -= 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(KeyCode::Char(c), m) if m.is_empty() => {
|
||||||
|
match self.new_entry_mode {
|
||||||
|
NewEntryMode::Task => {
|
||||||
|
self.new_entry_buffer.insert(self.new_entry_cursor, c);
|
||||||
|
self.new_entry_cursor += 1;
|
||||||
|
}
|
||||||
|
NewEntryMode::Project => {
|
||||||
|
self.new_entry_project.insert(self.new_entry_cursor, c);
|
||||||
|
self.new_entry_cursor += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
Ok(false)
|
||||||
|
}
|
||||||
|
|
||||||
fn handle_help_event(&mut self, event: Event) -> anyhow::Result<bool> {
|
fn handle_help_event(&mut self, event: Event) -> anyhow::Result<bool> {
|
||||||
match event {
|
match event {
|
||||||
Event::Key(KeyEvent { code, .. }) => match code {
|
Event::Key(KeyEvent { code, .. }) => match code {
|
||||||
|
|
@ -125,6 +231,14 @@ impl App {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn start_new_entry(&mut self) {
|
||||||
|
self.current_screen = Screen::NewEntry;
|
||||||
|
self.new_entry_buffer.clear();
|
||||||
|
self.new_entry_project.clear();
|
||||||
|
self.new_entry_cursor = 0;
|
||||||
|
self.new_entry_mode = NewEntryMode::Task;
|
||||||
|
}
|
||||||
|
|
||||||
fn edit_app_config(&mut self) -> anyhow::Result<()> {
|
fn edit_app_config(&mut self) -> anyhow::Result<()> {
|
||||||
use crossterm::{
|
use crossterm::{
|
||||||
terminal::{disable_raw_mode, enable_raw_mode},
|
terminal::{disable_raw_mode, enable_raw_mode},
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,10 @@ pub struct Config {
|
||||||
pub show_help_hint: bool,
|
pub show_help_hint: bool,
|
||||||
#[serde(default = "default_show_command_hints")]
|
#[serde(default = "default_show_command_hints")]
|
||||||
pub show_command_hints: bool,
|
pub show_command_hints: bool,
|
||||||
|
#[serde(default = "Vec::new")]
|
||||||
|
pub projects: Vec<String>,
|
||||||
|
#[serde(default = "default_strict_projects")]
|
||||||
|
pub strict_projects: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn default_show_help_hint() -> bool {
|
fn default_show_help_hint() -> bool {
|
||||||
|
|
@ -17,11 +21,17 @@ fn default_show_command_hints() -> bool {
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn default_strict_projects() -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
impl Default for Config {
|
impl Default for Config {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
show_help_hint: default_show_help_hint(),
|
show_help_hint: default_show_help_hint(),
|
||||||
show_command_hints: default_show_command_hints(),
|
show_command_hints: default_show_command_hints(),
|
||||||
|
projects: Vec::new(),
|
||||||
|
strict_projects: default_strict_projects(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -54,4 +64,11 @@ impl Config {
|
||||||
path.push("config.yaml");
|
path.push("config.yaml");
|
||||||
Ok(path)
|
Ok(path)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn is_valid_project(&self, project: &str) -> bool {
|
||||||
|
if !self.strict_projects {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
self.projects.contains(&project.to_string())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
260
src/ui.rs
260
src/ui.rs
|
|
@ -6,7 +6,7 @@ use ratatui::{
|
||||||
Frame,
|
Frame,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{state::{AppState, TimeItem}, app::{App, Screen}};
|
use crate::{state::{AppState, TimeItem}, app::{App, Screen, NewEntryMode}};
|
||||||
|
|
||||||
const ACTIVE_COLOR: Color = Color::Green;
|
const ACTIVE_COLOR: Color = Color::Green;
|
||||||
const INACTIVE_COLOR: Color = Color::Yellow;
|
const INACTIVE_COLOR: Color = Color::Yellow;
|
||||||
|
|
@ -16,6 +16,7 @@ pub fn render(frame: &mut Frame, app: &App) {
|
||||||
Screen::Main => render_main(frame, app),
|
Screen::Main => render_main(frame, app),
|
||||||
Screen::Help => render_help(frame, app),
|
Screen::Help => render_help(frame, app),
|
||||||
Screen::ConfigHelp => render_config_help(frame, app),
|
Screen::ConfigHelp => render_config_help(frame, app),
|
||||||
|
Screen::NewEntry => render_new_entry(frame, app),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -81,7 +82,7 @@ fn render_main(frame: &mut Frame, app: &App) {
|
||||||
render_section(
|
render_section(
|
||||||
frame,
|
frame,
|
||||||
sections[2],
|
sections[2],
|
||||||
"Recent Items",
|
"Ad-Hoc Items",
|
||||||
&app.state.recent_items,
|
&app.state.recent_items,
|
||||||
app.state.current_pane == 2,
|
app.state.current_pane == 2,
|
||||||
app.state.selected_indices[2],
|
app.state.selected_indices[2],
|
||||||
|
|
@ -100,10 +101,82 @@ fn render_main(frame: &mut Frame, app: &App) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn render_new_entry(frame: &mut Frame, app: &App) {
|
||||||
|
let area = centered_rect(60, 6, frame.size());
|
||||||
|
frame.render_widget(Clear, area);
|
||||||
|
|
||||||
|
let chunks = Layout::default()
|
||||||
|
.direction(Direction::Vertical)
|
||||||
|
.constraints([
|
||||||
|
Constraint::Length(3),
|
||||||
|
Constraint::Length(3),
|
||||||
|
])
|
||||||
|
.split(area);
|
||||||
|
|
||||||
|
// Task name input
|
||||||
|
let task_block = Block::default()
|
||||||
|
.title("Task Name")
|
||||||
|
.borders(Borders::ALL)
|
||||||
|
.style(Style::default().fg(if matches!(app.new_entry_mode, NewEntryMode::Task) {
|
||||||
|
ACTIVE_COLOR
|
||||||
|
} else {
|
||||||
|
Color::White
|
||||||
|
}));
|
||||||
|
|
||||||
|
let task_text = Paragraph::new(app.new_entry_buffer.as_str())
|
||||||
|
.block(task_block);
|
||||||
|
|
||||||
|
frame.render_widget(task_text, chunks[0]);
|
||||||
|
|
||||||
|
// Project input
|
||||||
|
let project_block = Block::default()
|
||||||
|
.title("Project (optional)")
|
||||||
|
.borders(Borders::ALL)
|
||||||
|
.style(Style::default().fg(if matches!(app.new_entry_mode, NewEntryMode::Project) {
|
||||||
|
ACTIVE_COLOR
|
||||||
|
} else {
|
||||||
|
Color::White
|
||||||
|
}));
|
||||||
|
|
||||||
|
let project_text = if !app.config.projects.is_empty() {
|
||||||
|
format!("{} (available: {})",
|
||||||
|
app.new_entry_project,
|
||||||
|
app.config.projects.join(", ")
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
app.new_entry_project.clone()
|
||||||
|
};
|
||||||
|
|
||||||
|
let project_text = Paragraph::new(project_text)
|
||||||
|
.block(project_block);
|
||||||
|
|
||||||
|
frame.render_widget(project_text, chunks[1]);
|
||||||
|
|
||||||
|
// Render command bar
|
||||||
|
let bar_area = Rect::new(
|
||||||
|
0,
|
||||||
|
frame.size().height - 1,
|
||||||
|
frame.size().width,
|
||||||
|
1,
|
||||||
|
);
|
||||||
|
|
||||||
|
let command_text = match app.new_entry_mode {
|
||||||
|
NewEntryMode::Task => "Enter task name, press Enter to continue",
|
||||||
|
NewEntryMode::Project => "Enter project name (optional), press Enter to save",
|
||||||
|
};
|
||||||
|
|
||||||
|
let command_bar = Paragraph::new(command_text)
|
||||||
|
.style(Style::default().fg(Color::White))
|
||||||
|
.alignment(Alignment::Left);
|
||||||
|
|
||||||
|
frame.render_widget(command_bar, bar_area);
|
||||||
|
}
|
||||||
|
|
||||||
fn render_bottom_bar(frame: &mut Frame, area: Rect, app: &App) {
|
fn render_bottom_bar(frame: &mut Frame, area: Rect, app: &App) {
|
||||||
if app.config.show_command_hints {
|
if app.config.show_command_hints {
|
||||||
let commands = vec![
|
let commands = vec![
|
||||||
("c", "config"),
|
("c", "config"),
|
||||||
|
("n", "new"),
|
||||||
("q", "quit"),
|
("q", "quit"),
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
@ -167,6 +240,104 @@ fn render_help_command_bar(frame: &mut Frame) {
|
||||||
frame.render_widget(command_bar, bar_area);
|
frame.render_widget(command_bar, bar_area);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn render_help(frame: &mut Frame, _app: &App) {
|
||||||
|
let width = frame.size().width.saturating_sub(4).min(60);
|
||||||
|
let area = centered_rect(width, frame.size().height.saturating_sub(2), frame.size());
|
||||||
|
|
||||||
|
frame.render_widget(Clear, area);
|
||||||
|
|
||||||
|
let help_text = vec![
|
||||||
|
"WAT - Watson Time Tracker Interface",
|
||||||
|
"",
|
||||||
|
"This tool helps you track time using Watson with a convenient interface.",
|
||||||
|
"",
|
||||||
|
"The interface is divided into three sections:",
|
||||||
|
"1. Permanent Items: Configured tasks that are always available",
|
||||||
|
"2. Recurring Items: Frequently used tasks that you might return to",
|
||||||
|
"3. Ad-Hoc Items: One-off tasks and quick entries",
|
||||||
|
"",
|
||||||
|
"Navigation:",
|
||||||
|
"- Use j/k or ↑/↓ to move selection up/down",
|
||||||
|
"- Use h/l or ←/→ to switch between panes",
|
||||||
|
"- Use Ctrl+n/Ctrl+p to switch between panes",
|
||||||
|
"- Press Enter to start/stop time tracking",
|
||||||
|
"- Press n to create a new task in the current section",
|
||||||
|
"",
|
||||||
|
"Main Commands:",
|
||||||
|
"j/k, arrows - Navigate",
|
||||||
|
"h/l, arrows - Switch panes",
|
||||||
|
"Enter - Start/stop timer",
|
||||||
|
"Ctrl+e - Edit tasks config",
|
||||||
|
"c - Edit app config",
|
||||||
|
"n - New task",
|
||||||
|
"q - Quit",
|
||||||
|
"? (or ESC) - Exit help",
|
||||||
|
];
|
||||||
|
|
||||||
|
let text = help_text.join("\n");
|
||||||
|
|
||||||
|
let block = Block::default()
|
||||||
|
.title("Help")
|
||||||
|
.borders(Borders::ALL)
|
||||||
|
.style(Style::default().fg(Color::White));
|
||||||
|
|
||||||
|
let paragraph = Paragraph::new(text)
|
||||||
|
.block(block)
|
||||||
|
.style(Style::default().fg(Color::White))
|
||||||
|
.wrap(ratatui::widgets::Wrap { trim: true });
|
||||||
|
|
||||||
|
frame.render_widget(paragraph, area);
|
||||||
|
render_help_command_bar(frame);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_config_help(frame: &mut Frame, _app: &App) {
|
||||||
|
let width = frame.size().width.saturating_sub(4).min(60);
|
||||||
|
let area = centered_rect(width, frame.size().height.saturating_sub(2), frame.size());
|
||||||
|
|
||||||
|
frame.render_widget(Clear, area);
|
||||||
|
|
||||||
|
let help_text = vec![
|
||||||
|
"WAT Configuration",
|
||||||
|
"",
|
||||||
|
"The configuration file is in YAML format and supports these options:",
|
||||||
|
"",
|
||||||
|
"show_help_hint: true/false",
|
||||||
|
" Controls visibility of the help hint in the bottom bar",
|
||||||
|
"",
|
||||||
|
"show_command_hints: true/false",
|
||||||
|
" Controls visibility of command hints in the bottom bar",
|
||||||
|
"",
|
||||||
|
"projects: [list of strings]",
|
||||||
|
" List of available project names",
|
||||||
|
"",
|
||||||
|
"strict_projects: true/false",
|
||||||
|
" If true, only projects from the projects list are allowed",
|
||||||
|
"",
|
||||||
|
"Example configuration:",
|
||||||
|
"show_help_hint: true",
|
||||||
|
"show_command_hints: true",
|
||||||
|
"projects:",
|
||||||
|
" - project1",
|
||||||
|
" - project2",
|
||||||
|
"strict_projects: false",
|
||||||
|
];
|
||||||
|
|
||||||
|
let text = help_text.join("\n");
|
||||||
|
|
||||||
|
let block = Block::default()
|
||||||
|
.title("Configuration Help")
|
||||||
|
.borders(Borders::ALL)
|
||||||
|
.style(Style::default().fg(Color::White));
|
||||||
|
|
||||||
|
let paragraph = Paragraph::new(text)
|
||||||
|
.block(block)
|
||||||
|
.style(Style::default().fg(Color::White))
|
||||||
|
.wrap(ratatui::widgets::Wrap { trim: true });
|
||||||
|
|
||||||
|
frame.render_widget(paragraph, area);
|
||||||
|
render_help_command_bar(frame);
|
||||||
|
}
|
||||||
|
|
||||||
fn render_section(
|
fn render_section(
|
||||||
frame: &mut Frame,
|
frame: &mut Frame,
|
||||||
area: Rect,
|
area: Rect,
|
||||||
|
|
@ -216,91 +387,6 @@ fn render_section(
|
||||||
frame.render_widget(list, area);
|
frame.render_widget(list, area);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_help(frame: &mut Frame, _app: &App) {
|
|
||||||
let width = frame.size().width.saturating_sub(4).min(60);
|
|
||||||
let area = centered_rect(width, frame.size().height.saturating_sub(2), frame.size());
|
|
||||||
|
|
||||||
frame.render_widget(Clear, area);
|
|
||||||
|
|
||||||
let help_text = vec![
|
|
||||||
"WAT - Watson Time Tracker Interface",
|
|
||||||
"",
|
|
||||||
"This tool helps you track time using Watson with a convenient interface.",
|
|
||||||
"",
|
|
||||||
"The interface is divided into three sections:",
|
|
||||||
"1. Permanent Items: Configured tasks that are always available",
|
|
||||||
"2. Recurring Items: Frequently used tasks that you might return to",
|
|
||||||
"3. Recent Items: One-off tasks, showing the last 20 used",
|
|
||||||
"",
|
|
||||||
"Navigation:",
|
|
||||||
"- Use j/k or ↑/↓ to move selection up/down",
|
|
||||||
"- Use h/l or ←/→ to switch between panes",
|
|
||||||
"- Press Enter to start/stop time tracking",
|
|
||||||
"",
|
|
||||||
"Main Commands:",
|
|
||||||
"j/k, arrows - Navigate",
|
|
||||||
"h/l, arrows - Switch panes",
|
|
||||||
"Enter - Start/stop timer",
|
|
||||||
"Ctrl+e - Edit tasks config",
|
|
||||||
"c - Edit app config",
|
|
||||||
"q - Quit",
|
|
||||||
"? (or ESC) - Exit help",
|
|
||||||
];
|
|
||||||
|
|
||||||
let text = help_text.join("\n");
|
|
||||||
|
|
||||||
let block = Block::default()
|
|
||||||
.title("Help")
|
|
||||||
.borders(Borders::ALL)
|
|
||||||
.style(Style::default().fg(Color::White));
|
|
||||||
|
|
||||||
let paragraph = Paragraph::new(text)
|
|
||||||
.block(block)
|
|
||||||
.style(Style::default().fg(Color::White))
|
|
||||||
.wrap(ratatui::widgets::Wrap { trim: true });
|
|
||||||
|
|
||||||
frame.render_widget(paragraph, area);
|
|
||||||
render_help_command_bar(frame);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn render_config_help(frame: &mut Frame, _app: &App) {
|
|
||||||
let width = frame.size().width.saturating_sub(4).min(60);
|
|
||||||
let area = centered_rect(width, frame.size().height.saturating_sub(2), frame.size());
|
|
||||||
|
|
||||||
frame.render_widget(Clear, area);
|
|
||||||
|
|
||||||
let help_text = vec![
|
|
||||||
"WAT Configuration",
|
|
||||||
"",
|
|
||||||
"The configuration file is in YAML format and supports these options:",
|
|
||||||
"",
|
|
||||||
"show_help_hint: true/false",
|
|
||||||
" Controls visibility of the help hint in the bottom bar",
|
|
||||||
"",
|
|
||||||
"show_command_hints: true/false",
|
|
||||||
" Controls visibility of command hints in the bottom bar",
|
|
||||||
"",
|
|
||||||
"Example configuration:",
|
|
||||||
"show_help_hint: true",
|
|
||||||
"show_command_hints: true",
|
|
||||||
];
|
|
||||||
|
|
||||||
let text = help_text.join("\n");
|
|
||||||
|
|
||||||
let block = Block::default()
|
|
||||||
.title("Configuration Help")
|
|
||||||
.borders(Borders::ALL)
|
|
||||||
.style(Style::default().fg(Color::White));
|
|
||||||
|
|
||||||
let paragraph = Paragraph::new(text)
|
|
||||||
.block(block)
|
|
||||||
.style(Style::default().fg(Color::White))
|
|
||||||
.wrap(ratatui::widgets::Wrap { trim: true });
|
|
||||||
|
|
||||||
frame.render_widget(paragraph, area);
|
|
||||||
render_help_command_bar(frame);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn centered_rect(width: u16, height: u16, r: Rect) -> Rect {
|
fn centered_rect(width: u16, height: u16, r: Rect) -> Rect {
|
||||||
let x = (r.width.saturating_sub(width)) / 2;
|
let x = (r.width.saturating_sub(width)) / 2;
|
||||||
let y = (r.height.saturating_sub(height)) / 2;
|
let y = (r.height.saturating_sub(height)) / 2;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue