Fix projects, remove output in interface, log output to file
This commit is contained in:
parent
401ee37b32
commit
ae045167bf
5 changed files with 274 additions and 162 deletions
175
src/ui.rs
175
src/ui.rs
|
|
@ -1,12 +1,15 @@
|
|||
use ratatui::{
|
||||
layout::{Constraint, Direction, Layout, Rect, Alignment},
|
||||
layout::{Alignment, Constraint, Direction, Layout, Rect},
|
||||
style::{Color, Modifier, Style},
|
||||
text::{Line, Span},
|
||||
widgets::{Block, Borders, List, ListItem, Paragraph, Clear},
|
||||
widgets::{Block, Borders, Clear, List, ListItem, Paragraph},
|
||||
Frame,
|
||||
};
|
||||
|
||||
use crate::{state::{AppState, TimeItem}, app::{App, Screen, NewEntryMode}};
|
||||
use crate::{
|
||||
app::{App, NewEntryMode, Screen},
|
||||
state::{AppState, TimeItem},
|
||||
};
|
||||
|
||||
const ACTIVE_COLOR: Color = Color::Green;
|
||||
const INACTIVE_COLOR: Color = Color::Yellow;
|
||||
|
|
@ -25,24 +28,28 @@ fn render_main(frame: &mut Frame, app: &App) {
|
|||
let show_bottom_bar = app.config.show_help_hint || app.config.show_command_hints;
|
||||
let has_status = app.status_message.is_some();
|
||||
let bottom_height = if show_bottom_bar {
|
||||
if has_status { 2 } else { 1 }
|
||||
if has_status {
|
||||
2
|
||||
} else {
|
||||
1
|
||||
}
|
||||
} else {
|
||||
if has_status { 1 } else { 0 }
|
||||
if has_status {
|
||||
1
|
||||
} else {
|
||||
0
|
||||
}
|
||||
};
|
||||
|
||||
let constraints = if bottom_height > 0 {
|
||||
vec![
|
||||
Constraint::Min(3), // At least 3 lines for each section
|
||||
Constraint::Min(3), // At least 3 lines for each section
|
||||
Constraint::Min(3),
|
||||
Constraint::Min(3),
|
||||
Constraint::Length(bottom_height), // Command bar + optional status
|
||||
Constraint::Length(bottom_height), // Command bar + optional status
|
||||
]
|
||||
} else {
|
||||
vec![
|
||||
Constraint::Min(3),
|
||||
Constraint::Min(3),
|
||||
Constraint::Min(3),
|
||||
]
|
||||
vec![Constraint::Min(3), Constraint::Min(3), Constraint::Min(3)]
|
||||
};
|
||||
|
||||
let chunks = Layout::default()
|
||||
|
|
@ -109,24 +116,22 @@ fn render_new_entry(frame: &mut Frame, app: &App) {
|
|||
|
||||
let chunks = Layout::default()
|
||||
.direction(Direction::Vertical)
|
||||
.constraints([
|
||||
Constraint::Length(3),
|
||||
Constraint::Length(3),
|
||||
])
|
||||
.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
|
||||
}));
|
||||
.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);
|
||||
let task_text = Paragraph::new(app.new_entry_buffer.as_str()).block(task_block);
|
||||
|
||||
frame.render_widget(task_text, chunks[0]);
|
||||
|
||||
|
|
@ -134,14 +139,17 @@ fn render_new_entry(frame: &mut Frame, app: &App) {
|
|||
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
|
||||
}));
|
||||
.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: {})",
|
||||
format!(
|
||||
"{} (available: {})",
|
||||
app.new_entry_project,
|
||||
app.config.projects.join(", ")
|
||||
)
|
||||
|
|
@ -149,18 +157,12 @@ fn render_new_entry(frame: &mut Frame, app: &App) {
|
|||
app.new_entry_project.clone()
|
||||
};
|
||||
|
||||
let project_text = Paragraph::new(project_text)
|
||||
.block(project_block);
|
||||
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 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",
|
||||
|
|
@ -180,8 +182,8 @@ fn render_bottom_bar(frame: &mut Frame, area: Rect, app: &App) {
|
|||
Layout::default()
|
||||
.direction(Direction::Vertical)
|
||||
.constraints([
|
||||
Constraint::Length(1), // Status message
|
||||
Constraint::Length(1), // Command bar
|
||||
Constraint::Length(1), // Status message
|
||||
Constraint::Length(1), // Command bar
|
||||
])
|
||||
.split(area)
|
||||
} else {
|
||||
|
|
@ -210,19 +212,23 @@ fn render_bottom_bar(frame: &mut Frame, area: Rect, app: &App) {
|
|||
("d", "delete"),
|
||||
("q", "quit"),
|
||||
];
|
||||
|
||||
let command_text = format!(" {}", commands.iter()
|
||||
.map(|(key, desc)| format!("{} ({})", key, desc))
|
||||
.collect::<Vec<_>>()
|
||||
.join(" · "));
|
||||
|
||||
|
||||
let command_text = format!(
|
||||
" {}",
|
||||
commands
|
||||
.iter()
|
||||
.map(|(key, desc)| format!("{} ({})", key, desc))
|
||||
.collect::<Vec<_>>()
|
||||
.join(" · ")
|
||||
);
|
||||
|
||||
let command_area = if app.config.show_help_hint {
|
||||
// Leave space for help hint
|
||||
Rect::new(
|
||||
chunks[command_line_idx].x,
|
||||
chunks[command_line_idx].y,
|
||||
chunks[command_line_idx].width.saturating_sub(12),
|
||||
1
|
||||
chunks[command_line_idx].x,
|
||||
chunks[command_line_idx].y,
|
||||
chunks[command_line_idx].width.saturating_sub(12),
|
||||
1,
|
||||
)
|
||||
} else {
|
||||
chunks[command_line_idx]
|
||||
|
|
@ -231,7 +237,7 @@ fn render_bottom_bar(frame: &mut Frame, area: Rect, app: &App) {
|
|||
let command_bar = Paragraph::new(command_text)
|
||||
.style(Style::default().fg(Color::White))
|
||||
.alignment(Alignment::Left);
|
||||
|
||||
|
||||
frame.render_widget(command_bar, command_area);
|
||||
}
|
||||
|
||||
|
|
@ -239,49 +245,50 @@ fn render_bottom_bar(frame: &mut Frame, area: Rect, app: &App) {
|
|||
let help_hint = Paragraph::new("(?) for help")
|
||||
.alignment(Alignment::Right)
|
||||
.style(Style::default().fg(Color::DarkGray));
|
||||
|
||||
|
||||
let help_area = Rect::new(
|
||||
chunks[command_line_idx].x + chunks[command_line_idx].width.saturating_sub(12),
|
||||
chunks[command_line_idx].y,
|
||||
12,
|
||||
1,
|
||||
);
|
||||
|
||||
|
||||
frame.render_widget(help_hint, help_area);
|
||||
}
|
||||
}
|
||||
|
||||
fn render_help_command_bar(frame: &mut Frame) {
|
||||
let commands = vec![
|
||||
("c", "configuration help"),
|
||||
("q/ESC", "back"),
|
||||
];
|
||||
|
||||
let command_text = format!(" {}", commands.iter()
|
||||
.map(|(key, desc)| format!("{} ({})", key, desc))
|
||||
.collect::<Vec<_>>()
|
||||
.join(" · "));
|
||||
|
||||
let commands = vec![("c", "configuration help"), ("q/ESC", "back")];
|
||||
|
||||
let command_text = format!(
|
||||
" {}",
|
||||
commands
|
||||
.iter()
|
||||
.map(|(key, desc)| format!("{} ({})", key, desc))
|
||||
.collect::<Vec<_>>()
|
||||
.join(" · ")
|
||||
);
|
||||
|
||||
let bar_area = Rect::new(
|
||||
0,
|
||||
frame.size().height.saturating_sub(1),
|
||||
frame.size().width,
|
||||
1,
|
||||
);
|
||||
|
||||
|
||||
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_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",
|
||||
"",
|
||||
|
|
@ -312,17 +319,17 @@ fn render_help(frame: &mut Frame, _app: &App) {
|
|||
];
|
||||
|
||||
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);
|
||||
}
|
||||
|
|
@ -330,9 +337,9 @@ fn render_help(frame: &mut Frame, _app: &App) {
|
|||
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",
|
||||
"",
|
||||
|
|
@ -360,17 +367,17 @@ fn render_config_help(frame: &mut Frame, _app: &App) {
|
|||
];
|
||||
|
||||
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);
|
||||
}
|
||||
|
|
@ -384,8 +391,12 @@ fn render_section(
|
|||
selected: usize,
|
||||
state: &AppState,
|
||||
) {
|
||||
let border_color = if is_active { ACTIVE_COLOR } else { INACTIVE_COLOR };
|
||||
|
||||
let border_color = if is_active {
|
||||
ACTIVE_COLOR
|
||||
} else {
|
||||
INACTIVE_COLOR
|
||||
};
|
||||
|
||||
let block = Block::default()
|
||||
.borders(Borders::ALL)
|
||||
.title(title)
|
||||
|
|
@ -402,9 +413,13 @@ fn render_section(
|
|||
.unwrap_or(false);
|
||||
|
||||
let style = if is_running {
|
||||
Style::default().fg(ACTIVE_COLOR).add_modifier(Modifier::BOLD)
|
||||
Style::default()
|
||||
.fg(ACTIVE_COLOR)
|
||||
.add_modifier(Modifier::BOLD)
|
||||
} else if i == selected && is_active {
|
||||
Style::default().fg(border_color).add_modifier(Modifier::REVERSED)
|
||||
Style::default()
|
||||
.fg(border_color)
|
||||
.add_modifier(Modifier::REVERSED)
|
||||
} else {
|
||||
Style::default()
|
||||
};
|
||||
|
|
@ -427,11 +442,11 @@ fn render_section(
|
|||
fn centered_rect(width: u16, height: u16, r: Rect) -> Rect {
|
||||
let x = (r.width.saturating_sub(width)) / 2;
|
||||
let y = (r.height.saturating_sub(height)) / 2;
|
||||
|
||||
|
||||
Rect {
|
||||
x: r.x + x,
|
||||
y: r.y + y,
|
||||
width: width.min(r.width),
|
||||
height: height.min(r.height),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue