feat(ui): add support for editing cards #15

Manually merged
dananglin merged 2 commits from 14-edit-card-title-description into main 2024-01-10 21:10:03 +00:00
3 changed files with 107 additions and 58 deletions
Showing only changes of commit d167039176 - Show all commits

View file

@ -13,6 +13,16 @@ output:
sort-results: true sort-results: true
linters-settings: 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: exhaustivestruct:
struct-patterns: struct-patterns:
- 'forge.dananglin.me.uk/code/dananglin/pelican.Status' - 'forge.dananglin.me.uk/code/dananglin/pelican.Status'

View file

@ -5,15 +5,23 @@ import (
"github.com/rivo/tview" "github.com/rivo/tview"
) )
type modalInput struct { type cardModalMode int
const (
create cardModalMode = iota
edit
)
type cardModal struct {
*tview.Form *tview.Form
frame *tview.Frame frame *tview.Frame
title string title string
description string description string
done func(string, string, bool) done func(string, string, bool, cardModalMode)
mode cardModalMode
} }
func newModalInput() *modalInput { func newCardModal() *cardModal {
var ( var (
background = tcell.ColorBlack.TrueColor() background = tcell.ColorBlack.TrueColor()
buttonBackground = tcell.ColorBlueViolet.TrueColor() buttonBackground = tcell.ColorBlueViolet.TrueColor()
@ -24,12 +32,13 @@ func newModalInput() *modalInput {
form = tview.NewForm() form = tview.NewForm()
) )
modal := modalInput{ modal := cardModal{
Form: form, Form: form,
frame: tview.NewFrame(form), frame: tview.NewFrame(form),
title: "", title: "",
description: "", description: "",
done: nil, done: nil,
mode: create,
} }
// Stylise the buttons // Stylise the buttons
@ -51,49 +60,39 @@ func newModalInput() *modalInput {
SetBackgroundColor(background). SetBackgroundColor(background).
SetBorderPadding(1, 1, 1, 1) SetBorderPadding(1, 1, 1, 1)
modal.AddButton("Create card", func() { modal.AddButton("Save", func() {
if modal.done != nil { 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() { modal.AddButton("Cancel", func() {
if modal.done != nil { 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 return &modal
} }
func (m *modalInput) reset() { func (m *cardModal) updateInputFields(title, description string) {
m.Clear(false) m.Clear(false)
m.addInputFields() m.AddInputField("Title", title, 60, nil, func(text string) {
}
func (m *modalInput) addInputFields() {
m.AddInputField("Title", "", 60, nil, func(text string) {
m.title = text 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 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 m.done = handler
return m return m
} }
func (m *modalInput) Draw(screen tcell.Screen) { func (m *cardModal) Draw(screen tcell.Screen) {
buttonsWidth := 20 buttonsWidth := 20
screenWidth, screenHeight := screen.Size() screenWidth, screenHeight := screen.Size()
width := screenWidth / 3 width := screenWidth / 3

View file

@ -26,8 +26,8 @@ const (
const ( const (
mainPage string = "main" mainPage string = "main"
quitPage string = "quit" quitPage string = "quit"
addPage string = "add" cardModalPage string = "card modal"
deleteCardPage string = "delete card" deleteCardModalPage string = "delete card modal"
) )
type UI struct { type UI struct {
@ -40,7 +40,7 @@ type UI struct {
board board.Board board board.Board
mode boardMode mode boardMode
quitModal *tview.Modal quitModal *tview.Modal
addModal *modalInput cardModal *cardModal
deleteCardModal *tview.Modal deleteCardModal *tview.Modal
statusSelection statusSelection statusSelection statusSelection
} }
@ -57,7 +57,7 @@ func NewUI(path string) (UI, error) {
pages: tview.NewPages(), pages: tview.NewPages(),
flex: tview.NewFlex(), flex: tview.NewFlex(),
quitModal: tview.NewModal(), quitModal: tview.NewModal(),
addModal: newModalInput(), cardModal: newCardModal(),
focusedColumn: 0, focusedColumn: 0,
columns: nil, columns: nil,
board: kanban, board: kanban,
@ -95,8 +95,10 @@ func (u *UI) init() error {
u.shiftColumnFocus(next) u.shiftColumnFocus(next)
case letter == 'c': case letter == 'c':
if u.mode == normal { if u.mode == normal {
u.pages.ShowPage(addPage) u.cardModal.mode = create
u.SetFocus(u.addModal) u.cardModal.updateInputFields("", "")
u.pages.ShowPage(cardModalPage)
u.SetFocus(u.cardModal)
} }
case letter == 'm': case letter == 'm':
if u.mode == normal { if u.mode == normal {
@ -105,9 +107,19 @@ func (u *UI) init() error {
u.statusSelection.currentStatusID = u.columns[u.focusedColumn].statusID u.statusSelection.currentStatusID = u.columns[u.focusedColumn].statusID
u.mode = selection 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: case key == tcell.KeyCtrlD:
if u.mode == normal { if u.mode == normal {
u.pages.ShowPage(deleteCardPage) u.pages.ShowPage(deleteCardModalPage)
u.SetFocus(u.deleteCardModal) u.SetFocus(u.deleteCardModal)
} }
case key == tcell.KeyCtrlQ: case key == tcell.KeyCtrlQ:
@ -139,11 +151,11 @@ func (u *UI) init() error {
u.initQuitModal() u.initQuitModal()
u.pages.AddPage(quitPage, u.quitModal, false, false) u.pages.AddPage(quitPage, u.quitModal, false, false)
u.initAddInputModal() u.initCardModal()
u.pages.AddPage(addPage, u.addModal, false, false) u.pages.AddPage(cardModalPage, u.cardModal, false, false)
u.initDeleteCardModal() u.initDeleteCardModal()
u.pages.AddPage(deleteCardPage, u.deleteCardModal, false, false) u.pages.AddPage(deleteCardModalPage, u.deleteCardModal, false, false)
u.SetRoot(u.pages, true) u.SetRoot(u.pages, true)
@ -154,18 +166,25 @@ func (u *UI) init() error {
return nil return nil
} }
// initAddInputModal initialises the add input modal. // initCardModal initialises the card modal.
func (u *UI) initAddInputModal() { func (u *UI) initCardModal() {
doneFunc := func(text, description string, success bool) { doneFunc := func(title, description string, success bool, mode cardModalMode) {
if success { 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.setColumnFocus()
} }
u.addModal.SetDoneFunc(doneFunc) u.cardModal.setDoneFunc(doneFunc)
} }
// initDeleteCardModal initialises the modal for deleting cards. // initDeleteCardModal initialises the modal for deleting cards.
@ -176,7 +195,7 @@ func (u *UI) initDeleteCardModal() {
_ = u.refresh(true) _ = u.refresh(true)
} }
u.pages.HidePage(deleteCardPage) u.pages.HidePage(deleteCardModalPage)
u.setColumnFocus() u.setColumnFocus()
} }
@ -185,20 +204,6 @@ func (u *UI) initDeleteCardModal() {
SetDoneFunc(doneFunc) 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. // initQuitModal initialises the quit modal.
func (u *UI) initQuitModal() { func (u *UI) initQuitModal() {
doneFunc := func(_ int, buttonLabel string) { doneFunc := func(_ int, buttonLabel string) {
@ -217,10 +222,10 @@ func (u *UI) initQuitModal() {
} }
// newCard creates and saves a new card to the database. // 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{ args := board.CardArgs{
NewTitle: title, NewTitle: title,
NewDescription: content, NewDescription: description,
} }
if _, err := u.board.CreateCard(args); err != nil { if _, err := u.board.CreateCard(args); err != nil {
@ -232,6 +237,40 @@ func (u *UI) newCard(title, content string) error {
return nil 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 { func (u *UI) initColumns() error {
u.flex.Clear() u.flex.Clear()
@ -317,6 +356,7 @@ func (u *UI) shutdown() {
u.Stop() u.Stop()
} }
// boardMode returns the current board mode.
func (u *UI) boardMode() boardMode { func (u *UI) boardMode() boardMode {
return u.mode return u.mode
} }