refactor: a lil bit of refactoring

Changes:

- Rename the UI type to App.
- Rename NewUI() to NewApp().
- Move init functions to internal/ui/ui.go.
- Call app's initialisation function from main.
- Move the keymapping functionalities to internal/ui/keymappings.go.
- Add documentation for updateColumn() and updateAllColumns().
- Applied gofmt.

Also:

- Build(mage): optionally include -a and/or -v during go build.
This commit is contained in:
Dan Anglin 2024-01-14 13:46:42 +00:00
parent 43095b9932
commit dbb37a1060
Signed by: dananglin
GPG key ID: 0C1D44CFBEE68638
9 changed files with 387 additions and 325 deletions

2
.gitignore vendored
View file

@ -1,3 +1,3 @@
/test/databases/*.db /test/databases/*.db
/pelican /pelican
/kanban.pelican /*.pelican

View file

@ -25,12 +25,16 @@ func main() {
log.Fatalf("ERROR: Unexpected number of command-line arguments; want 1; got %d", len(args)) log.Fatalf("ERROR: Unexpected number of command-line arguments; want 1; got %d", len(args))
} }
pelican, err := ui.NewUI(args[0]) pelican, err := ui.NewApp(args[0])
if err != nil { if err != nil {
log.Fatalf("ERROR: Unable to initialise Pelican; %v", err) log.Fatalf("ERROR: Unable to create pelican; %v", err)
}
if err := pelican.Init(); err != nil {
log.Fatalf("ERROR: Unable to initialise pelican; %v", err)
} }
if err := pelican.Run(); err != nil { if err := pelican.Run(); err != nil {
log.Fatalf("Error: an error occurred while running pelican, %s", err) log.Fatalf("ERROR: an error occurred while running pelican, %s", err)
} }
} }

View file

@ -12,10 +12,10 @@ func (e CardNotExistError) Error() string {
// Card represents a card on a Kanban board. // Card represents a card on a Kanban board.
type Card struct { type Card struct {
ID int ID int
Title string Title string
Description string Description string
Created string Created string
} }
// UpdateId updates the ID of the Card value. // UpdateId updates the ID of the Card value.

View file

@ -58,7 +58,7 @@ func testCreateCard(kanban board.Board, title, content string, expectedCardID, e
t.Log("When the card is created and saved to the database.") t.Log("When the card is created and saved to the database.")
args := board.CardArgs{ args := board.CardArgs{
NewTitle: title, NewTitle: title,
NewDescription: content, NewDescription: content,
} }
@ -123,7 +123,7 @@ func testUpdateCard(kanban board.Board, cardID int, newTitle, newContent, timest
args := board.UpdateCardArgs{ args := board.UpdateCardArgs{
CardID: cardID, CardID: cardID,
CardArgs: board.CardArgs{ CardArgs: board.CardArgs{
NewTitle: newTitle, NewTitle: newTitle,
NewDescription: newContent, NewDescription: newContent,
}, },
} }
@ -138,10 +138,10 @@ func testUpdateCard(kanban board.Board, cardID int, newTitle, newContent, timest
} }
want := board.Card{ want := board.Card{
ID: cardID, ID: cardID,
Title: newTitle, Title: newTitle,
Description: newContent, Description: newContent,
Created: timestamp, Created: timestamp,
} }
if !reflect.DeepEqual(got, want) { if !reflect.DeepEqual(got, want) {
@ -159,7 +159,7 @@ func testUpdateCardContent(kanban board.Board, cardID int, expectedTitle, newCon
args := board.UpdateCardArgs{ args := board.UpdateCardArgs{
CardID: cardID, CardID: cardID,
CardArgs: board.CardArgs{ CardArgs: board.CardArgs{
NewTitle: "", NewTitle: "",
NewDescription: newContent, NewDescription: newContent,
}, },
} }
@ -174,10 +174,10 @@ func testUpdateCardContent(kanban board.Board, cardID int, expectedTitle, newCon
} }
want := board.Card{ want := board.Card{
ID: cardID, ID: cardID,
Title: expectedTitle, Title: expectedTitle,
Description: newContent, Description: newContent,
Created: timestamp, Created: timestamp,
} }
if !reflect.DeepEqual(got, want) { if !reflect.DeepEqual(got, want) {

View file

@ -163,8 +163,8 @@ func TestReadAndWriteCards(t *testing.T) {
}() }()
singleCard := board.Card{ singleCard := board.Card{
ID: -1, ID: -1,
Title: "A test task.", Title: "A test task.",
Description: "This task should be completed.", Description: "This task should be completed.",
} }
@ -173,18 +173,18 @@ func TestReadAndWriteCards(t *testing.T) {
manyCards := []board.Card{ manyCards := []board.Card{
{ {
ID: -1, ID: -1,
Title: "Test card A.", Title: "Test card A.",
Description: "This is test card A.", Description: "This is test card A.",
}, },
{ {
ID: -1, ID: -1,
Title: "Test card B.", Title: "Test card B.",
Description: "This is test card B.", Description: "This is test card B.",
}, },
{ {
ID: -1, ID: -1,
Title: "Test card C.", Title: "Test card C.",
Description: "This is test card C.", Description: "This is test card C.",
}, },
} }
@ -223,8 +223,8 @@ func testReadOneCard(t *testing.T, database *bolt.DB, cardID int) {
} }
want := board.Card{ want := board.Card{
ID: 1, ID: 1,
Title: "A test task.", Title: "A test task.",
Description: "This task should be completed.", Description: "This task should be completed.",
} }
@ -278,18 +278,18 @@ func testReadManyCards(t *testing.T, database *bolt.DB, cardIDs []int) {
want := []board.Card{ want := []board.Card{
{ {
ID: 2, ID: 2,
Title: "Test card A.", Title: "Test card A.",
Description: "This is test card A.", Description: "This is test card A.",
}, },
{ {
ID: 3, ID: 3,
Title: "Test card B.", Title: "Test card B.",
Description: "This is test card B.", Description: "This is test card B.",
}, },
{ {
ID: 4, ID: 4,
Title: "Test card C.", Title: "Test card C.",
Description: "This is test card C.", Description: "This is test card C.",
}, },
} }
@ -326,8 +326,8 @@ func TestDeleteOneCard(t *testing.T) {
// Create one card, get card ID. // Create one card, get card ID.
card := board.Card{ card := board.Card{
ID: -1, ID: -1,
Title: "Test card", Title: "Test card",
Description: "", Description: "",
} }

106
internal/ui/init.go Normal file
View file

@ -0,0 +1,106 @@
package ui
import (
"fmt"
"github.com/gdamore/tcell/v2"
)
// initColumns initialises the columns of the Kanban board.
func (a *App) initColumns() error {
a.columnFlex.Clear()
statusList, err := a.board.StatusList()
if err != nil {
return fmt.Errorf("unable to retrieve the list of statuses; %w", err)
}
columns := make([]*column, len(statusList))
for i := range statusList {
column := newColumn(statusList[i].ID, statusList[i].Name, a.boardMode)
a.columnFlex.AddItem(column, 50, 1, true)
columns[i] = column
}
a.columns = columns
return nil
}
// initCardForm initialises the card form.
func (a *App) initCardForm() {
doneFunc := func(title, description string, success bool, mode cardFormMode) {
if success {
switch mode {
case create:
a.saveCard(title, description)
case edit:
a.editFocusedCard(title, description)
}
}
a.pages.HidePage(cardFormPage)
a.setColumnFocus()
}
a.cardForm.setDoneFunc(doneFunc)
}
// initDeleteCardModal initialises the modal for deleting cards.
func (a *App) initDeleteCardModal() {
doneFunc := func(_ int, buttonLabel string) {
if buttonLabel == "Confirm" {
a.deleteFocusedCard()
a.refresh(true)
}
a.pages.HidePage(deleteCardModalPage)
a.setColumnFocus()
}
a.deleteCardModal.AddButtons([]string{"Confirm", "Cancel"}).SetDoneFunc(doneFunc)
a.deleteCardModal.SetBorder(true).SetBorderColor(tcell.ColorOrangeRed)
a.deleteCardModal.SetBackgroundColor(tcell.ColorBlack.TrueColor())
a.deleteCardModal.SetButtonBackgroundColor(tcell.ColorBlueViolet.TrueColor())
a.deleteCardModal.SetTextColor(tcell.ColorWhite.TrueColor())
}
// initQuitModal initialises the quit modal.
func (a *App) initQuitModal() {
doneFunc := func(_ int, buttonLabel string) {
switch buttonLabel {
case "Quit":
a.shutdown()
default:
a.pages.HidePage(quitPage)
a.setColumnFocus()
}
}
a.quitModal.SetText("Do you want to quit the application?").
AddButtons([]string{"Quit", "Cancel"}).
SetDoneFunc(doneFunc)
}
// initView initialises the view window for displaying the card.
func (a *App) initView() {
doneFunc := func(key tcell.Key) {
if key == tcell.KeyEsc {
a.pages.HidePage(viewPage)
a.view.Clear()
a.setColumnFocus()
}
}
a.view.setDoneFunc(doneFunc)
}
func (a *App) initStatusbar() {
changedFunc := func() {
a.Draw()
}
a.statusbar.SetChangedFunc(changedFunc)
}

131
internal/ui/keymappings.go Normal file
View file

@ -0,0 +1,131 @@
package ui
import (
"fmt"
"github.com/gdamore/tcell/v2"
)
func (a *App) inputCapture() func(event *tcell.EventKey) *tcell.EventKey {
return func(event *tcell.EventKey) *tcell.EventKey {
key, letter := event.Key(), event.Rune()
switch {
case letter == 'h' || key == tcell.KeyLeft:
a.shiftColumnFocus(previous)
case letter == 'l' || key == tcell.KeyRight:
a.shiftColumnFocus(next)
case letter == 'c':
a.create()
case letter == 'm':
a.move()
case letter == 'e':
a.edit()
case key == tcell.KeyCtrlD:
a.delete()
case key == tcell.KeyCtrlQ:
a.quit()
case key == tcell.KeyESC:
a.escape()
case key == tcell.KeyEnter:
a.selected()
}
return event
}
}
func (a *App) create() {
if a.mode == normal {
a.cardForm.mode = create
a.cardForm.updateInputFields("", "")
a.cardForm.frame.SetTitle(" Create Card ")
a.pages.ShowPage(cardFormPage)
a.SetFocus(a.cardForm)
}
}
func (a *App) move() {
if a.mode == normal {
a.statusSelection.cardID = a.focusedCardID()
a.statusSelection.currentStatusID = a.focusedStatusID()
a.mode = selection
}
}
func (a *App) edit() {
if a.mode == normal {
a.cardForm.mode = edit
card, ok := a.getFocusedCard()
if !ok {
return
}
a.cardForm.updateInputFields(card.Title, card.Description)
a.cardForm.frame.SetTitle(" Edit Card ")
a.pages.ShowPage(cardFormPage)
a.SetFocus(a.cardForm)
}
}
func (a *App) delete() {
if a.mode == normal {
card, ok := a.getFocusedCard()
if !ok {
return
}
text := fmt.Sprintf("Do you want to delete '%s'?", card.Title)
a.deleteCardModal.SetText(text)
a.pages.ShowPage(deleteCardModalPage)
a.SetFocus(a.deleteCardModal)
}
}
func (a *App) quit() {
if a.mode == normal {
a.pages.ShowPage(quitPage)
a.SetFocus(a.quitModal)
}
}
func (a *App) selected() {
switch a.mode {
case normal:
card, ok := a.getFocusedCard()
if !ok {
break
}
status := a.focusedStatusName()
a.view.setText(card.ID, card.Title, status, card.Created, card.Description)
a.pages.ShowPage(viewPage)
a.SetFocus(a.view)
case selection:
a.statusSelection.nextStatusID = a.focusedStatusID()
if a.statusSelection.currentStatusID != a.statusSelection.nextStatusID {
if err := a.statusSelection.moveCard(a.board); err != nil {
a.statusbar.displayMessage(
errorLevel,
fmt.Sprintf("Failed to move card: %v", err),
)
} else {
a.statusbar.displayMessage(
infoLevel,
"Card moved successfully.",
)
}
}
a.statusSelection = statusSelection{0, 0, 0}
a.mode = normal
a.refresh(false)
}
}
func (a *App) escape() {
if a.mode != normal {
a.mode = normal
}
}

View file

@ -4,7 +4,6 @@ import (
"fmt" "fmt"
"codeflow.dananglin.me.uk/apollo/pelican/internal/board" "codeflow.dananglin.me.uk/apollo/pelican/internal/board"
"github.com/gdamore/tcell/v2"
"github.com/rivo/tview" "github.com/rivo/tview"
) )
@ -31,7 +30,7 @@ const (
viewPage string = "view" viewPage string = "view"
) )
type UI struct { type App struct {
*tview.Application *tview.Application
columns []*column columns []*column
@ -49,14 +48,14 @@ type UI struct {
statusbar *statusbar statusbar *statusbar
} }
// NewUI returns a new UI value. // NewApp returns a new App value.
func NewUI(path string) (UI, error) { func NewApp(path string) (App, error) {
kanban, err := board.Open(path) kanban, err := board.Open(path)
if err != nil { if err != nil {
return UI{}, fmt.Errorf("unable to open the project's board; %w", err) return App{}, fmt.Errorf("unable to open the project's board; %w", err)
} }
userInterface := UI{ app := App{
Application: tview.NewApplication(), Application: tview.NewApplication(),
pages: tview.NewPages(), pages: tview.NewPages(),
baseFlex: tview.NewFlex(), baseFlex: tview.NewFlex(),
@ -73,248 +72,66 @@ func NewUI(path string) (UI, error) {
statusbar: newStatusbar(), statusbar: newStatusbar(),
} }
if err := userInterface.init(); err != nil { return app, nil
return UI{}, fmt.Errorf("received an error after running the initialisation; %w", err)
}
return userInterface, nil
}
// closeBoard closes the board.
func (u *UI) closeBoard() {
_ = u.board.Close()
} }
// init initialises the UI. // init initialises the UI.
func (u *UI) init() error { func (a *App) Init() error {
if err := u.initColumns(); err != nil { if err := a.initColumns(); err != nil {
return fmt.Errorf("error initialising the status columns; %w", err) return fmt.Errorf("error initialising the status columns; %w", err)
} }
u.columnFlex.SetInputCapture(u.inputCapture()) a.columnFlex.SetInputCapture(a.inputCapture())
u.initStatusbar() a.initStatusbar()
u.baseFlex.SetDirection(tview.FlexRow) a.baseFlex.SetDirection(tview.FlexRow)
u.baseFlex.AddItem(u.columnFlex, 0, 1, true) a.baseFlex.AddItem(a.columnFlex, 0, 1, true)
u.baseFlex.AddItem(u.statusbar, 2, 1, false) a.baseFlex.AddItem(a.statusbar, 2, 1, false)
u.pages.AddPage(mainPage, u.baseFlex, true, true) a.pages.AddPage(mainPage, a.baseFlex, true, true)
u.initQuitModal() a.initQuitModal()
u.pages.AddPage(quitPage, u.quitModal, false, false) a.pages.AddPage(quitPage, a.quitModal, false, false)
u.initCardForm() a.initCardForm()
u.pages.AddPage(cardFormPage, u.cardForm, false, false) a.pages.AddPage(cardFormPage, a.cardForm, false, false)
u.initDeleteCardModal() a.initDeleteCardModal()
u.pages.AddPage(deleteCardModalPage, u.deleteCardModal, false, false) a.pages.AddPage(deleteCardModalPage, a.deleteCardModal, false, false)
u.initView() a.initView()
u.pages.AddPage(viewPage, u.view, false, false) a.pages.AddPage(viewPage, a.view, false, false)
u.SetRoot(u.pages, true) a.SetRoot(a.pages, true)
u.refresh(false) a.refresh(false)
return nil return nil
} }
func (u *UI) inputCapture() func(event *tcell.EventKey) *tcell.EventKey { // saveCard saves a new card to the database.
return func(event *tcell.EventKey) *tcell.EventKey { func (a *App) saveCard(title, description string) {
key, letter := event.Key(), event.Rune()
switch {
case letter == 'h' || key == tcell.KeyLeft:
u.shiftColumnFocus(previous)
case letter == 'l' || key == tcell.KeyRight:
u.shiftColumnFocus(next)
case letter == 'c':
if u.mode == normal {
u.cardForm.mode = create
u.cardForm.updateInputFields("", "")
u.cardForm.frame.SetTitle(" Create Card ")
u.pages.ShowPage(cardFormPage)
u.SetFocus(u.cardForm)
}
case letter == 'm':
if u.mode == normal {
u.statusSelection.cardID = u.focusedCardID()
u.statusSelection.currentStatusID = u.focusedStatusID()
u.mode = selection
}
case letter == 'e':
if u.mode == normal {
u.cardForm.mode = edit
card, ok := u.getFocusedCard()
if !ok {
break
}
u.cardForm.updateInputFields(card.Title, card.Description)
u.cardForm.frame.SetTitle(" Edit Card ")
u.pages.ShowPage(cardFormPage)
u.SetFocus(u.cardForm)
}
case key == tcell.KeyCtrlD:
if u.mode == normal {
card, ok := u.getFocusedCard()
if !ok {
break
}
text := fmt.Sprintf("Do you want to delete '%s'?", card.Title)
u.deleteCardModal.SetText(text)
u.pages.ShowPage(deleteCardModalPage)
u.SetFocus(u.deleteCardModal)
}
case key == tcell.KeyCtrlQ:
if u.mode == normal {
u.pages.ShowPage(quitPage)
u.SetFocus(u.quitModal)
}
case key == tcell.KeyESC:
if u.mode != normal {
u.mode = normal
}
case key == tcell.KeyEnter:
switch u.mode {
case normal:
card, ok := u.getFocusedCard()
if !ok {
break
}
status := u.focusedStatusName()
u.view.setText(card.ID, card.Title, status, card.Created, card.Description)
u.pages.ShowPage(viewPage)
u.SetFocus(u.view)
case selection:
u.statusSelection.nextStatusID = u.focusedStatusID()
if u.statusSelection.currentStatusID != u.statusSelection.nextStatusID {
if err := u.statusSelection.moveCard(u.board); err != nil {
u.statusbar.displayMessage(
errorLevel,
fmt.Sprintf("Failed to move card: %v", err),
)
} else {
u.statusbar.displayMessage(
infoLevel,
"Card moved successfully.",
)
}
}
u.statusSelection = statusSelection{0, 0, 0}
u.mode = normal
u.refresh(false)
}
}
return event
}
}
// initCardForm initialises the card form.
func (u *UI) initCardForm() {
doneFunc := func(title, description string, success bool, mode cardFormMode) {
if success {
switch mode {
case create:
u.saveCard(title, description)
case edit:
u.editFocusedCard(title, description)
}
}
u.pages.HidePage(cardFormPage)
u.setColumnFocus()
}
u.cardForm.setDoneFunc(doneFunc)
}
// initDeleteCardModal initialises the modal for deleting cards.
func (u *UI) initDeleteCardModal() {
doneFunc := func(_ int, buttonLabel string) {
if buttonLabel == "Confirm" {
u.deleteFocusedCard()
u.refresh(true)
}
u.pages.HidePage(deleteCardModalPage)
u.setColumnFocus()
}
u.deleteCardModal.AddButtons([]string{"Confirm", "Cancel"}).SetDoneFunc(doneFunc)
u.deleteCardModal.SetBorder(true).SetBorderColor(tcell.ColorOrangeRed)
u.deleteCardModal.SetBackgroundColor(tcell.ColorBlack.TrueColor())
u.deleteCardModal.SetButtonBackgroundColor(tcell.ColorBlueViolet.TrueColor())
u.deleteCardModal.SetTextColor(tcell.ColorWhite.TrueColor())
}
// initQuitModal initialises the quit modal.
func (u *UI) initQuitModal() {
doneFunc := func(_ int, buttonLabel string) {
switch buttonLabel {
case "Quit":
u.shutdown()
default:
u.pages.HidePage(quitPage)
u.setColumnFocus()
}
}
u.quitModal.SetText("Do you want to quit the application?").
AddButtons([]string{"Quit", "Cancel"}).
SetDoneFunc(doneFunc)
}
// initView initialises the view window for displaying the card.
func (u *UI) initView() {
doneFunc := func(key tcell.Key) {
if key == tcell.KeyEsc {
u.pages.HidePage(viewPage)
u.view.Clear()
u.setColumnFocus()
}
}
u.view.setDoneFunc(doneFunc)
}
func (u *UI) initStatusbar() {
changedFunc := func() {
u.Draw()
}
u.statusbar.SetChangedFunc(changedFunc)
}
// saveCard creates and saves a new card to the database.
func (u *UI) saveCard(title, description string) {
args := board.CardArgs{ args := board.CardArgs{
NewTitle: title, NewTitle: title,
NewDescription: description, NewDescription: description,
} }
_, err := u.board.CreateCard(args) _, err := a.board.CreateCard(args)
if err != nil { if err != nil {
u.statusbar.displayMessage(errorLevel, fmt.Sprintf("Failed to create card: %v.", err)) a.statusbar.displayMessage(errorLevel, fmt.Sprintf("Failed to create card: %v.", err))
return return
} }
u.statusbar.displayMessage(infoLevel, "Card created successfully.") a.statusbar.displayMessage(infoLevel, "Card created successfully.")
u.refresh(false) a.refresh(false)
} }
// editFocusedCard saves and edited card to the database. // editFocusedCard saves an edited card to the database.
func (u *UI) editFocusedCard(title, description string) { func (a *App) editFocusedCard(title, description string) {
cardID := u.focusedCardID() cardID := a.focusedCardID()
args := board.UpdateCardArgs{ args := board.UpdateCardArgs{
CardID: cardID, CardID: cardID,
@ -324,24 +141,24 @@ func (u *UI) editFocusedCard(title, description string) {
}, },
} }
if err := u.board.UpdateCard(args); err != nil { if err := a.board.UpdateCard(args); err != nil {
u.statusbar.displayMessage(errorLevel, fmt.Sprintf("Failed to edit card: %v.", err)) a.statusbar.displayMessage(errorLevel, fmt.Sprintf("Failed to edit card: %v.", err))
return return
} }
u.statusbar.displayMessage(infoLevel, "Card edited successfully.") a.statusbar.displayMessage(infoLevel, "Card edited successfully.")
u.refresh(true) a.refresh(true)
} }
// getFocusedCard retrieves the details of the focused card. // getFocusedCard retrieves the details of the card in focus.
func (u *UI) getFocusedCard() (board.Card, bool) { func (a *App) getFocusedCard() (board.Card, bool) {
cardID := u.focusedCardID() cardID := a.focusedCardID()
card, err := u.board.Card(cardID) card, err := a.board.Card(cardID)
if err != nil { if err != nil {
u.statusbar.displayMessage( a.statusbar.displayMessage(
errorLevel, errorLevel,
fmt.Sprintf("Failed to retrieve the card with card ID %d, %v.", cardID, err), fmt.Sprintf("Failed to retrieve the card with card ID %d, %v.", cardID, err),
) )
@ -352,47 +169,26 @@ func (u *UI) getFocusedCard() (board.Card, bool) {
return card, true return card, true
} }
// deleteFocusedCard deletes the focused card from the board. // deleteFocusedCard deletes the card in focus from the board.
func (u *UI) deleteFocusedCard() { func (a *App) deleteFocusedCard() {
args := board.DeleteCardArgs{ args := board.DeleteCardArgs{
CardID: u.focusedCardID(), CardID: a.focusedCardID(),
StatusID: u.focusedStatusID(), StatusID: a.focusedStatusID(),
} }
if err := u.board.DeleteCard(args); err != nil { if err := a.board.DeleteCard(args); err != nil {
u.statusbar.displayMessage(errorLevel, fmt.Sprintf("Failed to delete card: %v.", err)) a.statusbar.displayMessage(errorLevel, fmt.Sprintf("Failed to delete card: %v.", err))
return return
} }
u.statusbar.displayMessage(infoLevel, "Card deleted successfully.") a.statusbar.displayMessage(infoLevel, "Card deleted successfully.")
} }
// initColumns initialises the columns of the Kanban board. // updateAllColumns ensures that all columns are up-to-date.
func (u *UI) initColumns() error { func (a *App) updateAllColumns() error {
u.columnFlex.Clear() for i := range a.columns {
if err := a.updateColumn(i); err != nil {
statusList, err := u.board.StatusList()
if err != nil {
return fmt.Errorf("unable to retrieve the list of statuses; %w", err)
}
columns := make([]*column, len(statusList))
for i := range statusList {
column := newColumn(statusList[i].ID, statusList[i].Name, u.boardMode)
u.columnFlex.AddItem(column, 50, 1, true)
columns[i] = column
}
u.columns = columns
return nil
}
func (u *UI) updateAllColumns() error {
for i := range u.columns {
if err := u.updateColumn(i); err != nil {
return err return err
} }
} }
@ -400,51 +196,55 @@ func (u *UI) updateAllColumns() error {
return nil return nil
} }
func (u *UI) updateColumn(index int) error { // updateColumn ensures that the column is up-to-date.
if err := u.columns[index].update(u.board); err != nil { 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 fmt.Errorf("unable to update column; %w", err)
} }
return nil return nil
} }
func (u *UI) shiftColumnFocus(movement boardMovement) { // 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 { switch movement {
case next: case next:
if u.focusedColumn == len(u.columns)-1 { if a.focusedColumn == len(a.columns)-1 {
u.focusedColumn = 0 a.focusedColumn = 0
} else { } else {
u.focusedColumn++ a.focusedColumn++
} }
case previous: case previous:
if u.focusedColumn == 0 { if a.focusedColumn == 0 {
u.focusedColumn = len(u.columns) - 1 a.focusedColumn = len(a.columns) - 1
} else { } else {
u.focusedColumn-- a.focusedColumn--
} }
} }
u.setColumnFocus() a.setColumnFocus()
} }
func (u *UI) setColumnFocus() { // setColumnFocus sets the focus to the column primitive as specified by focusedColumn.
u.SetFocus(u.columns[u.focusedColumn]) func (a *App) setColumnFocus() {
a.SetFocus(a.columns[a.focusedColumn])
} }
// refresh refreshes the UI. // refresh refreshes the UI.
func (u *UI) refresh(updateFocusedColumnOnly bool) { func (a *App) refresh(updateFocusedColumnOnly bool) {
if updateFocusedColumnOnly { if updateFocusedColumnOnly {
if err := u.updateColumn(u.focusedColumn); err != nil { if err := a.updateColumn(a.focusedColumn); err != nil {
u.statusbar.displayMessage( a.statusbar.displayMessage(
errorLevel, errorLevel,
fmt.Sprintf("Failed to update status column %q: %v", u.focusedStatusName(), err), fmt.Sprintf("Failed to update status column %q: %v", a.focusedStatusName(), err),
) )
return return
} }
} else { } else {
if err := u.updateAllColumns(); err != nil { if err := a.updateAllColumns(); err != nil {
u.statusbar.displayMessage( a.statusbar.displayMessage(
errorLevel, errorLevel,
fmt.Sprintf("Failed to update status columns: %v", err), fmt.Sprintf("Failed to update status columns: %v", err),
) )
@ -453,34 +253,39 @@ func (u *UI) refresh(updateFocusedColumnOnly bool) {
} }
} }
u.setColumnFocus() a.setColumnFocus()
} }
// shutdown shuts down the application. // shutdown shuts down the application.
func (u *UI) shutdown() { func (a *App) shutdown() {
u.closeBoard() a.closeBoard()
u.Stop() a.Stop()
} }
// boardMode returns the current board mode. // boardMode returns the current board mode.
func (u *UI) boardMode() boardMode { func (a *App) boardMode() boardMode {
return u.mode return a.mode
} }
// focusedCardID returns the ID of the card in focus. // focusedCardID returns the ID of the card in focus.
func (u *UI) focusedCardID() int { func (a *App) focusedCardID() int {
focusedCard := u.columns[u.focusedColumn].focusedCard focusedCard := a.columns[a.focusedColumn].focusedCard
id := u.columns[u.focusedColumn].cards[focusedCard].id id := a.columns[a.focusedColumn].cards[focusedCard].id
return id return id
} }
// focusedStatusID returns the ID of the status (column) in focus. // focusedStatusID returns the ID of the status (column) in focus.
func (u *UI) focusedStatusID() int { func (a *App) focusedStatusID() int {
return u.columns[u.focusedColumn].statusID return a.columns[a.focusedColumn].statusID
} }
// focusedStatusName returns the name of the status (column) in focus. // focusedStatusName returns the name of the status (column) in focus.
func (u *UI) focusedStatusName() string { func (a *App) focusedStatusName() string {
return u.columns[u.focusedColumn].statusName return a.columns[a.focusedColumn].statusName
}
// closeBoard closes the board.
func (a *App) closeBoard() {
_ = a.board.Close()
} }

View file

@ -25,7 +25,7 @@ var Default = Build
// To enable verbose mode set PELICAN_TEST_VERBOSE=1. // To enable verbose mode set PELICAN_TEST_VERBOSE=1.
// To enable coverage mode set PELICAN_TEST_COVER=1. // To enable coverage mode set PELICAN_TEST_COVER=1.
func Test() error { func Test() error {
goTest := sh.RunCmd("go", "test") test := sh.RunCmd("go", "test")
args := []string{"./..."} args := []string{"./..."}
@ -37,7 +37,7 @@ func Test() error {
args = append(args, "-cover") args = append(args, "-cover")
} }
return goTest(args...) return test(args...)
} }
// Lint runs golangci-lint against the code. // Lint runs golangci-lint against the code.
@ -46,11 +46,27 @@ func Lint() error {
} }
// Build build the executable // Build build the executable
// To rebuild packages that are already up-to-date set PELICAN_BUILD_REBUILD_ALL=1
// To enable verbose mode set PELICAN_BUILD_VERBOSE=1
func Build() error { func Build() error {
main := "./cmd/" + binary main := "./cmd/" + binary
flags := ldflags() flags := ldflags()
return sh.Run("go", "build", "-ldflags="+flags, "-a", "-o", binary, main) build := sh.RunCmd("go", "build")
args := []string{"-ldflags=" + flags, "-o", binary}
if os.Getenv("PELICAN_BUILD_REBUILD_ALL") == "1" {
args = append(args, "-a")
}
if os.Getenv("PELICAN_BUILD_VERBOSE") == "1" {
args = append(args, "-v")
}
args = append(args, main)
return build(args...)
} }
func Install() error { func Install() error {