Dan Anglin
fc5fa7b0ca
This PR allows users to specify the path to the database file Pelican now expects the user to specify the path to the project's database file which allows users to open different projects. This is a breaking change because Pelican no longer opens the default path automatically. If no path is set then Pelican stops with an error message.
250 lines
5.4 KiB
Go
250 lines
5.4 KiB
Go
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.
|
|
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)
|
|
}
|
|
|
|
ui := UI{
|
|
Application: tview.NewApplication(),
|
|
pages: tview.NewPages(),
|
|
flex: tview.NewFlex(),
|
|
quitModal: tview.NewModal(),
|
|
addModal: NewModalInput(),
|
|
focusedColumn: 0,
|
|
columns: nil,
|
|
move: nil,
|
|
board: b,
|
|
deleteCardModal: tview.NewModal(),
|
|
}
|
|
|
|
if err := ui.init(); err != nil {
|
|
return UI{}, fmt.Errorf("received an error after running the initialisation; %w", err)
|
|
}
|
|
|
|
return ui, nil
|
|
}
|
|
|
|
// 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() error {
|
|
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.SetRoot(u.pages, true)
|
|
|
|
if err := u.refresh(); err != nil {
|
|
return fmt.Errorf("error refreshing the board, %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// 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)
|
|
}
|