diff --git a/board.go b/board.go new file mode 100644 index 0000000..c7e045d --- /dev/null +++ b/board.go @@ -0,0 +1,92 @@ +package main + +import ( + "github.com/charmbracelet/bubbles/v2/help" + "github.com/charmbracelet/bubbles/v2/key" + tea "github.com/charmbracelet/bubbletea/v2" + "github.com/charmbracelet/lipgloss/v2" +) + +type EditFormMsg struct { + title string + description string + index int + column column +} + +type NewFormMsg struct { + index int + column column +} + +type Board struct { + help help.Model + loaded bool + focused status + cols []column + quitting bool +} + +func NewBoard() *Board { + help := help.New() + help.ShowAll = true + return &Board{help: help, focused: todo} +} + +func (m *Board) Init() tea.Cmd { + return nil +} + +func (m *Board) Update(msg tea.Msg) (*Board, tea.Cmd) { + var cmd tea.Cmd + var cmds []tea.Cmd + switch msg := msg.(type) { + case tea.WindowSizeMsg: + m.help.Width = msg.Width - margin + for i := range m.cols { + m.cols[i], cmd = m.cols[i].Update(msg) + cmds = append(cmds, cmd) + } + m.loaded = true + return m, tea.Batch(cmds...) + case ReturnBoardMsg: + if !msg.abort { + return m, m.cols[m.focused].Set(msg.form.index, msg.form.CreateTask()) + } + case moveMsg: + return m, m.cols[m.focused.getNext()].Set(APPEND, msg.Task) + case tea.KeyMsg: + switch { + case key.Matches(msg, keys.Quit): + m.quitting = true + return m, tea.Quit + case key.Matches(msg, keys.Left): + m.cols[m.focused].Blur() + m.focused = m.focused.getPrev() + m.cols[m.focused].Focus() + case key.Matches(msg, keys.Right): + m.cols[m.focused].Blur() + m.focused = m.focused.getNext() + m.cols[m.focused].Focus() + } + } + m.cols[m.focused], cmd = m.cols[m.focused].Update(msg) + return m, cmd +} + +// Changing to pointer receiver to get back to this model after adding a new task via the form... Otherwise I would need to pass this model along to the form and it becomes highly coupled to the other models. +func (m *Board) View() string { + if m.quitting { + return "" + } + if !m.loaded { + return "loading..." + } + board := lipgloss.JoinHorizontal( + lipgloss.Left, + m.cols[todo].View(), + m.cols[inProgress].View(), + m.cols[done].View(), + ) + return lipgloss.JoinVertical(lipgloss.Left, board, m.help.View(keys)) +} diff --git a/column.go b/column.go index 8f54d02..c6494c2 100644 --- a/column.go +++ b/column.go @@ -1,10 +1,10 @@ package main import ( - "github.com/charmbracelet/bubbles/key" - "github.com/charmbracelet/bubbles/list" - tea "github.com/charmbracelet/bubbletea" - "github.com/charmbracelet/lipgloss" + "github.com/charmbracelet/bubbles/v2/key" + "github.com/charmbracelet/bubbles/v2/list" + tea "github.com/charmbracelet/bubbletea/v2" + "github.com/charmbracelet/lipgloss/v2" ) const APPEND = -1 @@ -45,7 +45,7 @@ func (c column) Init() tea.Cmd { } // Update handles all the I/O for columns. -func (c column) Update(msg tea.Msg) (tea.Model, tea.Cmd) { +func (c column) Update(msg tea.Msg) (column, tea.Cmd) { var cmd tea.Cmd switch msg := msg.(type) { case tea.WindowSizeMsg: @@ -56,16 +56,24 @@ func (c column) Update(msg tea.Msg) (tea.Model, tea.Cmd) { case key.Matches(msg, keys.Edit): if len(c.list.VisibleItems()) != 0 { task := c.list.SelectedItem().(Task) - f := NewForm(task.title, task.description) - f.index = c.list.Index() - f.col = c - return f.Update(nil) + // return f.Update(nil) + // + return c, func() tea.Msg { + return EditFormMsg{ + title: task.title, + description: task.description, + index: c.list.Index(), + column: c, + } + } } case key.Matches(msg, keys.New): - f := newDefaultForm() - f.index = APPEND - f.col = c - return f.Update(nil) + return c, func() tea.Msg { + return NewFormMsg{ + index: c.list.Index(), + column: c, + } + } case key.Matches(msg, keys.Delete): return c, c.DeleteCurrent() case key.Matches(msg, keys.Enter): diff --git a/data.go b/data.go index 27efb30..42282f4 100644 --- a/data.go +++ b/data.go @@ -1,10 +1,10 @@ package main -import "github.com/charmbracelet/bubbles/list" +import "github.com/charmbracelet/bubbles/v2/list" // Provides the mock data to fill the kanban board -func (b *Board) initLists() { +func (b *Board) initLists() *Board { b.cols = []column{ newColumn(todo), newColumn(inProgress), @@ -27,4 +27,5 @@ func (b *Board) initLists() { b.cols[done].list.SetItems([]list.Item{ Task{status: done, title: "stay cool", description: "as a cucumber"}, }) + return b } diff --git a/form.go b/form.go index 50bfc4d..b237706 100644 --- a/form.go +++ b/form.go @@ -1,14 +1,19 @@ package main import ( - "github.com/charmbracelet/bubbles/help" - "github.com/charmbracelet/bubbles/key" - "github.com/charmbracelet/bubbles/textarea" - "github.com/charmbracelet/bubbles/textinput" - tea "github.com/charmbracelet/bubbletea" - "github.com/charmbracelet/lipgloss" + "github.com/charmbracelet/bubbles/v2/help" + "github.com/charmbracelet/bubbles/v2/key" + "github.com/charmbracelet/bubbles/v2/textarea" + "github.com/charmbracelet/bubbles/v2/textinput" + tea "github.com/charmbracelet/bubbletea/v2" + "github.com/charmbracelet/lipgloss/v2" ) +type ReturnBoardMsg struct { + abort bool + form Form +} + type Form struct { help help.Model title textinput.Model @@ -17,31 +22,29 @@ type Form struct { index int } -func newDefaultForm() *Form { +func newDefaultForm() Form { return NewForm("task name", "") } -func NewForm(title, description string) *Form { +func NewForm(title, description string) Form { form := Form{ help: help.New(), title: textinput.New(), description: textarea.New(), } + form.title.SetWidth(defaultWidth) + form.description.SetWidth(defaultWidth) form.title.Placeholder = title form.description.Placeholder = description form.title.Focus() - return &form + return form } func (f Form) CreateTask() Task { return Task{f.col.status, f.title.Value(), f.description.Value()} } -func (f Form) Init() tea.Cmd { - return nil -} - -func (f Form) Update(msg tea.Msg) (tea.Model, tea.Cmd) { +func (f Form) Update(msg tea.Msg) (Form, tea.Cmd) { var cmd tea.Cmd switch msg := msg.(type) { case column: @@ -53,7 +56,7 @@ func (f Form) Update(msg tea.Msg) (tea.Model, tea.Cmd) { return f, tea.Quit case key.Matches(msg, keys.Back): - return board.Update(nil) + return f, func() tea.Msg { return ReturnBoardMsg{true, f} } case key.Matches(msg, keys.Enter): if f.title.Focused() { f.title.Blur() @@ -61,7 +64,7 @@ func (f Form) Update(msg tea.Msg) (tea.Model, tea.Cmd) { return f, textarea.Blink } // Return the completed form as a message. - return board.Update(f) + return f, func() tea.Msg { return ReturnBoardMsg{false, f} } } } if f.title.Focused() { diff --git a/go.mod b/go.mod index 0aec6b8..7f2ed23 100644 --- a/go.mod +++ b/go.mod @@ -1,29 +1,29 @@ module github.com/charmbracelet/kancli -go 1.19 +go 1.23.0 + +toolchain go1.24.1 require ( - github.com/charmbracelet/bubbles v0.16.1 - github.com/charmbracelet/bubbletea v0.24.2 - github.com/charmbracelet/lipgloss v0.7.1 + github.com/charmbracelet/bubbles/v2 v2.0.0-beta.1 + github.com/charmbracelet/bubbletea/v2 v2.0.0-beta.1 + github.com/charmbracelet/lipgloss/v2 v2.0.0-beta.1 ) require ( github.com/atotto/clipboard v0.1.4 // indirect - github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect - github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 // indirect + github.com/charmbracelet/colorprofile v0.3.0 // indirect + github.com/charmbracelet/x/ansi v0.8.0 // indirect + github.com/charmbracelet/x/cellbuf v0.0.13 // indirect + github.com/charmbracelet/x/input v0.3.4 // indirect + github.com/charmbracelet/x/term v0.2.1 // indirect + github.com/charmbracelet/x/windows v0.2.0 // indirect github.com/lucasb-eyer/go-colorful v1.2.0 // indirect - github.com/mattn/go-isatty v0.0.19 // indirect - github.com/mattn/go-localereader v0.0.1 // indirect - github.com/mattn/go-runewidth v0.0.14 // indirect - github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect + github.com/mattn/go-runewidth v0.0.16 // indirect github.com/muesli/cancelreader v0.2.2 // indirect - github.com/muesli/reflow v0.3.0 // indirect - github.com/muesli/termenv v0.15.1 // indirect - github.com/rivo/uniseg v0.4.4 // indirect - github.com/sahilm/fuzzy v0.1.0 // indirect - golang.org/x/sync v0.3.0 // indirect - golang.org/x/sys v0.9.0 // indirect - golang.org/x/term v0.9.0 // indirect - golang.org/x/text v0.10.0 // indirect + github.com/rivo/uniseg v0.4.7 // indirect + github.com/sahilm/fuzzy v0.1.1 // indirect + github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect + golang.org/x/sync v0.12.0 // indirect + golang.org/x/sys v0.31.0 // indirect ) diff --git a/go.sum b/go.sum index 7dc5939..d8a857c 100644 --- a/go.sum +++ b/go.sum @@ -1,46 +1,19 @@ -github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4= github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI= -github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= -github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= -github.com/charmbracelet/bubbles v0.16.1 h1:6uzpAAaT9ZqKssntbvZMlksWHruQLNxg49H5WdeuYSY= -github.com/charmbracelet/bubbles v0.16.1/go.mod h1:2QCp9LFlEsBQMvIYERr7Ww2H2bA7xen1idUDIzm/+Xc= -github.com/charmbracelet/bubbletea v0.24.2 h1:uaQIKx9Ai6Gdh5zpTbGiWpytMU+CfsPp06RaW2cx/SY= -github.com/charmbracelet/bubbletea v0.24.2/go.mod h1:XdrNrV4J8GiyshTtx3DNuYkR1FDaJmO3l2nejekbsgg= -github.com/charmbracelet/lipgloss v0.7.1 h1:17WMwi7N1b1rVWOjMT+rCh7sQkvDU75B2hbZpc5Kc1E= -github.com/charmbracelet/lipgloss v0.7.1/go.mod h1:yG0k3giv8Qj8edTCbbg6AlQ5e8KNWpFujkNawKNhE2c= -github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 h1:q2hJAaP1k2wIvVRd/hEHD7lacgqrCPS+k8g1MndzfWY= -github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81/go.mod h1:YynlIjWYF8myEu6sdkwKIvGQq+cOckRm6So2avqoYAk= -github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= -github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= +github.com/charmbracelet/bubbles/v2 v2.0.0-beta.1/go.mod h1:6HamsBKWqEC/FVHuQMHgQL+knPyvHH55HwJDHl/adMw= +github.com/charmbracelet/bubbletea/v2 v2.0.0-beta.1/go.mod h1:qbcZLI5z8R49v9xBdU5V5Dh5D2uccx8wSwBqxQyErqc= +github.com/charmbracelet/colorprofile v0.3.0/go.mod h1:oHJ340RS2nmG1zRGPmhJKJ/jf4FPNNk0P39/wBPA1G0= +github.com/charmbracelet/lipgloss/v2 v2.0.0-beta.1/go.mod h1:tRlx/Hu0lo/j9viunCN2H+Ze6JrmdjQlXUQvvArgaOc= +github.com/charmbracelet/x/ansi v0.8.0/go.mod h1:wdYl/ONOLHLIVmQaxbIYEC/cRKOQyjTkowiI4blgS9Q= +github.com/charmbracelet/x/cellbuf v0.0.13/go.mod h1:xe0nKWGd3eJgtqZRaN9RjMtK7xUYchjzPr7q6kcvCCs= +github.com/charmbracelet/x/input v0.3.4/go.mod h1:JI8RcvdZWQIhn09VzeK3hdp4lTz7+yhiEdpEQtZN+2c= +github.com/charmbracelet/x/term v0.2.1/go.mod h1:oQ4enTYFV7QN4m0i9mzHrViD7TQKvNEEkHUMCmsxdUg= +github.com/charmbracelet/x/windows v0.2.0/go.mod h1:ZibNFR49ZFqCXgP76sYanisxRyC+EYrBE7TTknD8s1s= github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= -github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= -github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4= -github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88= -github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= -github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU= -github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= -github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI= -github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo= -github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA= +github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo= -github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s= -github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8= -github.com/muesli/termenv v0.15.1 h1:UzuTb/+hhlBugQz28rpzey4ZuKcZ03MeKsoG7IJZIxs= -github.com/muesli/termenv v0.15.1/go.mod h1:HeAQPTzpfs016yGtA4g00CsdYnVLJvxsS4ANqrZs2sQ= -github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= -github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis= -github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= -github.com/sahilm/fuzzy v0.1.0 h1:FzWGaw2Opqyu+794ZQ9SYifWv2EIXpwP4q8dY1kDAwI= -github.com/sahilm/fuzzy v0.1.0/go.mod h1:VFvziUEIMCrT6A6tw2RFIXPXXmzXbOsSHF0DOI8ZK9Y= -golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= -golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= -golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.9.0 h1:KS/R3tvhPqvJvwcKfnBHJwwthS11LRhmM5D59eEXa0s= -golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/term v0.9.0 h1:GRRCnKYhdQrD8kfRAdQ6Zcw1P0OcELxGLKJvtjVMZ28= -golang.org/x/term v0.9.0/go.mod h1:M6DEAAIenWoTxdKrOltXcmDY3rSplQUkrvaDU5FcQyo= -golang.org/x/text v0.10.0 h1:UpjohKhiEgNc0CSauXmwYftY1+LlaC75SJwh0SgCX58= -golang.org/x/text v0.10.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +github.com/sahilm/fuzzy v0.1.1/go.mod h1:VFvziUEIMCrT6A6tw2RFIXPXXmzXbOsSHF0DOI8ZK9Y= +github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= +golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= diff --git a/keys.go b/keys.go index 02ea573..393fcfd 100644 --- a/keys.go +++ b/keys.go @@ -1,19 +1,20 @@ package main -import "github.com/charmbracelet/bubbles/key" +import "github.com/charmbracelet/bubbles/v2/key" // ShortHelp returns keybindings to be shown in the mini help view. It's part // of the key.Map interface. func (k keyMap) ShortHelp() []key.Binding { - return []key.Binding{k.Help, k.Quit} + return []key.Binding{k.New, k.Edit, k.Delete, k.Help, k.Quit} } // FullHelp returns keybindings for the expanded help view. It's part of the // key.Map interface. func (k keyMap) FullHelp() [][]key.Binding { return [][]key.Binding{ - {k.Up, k.Down, k.Left, k.Right}, // first column - {k.Help, k.Quit}, // second column + {k.New, k.Edit, k.Delete, k.Enter}, + {k.Up, k.Down, k.Left, k.Right}, + {k.Back, k.Quit}, } } @@ -34,15 +35,15 @@ type keyMap struct { var keys = keyMap{ New: key.NewBinding( key.WithKeys("n"), - key.WithHelp("n", "new"), + key.WithHelp("n", "new item"), ), Edit: key.NewBinding( key.WithKeys("e"), - key.WithHelp("e", "edit"), + key.WithHelp("e", "edit item"), ), Delete: key.NewBinding( key.WithKeys("d"), - key.WithHelp("d", "delete"), + key.WithHelp("d", "delete item"), ), Up: key.NewBinding( key.WithKeys("up", "k"), @@ -62,7 +63,7 @@ var keys = keyMap{ ), Enter: key.NewBinding( key.WithKeys("enter"), - key.WithHelp("enter", "enter"), + key.WithHelp("enter", "move item"), ), Help: key.NewBinding( key.WithKeys("?"), diff --git a/main.go b/main.go index d348ae1..d764b7b 100644 --- a/main.go +++ b/main.go @@ -4,9 +4,11 @@ import ( "fmt" "os" - tea "github.com/charmbracelet/bubbletea" + tea "github.com/charmbracelet/bubbletea/v2" ) +const defaultWidth int = 80 + type status int func (s status) getNext() status { @@ -41,9 +43,7 @@ func main() { } defer f.Close() - board = NewBoard() - board.initLists() - p := tea.NewProgram(board) + p := tea.NewProgram(newModel()) if _, err := p.Run(); err != nil { fmt.Println(err) os.Exit(1) diff --git a/model.go b/model.go index f537222..2cba570 100644 --- a/model.go +++ b/model.go @@ -1,85 +1,55 @@ package main import ( - "github.com/charmbracelet/bubbles/help" - "github.com/charmbracelet/bubbles/key" - tea "github.com/charmbracelet/bubbletea" - "github.com/charmbracelet/lipgloss" + tea "github.com/charmbracelet/bubbletea/v2" ) -type Board struct { - help help.Model - loaded bool - focused status - cols []column - quitting bool +type Model struct { + // false is board, true is form + modifying bool + board *Board + form Form } -func NewBoard() *Board { - help := help.New() - help.ShowAll = true - return &Board{help: help, focused: todo} +func newModel() Model { + return Model{ + board: NewBoard().initLists(), + form: newDefaultForm(), + } } -func (m *Board) Init() tea.Cmd { +func (m Model) Init() tea.Cmd { return nil } -func (m *Board) Update(msg tea.Msg) (tea.Model, tea.Cmd) { +func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { + var cmd tea.Cmd switch msg := msg.(type) { - case tea.WindowSizeMsg: - var cmd tea.Cmd - var cmds []tea.Cmd - m.help.Width = msg.Width - margin - for i := 0; i < len(m.cols); i++ { - var res tea.Model - res, cmd = m.cols[i].Update(msg) - m.cols[i] = res.(column) - cmds = append(cmds, cmd) - } - m.loaded = true - return m, tea.Batch(cmds...) - case Form: - return m, m.cols[m.focused].Set(msg.index, msg.CreateTask()) - case moveMsg: - return m, m.cols[m.focused.getNext()].Set(APPEND, msg.Task) - case tea.KeyMsg: - switch { - case key.Matches(msg, keys.Quit): - m.quitting = true - return m, tea.Quit - case key.Matches(msg, keys.Left): - m.cols[m.focused].Blur() - m.focused = m.focused.getPrev() - m.cols[m.focused].Focus() - case key.Matches(msg, keys.Right): - m.cols[m.focused].Blur() - m.focused = m.focused.getNext() - m.cols[m.focused].Focus() - } + case ReturnBoardMsg: + m.modifying = false + case NewFormMsg: + m.modifying = true + m.form = newDefaultForm() + m.form.index = APPEND + m.form.col = msg.column + case EditFormMsg: + m.modifying = true + m.form = NewForm(msg.title, msg.description) + m.form.index = msg.index + m.form.col = msg.column } - res, cmd := m.cols[m.focused].Update(msg) - if _, ok := res.(column); ok { - m.cols[m.focused] = res.(column) - } else { - return res, cmd + + if m.modifying { + m.form, cmd = m.form.Update(msg) + return m, cmd } + m.board, cmd = m.board.Update(msg) return m, cmd } -// Changing to pointer receiver to get back to this model after adding a new task via the form... Otherwise I would need to pass this model along to the form and it becomes highly coupled to the other models. -func (m *Board) View() string { - if m.quitting { - return "" - } - if !m.loaded { - return "loading..." +func (m Model) View() string { + if m.modifying { + return m.form.View() } - board := lipgloss.JoinHorizontal( - lipgloss.Left, - m.cols[todo].View(), - m.cols[inProgress].View(), - m.cols[done].View(), - ) - return lipgloss.JoinVertical(lipgloss.Left, board, m.help.View(keys)) + return m.board.View() }