From d28bb534ab9f1b71d911cfd613fa49d52b6d5f71 Mon Sep 17 00:00:00 2001 From: Robson Cruz Date: Fri, 22 Dec 2023 20:57:17 -0300 Subject: [PATCH 1/3] --wip-- [skip ci] --- cmd/note_edit.go | 18 +++++++ internal/ui/models.go | 41 ++++++++++++++++ internal/ui/views.go | 108 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 167 insertions(+) create mode 100644 internal/ui/models.go create mode 100644 internal/ui/views.go diff --git a/cmd/note_edit.go b/cmd/note_edit.go index 3a77b7f..8c41266 100644 --- a/cmd/note_edit.go +++ b/cmd/note_edit.go @@ -5,7 +5,9 @@ import ( "strconv" "time" + tea "github.com/charmbracelet/bubbletea" "github.com/deadpyxel/workday/internal/journal" + "github.com/deadpyxel/workday/internal/ui" "github.com/spf13/cobra" "github.com/spf13/viper" ) @@ -48,6 +50,22 @@ func editNoteInCurrentDay(cmd *cobra.Command, args []string) error { return fmt.Errorf("The index provided is not valid for the existing notes: %d", noteIdx) } + p := tea.NewProgram(ui.NewEditNoteModel(&entries[idx])) + if _, err := p.Run(); err != nil { + return err + } + + editState := ui.NewEditNoteState(&entries[idx].Notes[noteIdx]) + p = tea.NewProgram(editState) + if _, err := p.Run(); err != nil { + return err + } + + if !editState.Finished { + fmt.Println("Edit cancelled.") + return nil + } + entries[idx].Notes[noteIdx] = journal.Note{Contents: newNote} err = journal.SaveEntries(entries, journalPath) diff --git a/internal/ui/models.go b/internal/ui/models.go new file mode 100644 index 0000000..cfc42d8 --- /dev/null +++ b/internal/ui/models.go @@ -0,0 +1,41 @@ +package ui + +import ( + "strings" + + "github.com/charmbracelet/bubbles/textinput" + "github.com/deadpyxel/workday/internal/journal" +) + +type EditNoteModel struct { + choices []journal.Note + cursor int + selected map[int]struct{} +} + +func NewEditNoteModel(entry *journal.JournalEntry) *EditNoteModel { + return &EditNoteModel{choices: entry.Notes, selected: make(map[int]struct{})} +} + +type EditNoteState struct { + Note *journal.Note + NewNote textinput.Model + NewTags textinput.Model + Finished bool +} + +func NewEditNoteState(note *journal.Note) *EditNoteState { + contentInput := textinput.New() + contentInput.SetValue(note.Contents) + contentInput.Placeholder = "Enter note contents" + + tagsInput := textinput.New() + tagsInput.SetValue(strings.Join(note.Tags, ",")) + tagsInput.Placeholder = "Enter tags, comma separated" + + return &EditNoteState{ + Note: note, + NewNote: contentInput, + NewTags: tagsInput, + } +} diff --git a/internal/ui/views.go b/internal/ui/views.go new file mode 100644 index 0000000..a00aa01 --- /dev/null +++ b/internal/ui/views.go @@ -0,0 +1,108 @@ +package ui + +import ( + "fmt" + + "github.com/charmbracelet/bubbles/textinput" + tea "github.com/charmbracelet/bubbletea" +) + +func (m EditNoteModel) Init() tea.Cmd { + return nil +} + +func (m EditNoteModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { + switch msg := msg.(type) { + + // Is it a key press? + case tea.KeyMsg: + + // Cool, what was the actual key pressed? + switch msg.String() { + + // These keys should exit the program. + case "ctrl+c", "q": + return m, tea.Quit + + // The "up" and "k" keys move the cursor up + case "up", "k": + if m.cursor > 0 { + m.cursor-- + } + + // The "down" and "j" keys move the cursor down + case "down", "j": + if m.cursor < len(m.choices)-1 { + m.cursor++ + } + + // The "enter" key and the spacebar (a literal space) toggle + // the selected state for the item that the cursor is pointing at. + case "enter", " ": + _, ok := m.selected[m.cursor] + if ok { + delete(m.selected, m.cursor) + } else { + m.selected[m.cursor] = struct{}{} + } + } + } + + // Return the updated model to the Bubble Tea runtime for processing. + // Note that we're not returning a command. + return m, nil +} + +func (m EditNoteModel) View() string { + s := "Which Note do you want to edit?\n\n" + for i, note := range m.choices { + // Is the cursor pointing at this choice? + cursor := " " // No cursor + if m.cursor == i { + cursor = ">" // Render cursor + } + + // Is this choice selected? + checked := " " + if _, ok := m.selected[i]; ok { + checked = "x" + } + + // Render the row + s += fmt.Sprintf("%s [%s] %s\n", cursor, checked, ¬e) + } + + s += "\nPress q to quit.\n" + + return s +} + +func (s *EditNoteState) Init() tea.Cmd { + return textinput.Blink +} + +func (s *EditNoteState) Update(msg tea.Msg) (tea.Model, tea.Cmd) { + switch msg := msg.(type) { + case tea.KeyMsg: + switch msg.String() { + case "ctrl+c", "q": + return s, tea.Quit + case "enter": + s.Finished = true + return s, tea.Quit + } + default: + var cmd tea.Cmd + s.NewNote, cmd = s.NewNote.Update(msg) + // s.NewTags, cmd = s.NewTags.Update(msg) + return s, cmd + } + return s, nil +} + +func (s *EditNoteState) View() string { + if s.Finished { + return "" + } + return fmt.Sprintf("Editing note:\n\n%s\n\nTags: %s\n\nPress Enter to save, Ctrl+C to cancel.", s.NewNote.View(), s.NewTags.View()) +} From d9d5242838bbd7512cb75b8a40de27d4dfb9db7b Mon Sep 17 00:00:00 2001 From: Robson Cruz Date: Thu, 22 Feb 2024 10:42:46 -0300 Subject: [PATCH 2/3] test: update tests description --- internal/journal/utils_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/journal/utils_test.go b/internal/journal/utils_test.go index 13ef59c..d16604e 100644 --- a/internal/journal/utils_test.go +++ b/internal/journal/utils_test.go @@ -125,7 +125,7 @@ func TestCalculateTotalTime(t *testing.T) { t.Errorf("Expected %v, but got %v", expected, result) } }) - t.Run("When slice contains valid entries, returns expected result with no errors", func(t *testing.T) { + t.Run("When slice contains valid entries returns expected result with no errors", func(t *testing.T) { entries := []JournalEntry{ {StartTime: time.Date(2021, time.January, 1, 10, 0, 0, 0, time.UTC), EndTime: time.Date(2021, time.January, 1, 12, 0, 0, 0, time.UTC)}, {StartTime: time.Date(2021, time.January, 1, 14, 0, 0, 0, time.UTC), EndTime: time.Date(2021, time.January, 1, 16, 0, 0, 0, time.UTC)}, @@ -140,7 +140,7 @@ func TestCalculateTotalTime(t *testing.T) { } }) - t.Run("When slice contains invalid entries, returns 0 with error", func(t *testing.T) { + t.Run("When slice contains invalid entries returns 0 with error", func(t *testing.T) { entries := []JournalEntry{ {StartTime: time.Date(2021, time.January, 1, 10, 0, 0, 0, time.UTC), EndTime: time.Date(2021, time.January, 1, 9, 0, 0, 0, time.UTC)}, } From d8dfce93a0ec265043ebd30bdf9d31b0dc83b2ed Mon Sep 17 00:00:00 2001 From: Robson Cruz Date: Fri, 22 Mar 2024 16:32:16 -0300 Subject: [PATCH 3/3] refactor: update package structure Since the UI only relates the CLI, it should be part of that package by logic --- cmd/note_edit.go | 2 +- {internal => cmd}/ui/models.go | 0 {internal => cmd}/ui/views.go | 0 3 files changed, 1 insertion(+), 1 deletion(-) rename {internal => cmd}/ui/models.go (100%) rename {internal => cmd}/ui/views.go (100%) diff --git a/cmd/note_edit.go b/cmd/note_edit.go index 8c41266..9559528 100644 --- a/cmd/note_edit.go +++ b/cmd/note_edit.go @@ -6,8 +6,8 @@ import ( "time" tea "github.com/charmbracelet/bubbletea" + "github.com/deadpyxel/workday/cmd/ui" "github.com/deadpyxel/workday/internal/journal" - "github.com/deadpyxel/workday/internal/ui" "github.com/spf13/cobra" "github.com/spf13/viper" ) diff --git a/internal/ui/models.go b/cmd/ui/models.go similarity index 100% rename from internal/ui/models.go rename to cmd/ui/models.go diff --git a/internal/ui/views.go b/cmd/ui/views.go similarity index 100% rename from internal/ui/views.go rename to cmd/ui/views.go