From d1670391764cae02137faac530fb01aae0fd992b Mon Sep 17 00:00:00 2001 From: Dan Anglin Date: Wed, 10 Jan 2024 18:21:14 +0000 Subject: [PATCH] feat(ui): add support for editing cards This commit adds support for editing an existing card's title and description. The (previously named) input modal has been enhanced to support both creating and editing cards. Part of apollo/pelican#14 --- .golangci.yaml | 10 ++ internal/ui/{modalinput.go => cardmodal.go} | 43 ++++---- internal/ui/ui.go | 112 +++++++++++++------- 3 files changed, 107 insertions(+), 58 deletions(-) rename internal/ui/{modalinput.go => cardmodal.go} (69%) diff --git a/.golangci.yaml b/.golangci.yaml index db69838..4278331 100644 --- a/.golangci.yaml +++ b/.golangci.yaml @@ -13,6 +13,16 @@ output: sort-results: true linters-settings: + depguard: + rules: + main: + files: + - $all + allow: + - $gostd + - codeflow.dananglin.me.uk/apollo/pelican + - github.com/gdamore/tcell/v2 + - github.com/rivo/tview exhaustivestruct: struct-patterns: - 'forge.dananglin.me.uk/code/dananglin/pelican.Status' diff --git a/internal/ui/modalinput.go b/internal/ui/cardmodal.go similarity index 69% rename from internal/ui/modalinput.go rename to internal/ui/cardmodal.go index 2b2b91b..2cfca1a 100644 --- a/internal/ui/modalinput.go +++ b/internal/ui/cardmodal.go @@ -5,15 +5,23 @@ import ( "github.com/rivo/tview" ) -type modalInput struct { +type cardModalMode int + +const ( + create cardModalMode = iota + edit +) + +type cardModal struct { *tview.Form frame *tview.Frame title string description string - done func(string, string, bool) + done func(string, string, bool, cardModalMode) + mode cardModalMode } -func newModalInput() *modalInput { +func newCardModal() *cardModal { var ( background = tcell.ColorBlack.TrueColor() buttonBackground = tcell.ColorBlueViolet.TrueColor() @@ -24,12 +32,13 @@ func newModalInput() *modalInput { form = tview.NewForm() ) - modal := modalInput{ + modal := cardModal{ Form: form, frame: tview.NewFrame(form), title: "", description: "", done: nil, + mode: create, } // Stylise the buttons @@ -51,49 +60,39 @@ func newModalInput() *modalInput { SetBackgroundColor(background). SetBorderPadding(1, 1, 1, 1) - modal.AddButton("Create card", func() { + modal.AddButton("Save", func() { if modal.done != nil { - modal.done(modal.title, modal.description, true) + modal.done(modal.title, modal.description, true, modal.mode) } - modal.reset() }) modal.AddButton("Cancel", func() { if modal.done != nil { - modal.done(modal.title, modal.description, false) + modal.done(modal.title, modal.description, false, modal.mode) } - modal.reset() }) - modal.addInputFields() - - modal.frame.SetTitle(" New Card ") - return &modal } -func (m *modalInput) reset() { +func (m *cardModal) updateInputFields(title, description string) { m.Clear(false) - m.addInputFields() -} - -func (m *modalInput) addInputFields() { - m.AddInputField("Title", "", 60, nil, func(text string) { + m.AddInputField("Title", title, 60, nil, func(text string) { m.title = text }) - m.AddTextArea("Description", "", 60, 10, 0, func(text string) { + m.AddTextArea("Description", description, 60, 10, 0, func(text string) { m.description = text }) } -func (m *modalInput) SetDoneFunc(handler func(string, string, bool)) *modalInput { +func (m *cardModal) setDoneFunc(handler func(string, string, bool, cardModalMode)) *cardModal { m.done = handler return m } -func (m *modalInput) Draw(screen tcell.Screen) { +func (m *cardModal) Draw(screen tcell.Screen) { buttonsWidth := 20 screenWidth, screenHeight := screen.Size() width := screenWidth / 3 diff --git a/internal/ui/ui.go b/internal/ui/ui.go index f87ecb5..df221ac 100644 --- a/internal/ui/ui.go +++ b/internal/ui/ui.go @@ -24,10 +24,10 @@ const ( ) const ( - mainPage string = "main" - quitPage string = "quit" - addPage string = "add" - deleteCardPage string = "delete card" + mainPage string = "main" + quitPage string = "quit" + cardModalPage string = "card modal" + deleteCardModalPage string = "delete card modal" ) type UI struct { @@ -40,7 +40,7 @@ type UI struct { board board.Board mode boardMode quitModal *tview.Modal - addModal *modalInput + cardModal *cardModal deleteCardModal *tview.Modal statusSelection statusSelection } @@ -57,7 +57,7 @@ func NewUI(path string) (UI, error) { pages: tview.NewPages(), flex: tview.NewFlex(), quitModal: tview.NewModal(), - addModal: newModalInput(), + cardModal: newCardModal(), focusedColumn: 0, columns: nil, board: kanban, @@ -95,8 +95,10 @@ func (u *UI) init() error { u.shiftColumnFocus(next) case letter == 'c': if u.mode == normal { - u.pages.ShowPage(addPage) - u.SetFocus(u.addModal) + u.cardModal.mode = create + u.cardModal.updateInputFields("", "") + u.pages.ShowPage(cardModalPage) + u.SetFocus(u.cardModal) } case letter == 'm': if u.mode == normal { @@ -105,9 +107,19 @@ func (u *UI) init() error { u.statusSelection.currentStatusID = u.columns[u.focusedColumn].statusID u.mode = selection } + case letter == 'e': + if u.mode == normal { + u.cardModal.mode = edit + focusedCard := u.columns[u.focusedColumn].focusedCard + cardID := u.columns[u.focusedColumn].cards[focusedCard].id + card, _ := u.board.Card(cardID) + u.cardModal.updateInputFields(card.Title, card.Description) + u.pages.ShowPage(cardModalPage) + u.SetFocus(u.cardModal) + } case key == tcell.KeyCtrlD: if u.mode == normal { - u.pages.ShowPage(deleteCardPage) + u.pages.ShowPage(deleteCardModalPage) u.SetFocus(u.deleteCardModal) } case key == tcell.KeyCtrlQ: @@ -139,11 +151,11 @@ func (u *UI) init() error { u.initQuitModal() u.pages.AddPage(quitPage, u.quitModal, false, false) - u.initAddInputModal() - u.pages.AddPage(addPage, u.addModal, false, false) + u.initCardModal() + u.pages.AddPage(cardModalPage, u.cardModal, false, false) u.initDeleteCardModal() - u.pages.AddPage(deleteCardPage, u.deleteCardModal, false, false) + u.pages.AddPage(deleteCardModalPage, u.deleteCardModal, false, false) u.SetRoot(u.pages, true) @@ -154,18 +166,25 @@ func (u *UI) init() error { return nil } -// initAddInputModal initialises the add input modal. -func (u *UI) initAddInputModal() { - doneFunc := func(text, description string, success bool) { +// initCardModal initialises the card modal. +func (u *UI) initCardModal() { + doneFunc := func(title, description string, success bool, mode cardModalMode) { if success { - _ = u.newCard(text, description) + switch mode { + case create: + _ = u.newCard(title, description) + case edit: + focusedCard := u.columns[u.focusedColumn].focusedCard + cardID := u.columns[u.focusedColumn].cards[focusedCard].id + _ = u.editCard(cardID, title, description) + } } - u.pages.HidePage(addPage) + u.pages.HidePage(cardModalPage) u.setColumnFocus() } - u.addModal.SetDoneFunc(doneFunc) + u.cardModal.setDoneFunc(doneFunc) } // initDeleteCardModal initialises the modal for deleting cards. @@ -176,7 +195,7 @@ func (u *UI) initDeleteCardModal() { _ = u.refresh(true) } - u.pages.HidePage(deleteCardPage) + u.pages.HidePage(deleteCardModalPage) u.setColumnFocus() } @@ -185,20 +204,6 @@ func (u *UI) initDeleteCardModal() { SetDoneFunc(doneFunc) } -// deleteCard deletes a card from the board. -func (u *UI) deleteCard() { - statusID := u.columns[u.focusedColumn].statusID - focusedCard := u.columns[u.focusedColumn].focusedCard - cardID := u.columns[u.focusedColumn].cards[focusedCard].id - - args := board.DeleteCardArgs{ - CardID: cardID, - StatusID: statusID, - } - - _ = u.board.DeleteCard(args) -} - // initQuitModal initialises the quit modal. func (u *UI) initQuitModal() { doneFunc := func(_ int, buttonLabel string) { @@ -217,10 +222,10 @@ func (u *UI) initQuitModal() { } // newCard creates and saves a new card to the database. -func (u *UI) newCard(title, content string) error { +func (u *UI) newCard(title, description string) error { args := board.CardArgs{ - NewTitle: title, - NewDescription: content, + NewTitle: title, + NewDescription: description, } if _, err := u.board.CreateCard(args); err != nil { @@ -232,6 +237,40 @@ func (u *UI) newCard(title, content string) error { return nil } +// editCard saves and edited card to the database. +func (u *UI) editCard(cardID int, title, description string) error { + args := board.UpdateCardArgs{ + CardID: cardID, + CardArgs: board.CardArgs{ + NewTitle: title, + NewDescription: description, + }, + } + + if err := u.board.UpdateCard(args); err != nil { + return fmt.Errorf("unable to edit card with ID: %d; %w", cardID, err) + } + + _ = u.refresh(true) + + return nil +} + +// deleteCard deletes a card from the board. +func (u *UI) deleteCard() { + statusID := u.columns[u.focusedColumn].statusID + focusedCard := u.columns[u.focusedColumn].focusedCard + cardID := u.columns[u.focusedColumn].cards[focusedCard].id + + args := board.DeleteCardArgs{ + CardID: cardID, + StatusID: statusID, + } + + _ = u.board.DeleteCard(args) +} + +// initColumns initialises the columns of the Kanban board. func (u *UI) initColumns() error { u.flex.Clear() @@ -317,6 +356,7 @@ func (u *UI) shutdown() { u.Stop() } +// boardMode returns the current board mode. func (u *UI) boardMode() boardMode { return u.mode }