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
This commit is contained in:
parent
f956b7da59
commit
d167039176
3 changed files with 107 additions and 58 deletions
|
@ -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'
|
||||
|
|
|
@ -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
|
|
@ -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
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue