2023-05-06 12:49:40 +01:00
|
|
|
package ui
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"strconv"
|
|
|
|
|
|
|
|
"codeflow.dananglin.me.uk/apollo/pelican/internal/board"
|
|
|
|
"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.
|
2023-12-12 12:47:58 +00:00
|
|
|
func NewUI(path string) (UI, error) {
|
|
|
|
b, err := board.Open(path)
|
|
|
|
if err != nil {
|
|
|
|
return UI{}, fmt.Errorf("unable to open the project's board; %w", err)
|
|
|
|
}
|
|
|
|
|
2023-05-06 12:49:40 +01:00
|
|
|
ui := UI{
|
|
|
|
Application: tview.NewApplication(),
|
|
|
|
pages: tview.NewPages(),
|
|
|
|
flex: tview.NewFlex(),
|
|
|
|
quitModal: tview.NewModal(),
|
|
|
|
addModal: NewModalInput(),
|
|
|
|
focusedColumn: 0,
|
|
|
|
columns: nil,
|
|
|
|
move: nil,
|
2023-12-12 12:47:58 +00:00
|
|
|
board: b,
|
2023-05-06 12:49:40 +01:00
|
|
|
deleteCardModal: tview.NewModal(),
|
|
|
|
}
|
|
|
|
|
2023-12-12 12:47:58 +00:00
|
|
|
if err := ui.init(); err != nil {
|
|
|
|
return UI{}, fmt.Errorf("received an error after running the initialisation; %w", err)
|
|
|
|
}
|
2023-05-06 12:49:40 +01:00
|
|
|
|
2023-12-12 12:47:58 +00:00
|
|
|
return ui, nil
|
2023-05-06 12:49:40 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// 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.
|
2023-12-12 12:47:58 +00:00
|
|
|
func (u *UI) init() error {
|
2023-05-06 12:49:40 +01:00
|
|
|
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)
|
|
|
|
|
2023-12-12 12:47:58 +00:00
|
|
|
u.SetRoot(u.pages, true)
|
2023-05-06 12:49:40 +01:00
|
|
|
|
2023-12-12 12:47:58 +00:00
|
|
|
if err := u.refresh(); err != nil {
|
|
|
|
return fmt.Errorf("error refreshing the board, %w", err)
|
|
|
|
}
|
2023-05-06 12:49:40 +01:00
|
|
|
|
2023-12-12 12:47:58 +00:00
|
|
|
return nil
|
2023-05-06 12:49:40 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// 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
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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)
|
|
|
|
}
|