feat(ui): add support for editing cards #15
3 changed files with 107 additions and 58 deletions
|
@ -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'
|
||||||
|
|
|
@ -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
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue