pelican/internal/ui/ui.go

292 lines
6.4 KiB
Go
Raw Normal View History

2023-05-06 12:49:40 +01:00
package ui
import (
"fmt"
"codeflow.dananglin.me.uk/apollo/pelican/internal/board"
"github.com/rivo/tview"
)
type (
boardMovement int
boardMode int
)
const (
next boardMovement = iota
previous
)
2023-05-06 12:49:40 +01:00
const (
normal boardMode = iota
selection
2023-05-06 12:49:40 +01:00
)
const (
mainPage string = "main"
quitPage string = "quit"
cardFormPage string = "card form"
deleteCardModalPage string = "delete card modal"
viewPage string = "view"
2023-05-06 12:49:40 +01:00
)
type App struct {
2023-05-06 12:49:40 +01:00
*tview.Application
columns []*column
baseFlex *tview.Flex
columnFlex *tview.Flex
2023-05-06 12:49:40 +01:00
pages *tview.Pages
focusedColumn int
board board.Board
mode boardMode
2023-05-06 12:49:40 +01:00
quitModal *tview.Modal
cardForm *cardForm
2023-05-06 12:49:40 +01:00
deleteCardModal *tview.Modal
statusSelection statusSelection
view *cardView
statusbar *statusbar
2023-05-06 12:49:40 +01:00
}
// NewApp returns a new App value.
func NewApp(path string) (App, error) {
kanban, err := board.Open(path)
if err != nil {
return App{}, fmt.Errorf("unable to open the project's board; %w", err)
}
app := App{
2023-05-06 12:49:40 +01:00
Application: tview.NewApplication(),
pages: tview.NewPages(),
baseFlex: tview.NewFlex(),
columnFlex: tview.NewFlex(),
2023-05-06 12:49:40 +01:00
quitModal: tview.NewModal(),
cardForm: newCardForm(),
2023-05-06 12:49:40 +01:00
focusedColumn: 0,
columns: nil,
board: kanban,
2023-05-06 12:49:40 +01:00
deleteCardModal: tview.NewModal(),
mode: normal,
statusSelection: statusSelection{0, 0, 0},
view: newCardView(),
statusbar: newStatusbar(),
2023-05-06 12:49:40 +01:00
}
return app, nil
2023-05-06 12:49:40 +01:00
}
// init initialises the UI.
func (a *App) Init() error {
if err := a.initColumns(); err != nil {
return fmt.Errorf("error initialising the status columns; %w", err)
2023-05-06 12:49:40 +01:00
}
a.columnFlex.SetInputCapture(a.inputCapture())
a.initStatusbar()
a.baseFlex.SetDirection(tview.FlexRow)
a.baseFlex.AddItem(a.columnFlex, 0, 1, true)
a.baseFlex.AddItem(a.statusbar, 2, 1, false)
a.pages.AddPage(mainPage, a.baseFlex, true, true)
a.initQuitModal()
a.pages.AddPage(quitPage, a.quitModal, false, false)
a.initCardForm()
a.pages.AddPage(cardFormPage, a.cardForm, false, false)
a.initDeleteCardModal()
a.pages.AddPage(deleteCardModalPage, a.deleteCardModal, false, false)
a.initView()
a.pages.AddPage(viewPage, a.view, false, false)
a.SetRoot(a.pages, true)
a.refresh(false)
return nil
}
// saveCard saves a new card to the database.
func (a *App) saveCard(title, description string) {
2023-05-06 12:49:40 +01:00
args := board.CardArgs{
NewTitle: title,
NewDescription: description,
2023-05-06 12:49:40 +01:00
}
_, err := a.board.CreateCard(args)
if err != nil {
a.statusbar.displayMessage(errorLevel, fmt.Sprintf("Failed to create card: %v.", err))
return
2023-05-06 12:49:40 +01:00
}
a.statusbar.displayMessage(infoLevel, "Card created successfully.")
a.refresh(false)
}
// editFocusedCard saves an edited card to the database.
func (a *App) editFocusedCard(title, description string) {
cardID := a.focusedCardID()
args := board.UpdateCardArgs{
CardID: cardID,
CardArgs: board.CardArgs{
NewTitle: title,
NewDescription: description,
},
}
if err := a.board.UpdateCard(args); err != nil {
a.statusbar.displayMessage(errorLevel, fmt.Sprintf("Failed to edit card: %v.", err))
return
}
a.statusbar.displayMessage(infoLevel, "Card edited successfully.")
a.refresh(true)
}
// getFocusedCard retrieves the details of the card in focus.
func (a *App) getFocusedCard() (board.Card, bool) {
cardID := a.focusedCardID()
card, err := a.board.Card(cardID)
if err != nil {
a.statusbar.displayMessage(
errorLevel,
fmt.Sprintf("Failed to retrieve the card with card ID %d, %v.", cardID, err),
)
return card, false
}
return card, true
}
// deleteFocusedCard deletes the card in focus from the board.
func (a *App) deleteFocusedCard() {
args := board.DeleteCardArgs{
CardID: a.focusedCardID(),
StatusID: a.focusedStatusID(),
}
if err := a.board.DeleteCard(args); err != nil {
a.statusbar.displayMessage(errorLevel, fmt.Sprintf("Failed to delete card: %v.", err))
return
}
a.statusbar.displayMessage(infoLevel, "Card deleted successfully.")
}
// updateAllColumns ensures that all columns are up-to-date.
func (a *App) updateAllColumns() error {
for i := range a.columns {
if err := a.updateColumn(i); err != nil {
return err
}
}
return nil
}
// updateColumn ensures that the column is up-to-date.
func (a *App) updateColumn(index int) error {
if err := a.columns[index].update(a.board); err != nil {
return fmt.Errorf("unable to update column; %w", err)
}
return nil
}
// shiftColumnFocus shifts the focus to the column to the left or right depending
// on the movement set by the user.
func (a *App) shiftColumnFocus(movement boardMovement) {
switch movement {
case next:
if a.focusedColumn == len(a.columns)-1 {
a.focusedColumn = 0
} else {
a.focusedColumn++
}
case previous:
if a.focusedColumn == 0 {
a.focusedColumn = len(a.columns) - 1
} else {
a.focusedColumn--
}
}
a.setColumnFocus()
}
// setColumnFocus sets the focus to the column primitive as specified by focusedColumn.
func (a *App) setColumnFocus() {
a.SetFocus(a.columns[a.focusedColumn])
}
// refresh refreshes the UI.
func (a *App) refresh(updateFocusedColumnOnly bool) {
if updateFocusedColumnOnly {
if err := a.updateColumn(a.focusedColumn); err != nil {
a.statusbar.displayMessage(
errorLevel,
fmt.Sprintf("Failed to update status column %q: %v", a.focusedStatusName(), err),
)
return
}
} else {
if err := a.updateAllColumns(); err != nil {
a.statusbar.displayMessage(
errorLevel,
fmt.Sprintf("Failed to update status columns: %v", err),
)
return
}
}
a.setColumnFocus()
}
// shutdown shuts down the application.
func (a *App) shutdown() {
a.closeBoard()
a.Stop()
2023-05-06 12:49:40 +01:00
}
// boardMode returns the current board mode.
func (a *App) boardMode() boardMode {
return a.mode
}
// focusedCardID returns the ID of the card in focus.
func (a *App) focusedCardID() int {
focusedCard := a.columns[a.focusedColumn].focusedCard
id := a.columns[a.focusedColumn].cards[focusedCard].id
return id
}
// focusedStatusID returns the ID of the status (column) in focus.
func (a *App) focusedStatusID() int {
return a.columns[a.focusedColumn].statusID
}
// focusedStatusName returns the name of the status (column) in focus.
func (a *App) focusedStatusName() string {
return a.columns[a.focusedColumn].statusName
}
// closeBoard closes the board.
func (a *App) closeBoard() {
_ = a.board.Close()
}