pelican/internal/ui/ui.go

266 lines
5.6 KiB
Go
Raw Normal View History

2023-05-06 12:49:40 +01:00
package ui
import (
"fmt"
"strconv"
"codeflow.dananglin.me.uk/apollo/pelican/internal/board"
"github.com/gdamore/tcell/v2"
"github.com/rivo/tview"
)
const (
shiftLeft int = iota
shiftRight
)
const (
mainPageName string = "main"
quitPageName string = "quit"
addPageName string = "add"
movePageName string = "move"
deleteCardPageName string = "delete card"
)
type UI struct {
*tview.Application
columns []column
flex *tview.Flex
pages *tview.Pages
focusedColumn int
board board.Board
quitModal *tview.Modal
addModal *modalInput
move *tview.Flex
deleteCardModal *tview.Modal
}
// NewUI returns a new UI value.
func NewUI() UI {
ui := UI{
Application: tview.NewApplication(),
pages: tview.NewPages(),
flex: tview.NewFlex(),
quitModal: tview.NewModal(),
addModal: NewModalInput(),
focusedColumn: 0,
columns: nil,
move: nil,
board: board.Board{},
deleteCardModal: tview.NewModal(),
}
ui.init()
return ui
}
// closeBoard closes the board.
func (u *UI) closeBoard() {
_ = u.board.Close()
}
// deleteCard deletes a card from the board.
func (u *UI) deleteCard() {
currentItem := u.columns[u.focusedColumn].cards.GetCurrentItem()
_, cardIDText := u.columns[u.focusedColumn].cards.GetItemText(currentItem)
cardID, _ := strconv.Atoi(cardIDText)
statusID := u.columns[u.focusedColumn].statusID
args := board.DeleteCardArgs{
CardID: cardID,
StatusID: statusID,
}
_ = u.board.DeleteCard(args)
}
// init initialises the UI.
func (u *UI) init() {
u.pages.AddPage(mainPageName, u.flex, true, true)
u.initQuitModal()
u.pages.AddPage(quitPageName, u.quitModal, false, false)
u.initAddInputModal()
u.pages.AddPage(addPageName, u.addModal, false, false)
u.initDeleteCardModal()
u.pages.AddPage(deleteCardPageName, u.deleteCardModal, false, false)
u.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey {
switch event.Rune() {
case 'o':
if u.flex.HasFocus() && len(u.columns) == 0 {
_ = u.openBoard("")
}
}
return event
})
u.SetRoot(u.pages, true)
}
// initAddInputModal initialises the add input modal.
func (u *UI) initAddInputModal() {
doneFunc := func(text string, success bool) {
if success {
_ = u.newCard(text, "")
}
u.pages.HidePage(addPageName)
u.setColumnFocus()
}
u.addModal.SetDoneFunc(doneFunc)
}
// initDeleteCardModal initialises the modal for deleting cards.
func (u *UI) initDeleteCardModal() {
doneFunc := func(_ int, buttonLabel string) {
if buttonLabel == "Confirm" {
u.deleteCard()
_ = u.refresh()
}
u.pages.HidePage(deleteCardPageName)
u.setColumnFocus()
}
u.deleteCardModal.SetText("Do you want to delete this card?").
AddButtons([]string{"Confirm", "Cancel"}).
SetDoneFunc(doneFunc)
}
// initQuitModal initialises the quit modal.
func (u *UI) initQuitModal() {
doneFunc := func(_ int, buttonLabel string) {
switch buttonLabel {
case "Quit":
u.shutdown()
default:
u.pages.HidePage(quitPageName)
u.setColumnFocus()
}
}
u.quitModal.SetText("Do you want to quit the application?").
AddButtons([]string{"Quit", "Cancel"}).
SetDoneFunc(doneFunc)
}
// newCard creates and saves a new card to the database.
func (u *UI) newCard(title, content string) error {
args := board.CardArgs{
NewTitle: title,
NewContent: content,
}
if _, err := u.board.CreateCard(args); err != nil {
return fmt.Errorf("unable to create card, %w", err)
}
_ = u.refresh()
return nil
}
// openBoard opens the kanban board.
func (u *UI) openBoard(path string) error {
b, err := board.Open(path)
if err != nil {
return fmt.Errorf("unable to load board, %w", err)
}
u.board = b
if err = u.refresh(); err != nil {
return fmt.Errorf("error refreshing the board, %w", err)
}
return nil
}
// refresh refreshes the UI.
func (u *UI) refresh() error {
statusList, err := u.board.StatusList()
if err != nil {
return fmt.Errorf("unable to get the status list, %w", err)
}
u.updateColumns(statusList)
u.updateMovePage(statusList)
u.setColumnFocus()
return nil
}
// shutdown shuts down the application.
func (u *UI) shutdown() {
u.closeBoard()
u.Stop()
}
func (u *UI) updateMovePage(statusList []board.Status) {
if u.pages.HasPage(movePageName) {
u.pages.RemovePage(movePageName)
}
move := tview.NewFlex()
statusSelection := tview.NewList()
statusSelection.SetBorder(true)
statusSelection.ShowSecondaryText(false)
statusSelection.SetHighlightFullLine(true)
statusSelection.SetSelectedFocusOnly(true)
statusSelection.SetWrapAround(false)
doneFunc := func() {
u.pages.HidePage(movePageName)
u.setColumnFocus()
}
statusSelection.SetDoneFunc(doneFunc)
selectedFunc := func(_ int, _, secondary string, _ rune) {
currentStatusID := u.columns[u.focusedColumn].statusID
nextStatusID, err := strconv.Atoi(secondary)
if err != nil {
nextStatusID = 0
}
currentItem := u.columns[u.focusedColumn].cards.GetCurrentItem()
_, cardIDText := u.columns[u.focusedColumn].cards.GetItemText(currentItem)
cardID, _ := strconv.Atoi(cardIDText)
args := board.MoveToStatusArgs{
CardID: cardID,
CurrentStatusID: currentStatusID,
NextStatusID: nextStatusID,
}
_ = u.board.MoveToStatus(args)
u.pages.HidePage(movePageName)
_ = u.refresh()
}
statusSelection.SetSelectedFunc(selectedFunc)
for _, status := range statusList {
id := strconv.Itoa(status.ID)
statusSelection.AddItem(fmt.Sprintf("\u25C9 %s", status.Name), id, 0, nil)
}
move.AddItem(statusSelection, 0, 1, true)
u.move = move
u.pages.AddPage(movePageName, move, false, false)
}