Add annotations
This commit is contained in:
parent
89d1c02690
commit
599cc22463
2 changed files with 173 additions and 0 deletions
123
src/app.rs
123
src/app.rs
|
|
@ -22,6 +22,7 @@ pub enum Screen {
|
|||
ReassignProject,
|
||||
LogView,
|
||||
LogViewHelp,
|
||||
AddAnnotation,
|
||||
}
|
||||
|
||||
pub enum LogViewPeriod {
|
||||
|
|
@ -70,6 +71,8 @@ pub struct App {
|
|||
pub help_scroll: usize,
|
||||
pub clipboard: Option<arboard::Clipboard>,
|
||||
pub status_message: Option<(String, std::time::Instant)>,
|
||||
pub annotation_buffer: String,
|
||||
pub annotation_cursor: usize,
|
||||
}
|
||||
|
||||
pub enum NewEntryMode {
|
||||
|
|
@ -130,6 +133,8 @@ impl App {
|
|||
help_scroll: 0,
|
||||
clipboard: arboard::Clipboard::new().ok(),
|
||||
status_message: None,
|
||||
annotation_buffer: String::new(),
|
||||
annotation_cursor: 0,
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -147,6 +152,7 @@ impl App {
|
|||
Screen::ReassignProject => self.handle_reassign_project_event(event),
|
||||
Screen::LogView => self.handle_log_view_event(event),
|
||||
Screen::LogViewHelp => self.handle_log_view_help_event(event),
|
||||
Screen::AddAnnotation => self.handle_add_annotation_event(event),
|
||||
};
|
||||
|
||||
// If we switched screens, signal that we need to clear
|
||||
|
|
@ -487,6 +493,12 @@ impl App {
|
|||
self.edit_selected_frame()?;
|
||||
}
|
||||
}
|
||||
KeyCode::Char('a') => {
|
||||
// Only allow annotation when selecting individual entry
|
||||
if matches!(self.log_view_selection_level, LogViewSelection::Entry) {
|
||||
self.start_add_annotation();
|
||||
}
|
||||
}
|
||||
KeyCode::Char('x') => {
|
||||
// Only allow delete when selecting individual entry
|
||||
if matches!(self.log_view_selection_level, LogViewSelection::Entry) {
|
||||
|
|
@ -1658,4 +1670,115 @@ impl App {
|
|||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn start_add_annotation(&mut self) {
|
||||
self.current_screen = Screen::AddAnnotation;
|
||||
self.annotation_buffer.clear();
|
||||
self.annotation_cursor = 0;
|
||||
}
|
||||
|
||||
fn handle_add_annotation_event(&mut self, event: Event) -> anyhow::Result<bool> {
|
||||
match event {
|
||||
Event::Key(KeyEvent {
|
||||
code, modifiers, ..
|
||||
}) => {
|
||||
match (code, modifiers) {
|
||||
(KeyCode::Esc, _) => {
|
||||
self.current_screen = Screen::LogView;
|
||||
self.annotation_buffer.clear();
|
||||
self.annotation_cursor = 0;
|
||||
}
|
||||
(KeyCode::Enter, _) => {
|
||||
if !self.annotation_buffer.is_empty() {
|
||||
self.add_annotation_to_frame()?;
|
||||
}
|
||||
self.current_screen = Screen::LogView;
|
||||
self.annotation_buffer.clear();
|
||||
self.annotation_cursor = 0;
|
||||
}
|
||||
(KeyCode::Backspace, _) => {
|
||||
if self.annotation_cursor > 0 {
|
||||
let idx = self.annotation_cursor - 1;
|
||||
if idx < self.annotation_buffer.len() {
|
||||
self.annotation_buffer.remove(idx);
|
||||
self.annotation_cursor -= 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
(KeyCode::Char(c), m) if m.is_empty() => {
|
||||
if self.annotation_cursor <= self.annotation_buffer.len() {
|
||||
self.annotation_buffer.insert(self.annotation_cursor, c);
|
||||
self.annotation_cursor += 1;
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
Ok(false)
|
||||
}
|
||||
|
||||
fn add_annotation_to_frame(&mut self) -> anyhow::Result<()> {
|
||||
use serde_json::Value;
|
||||
|
||||
// Check if selection is valid
|
||||
if self.log_view_selected >= self.log_view_frame_indices.len() {
|
||||
self.set_status_message("Invalid selection");
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// Get the actual frame index from the display index
|
||||
let frame_idx = self.log_view_frame_indices[self.log_view_selected];
|
||||
if frame_idx >= self.log_view_frames.len() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let frame_id = self.log_view_frames[frame_idx].id.clone();
|
||||
|
||||
// Read watson frames file
|
||||
let frames_path = dirs::config_dir()
|
||||
.ok_or_else(|| anyhow::anyhow!("Could not find config directory"))?
|
||||
.join("watson")
|
||||
.join("frames");
|
||||
|
||||
let frames_content = std::fs::read_to_string(&frames_path)?;
|
||||
let mut frames: Value = serde_json::from_str(&frames_content)?;
|
||||
|
||||
// Find and update the frame with matching id
|
||||
if let Some(frames_array) = frames.as_array_mut() {
|
||||
for frame in frames_array {
|
||||
if let Some(frame_array) = frame.as_array_mut() {
|
||||
// Frame format: [start, stop, project, id, tags, updated]
|
||||
if frame_array.len() > 4 {
|
||||
if let Some(id) = frame_array[3].as_str() {
|
||||
if id == frame_id {
|
||||
// Update tags array (index 4)
|
||||
if let Some(tags) = frame_array[4].as_array_mut() {
|
||||
// Add the annotation tag
|
||||
tags.push(Value::String(self.annotation_buffer.clone()));
|
||||
}
|
||||
// Update timestamp (index 5)
|
||||
if frame_array.len() > 5 {
|
||||
frame_array[5] = Value::Number(chrono::Utc::now().timestamp().into());
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Write back to frames file
|
||||
let updated_content = serde_json::to_string_pretty(&frames)?;
|
||||
std::fs::write(&frames_path, updated_content)?;
|
||||
|
||||
// Reload log content
|
||||
self.load_log_content()?;
|
||||
self.set_status_message("Annotation added");
|
||||
|
||||
self.needs_clear = true;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
|
|||
50
src/ui.rs
50
src/ui.rs
|
|
@ -24,6 +24,7 @@ pub fn render(frame: &mut Frame, app: &App) {
|
|||
Screen::ReassignProject => render_reassign_project(frame, app),
|
||||
Screen::LogView => render_log_view(frame, app),
|
||||
Screen::LogViewHelp => render_log_view_help(frame, app),
|
||||
Screen::AddAnnotation => render_add_annotation(frame, app),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -957,6 +958,7 @@ fn render_log_view_help(frame: &mut Frame, app: &App) {
|
|||
"",
|
||||
"Actions:",
|
||||
"- e: Edit the selected entry (Entry level only)",
|
||||
"- a: Add annotation tag to the selected entry (Entry level only)",
|
||||
"- x: Delete the selected entry (Entry level only)",
|
||||
"- b: Backfill - set entry start time to previous entry's end time",
|
||||
" (Entry level, By Date view only)",
|
||||
|
|
@ -1005,3 +1007,51 @@ fn render_log_view_help(frame: &mut Frame, app: &App) {
|
|||
|
||||
frame.render_widget(command_bar, bar_area);
|
||||
}
|
||||
|
||||
fn render_add_annotation(frame: &mut Frame, app: &App) {
|
||||
let area = centered_rect(60, 5, frame.size());
|
||||
frame.render_widget(Clear, area);
|
||||
|
||||
let chunks = Layout::default()
|
||||
.direction(Direction::Vertical)
|
||||
.constraints([Constraint::Length(3), Constraint::Length(2)])
|
||||
.split(area);
|
||||
|
||||
// Get current entry to show in title
|
||||
let current_entry = if app.log_view_selected < app.log_view_frame_indices.len() {
|
||||
let frame_idx = app.log_view_frame_indices[app.log_view_selected];
|
||||
app.log_view_frames.get(frame_idx).map(|frame| {
|
||||
if frame.tags.is_empty() {
|
||||
frame.project.clone()
|
||||
} else {
|
||||
format!("{} [{}]", frame.tags.join(", "), frame.project)
|
||||
}
|
||||
})
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let title = if let Some(entry) = current_entry {
|
||||
format!("Add Annotation to: {}", entry)
|
||||
} else {
|
||||
"Add Annotation".to_string()
|
||||
};
|
||||
|
||||
// Annotation input
|
||||
let annotation_block = Block::default()
|
||||
.title(title)
|
||||
.borders(Borders::ALL)
|
||||
.style(Style::default().fg(ACTIVE_COLOR));
|
||||
|
||||
let annotation_text = Paragraph::new(app.annotation_buffer.as_str())
|
||||
.block(annotation_block);
|
||||
|
||||
frame.render_widget(annotation_text, chunks[0]);
|
||||
|
||||
// Help text
|
||||
let help_text = Paragraph::new("Enter annotation text, press Enter to save, Esc to cancel")
|
||||
.style(Style::default().fg(Color::DarkGray))
|
||||
.alignment(Alignment::Center);
|
||||
|
||||
frame.render_widget(help_text, chunks[1]);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue