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
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'

View file

@ -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

View file

@ -26,8 +26,8 @@ const (
const (
mainPage string = "main"
quitPage string = "quit"
addPage string = "add"
deleteCardPage string = "delete card"
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,
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
}