feat: New primitive for columns #10
4 changed files with 306 additions and 155 deletions
|
@ -29,7 +29,7 @@ You can visit the https://magefile.org[website] for instructions on how to insta
|
||||||
|
|
||||||
=== Install with Mage
|
=== Install with Mage
|
||||||
|
|
||||||
TBC
|
_TBC_
|
||||||
|
|
||||||
=== Install with Go
|
=== Install with Go
|
||||||
|
|
||||||
|
@ -47,7 +47,7 @@ go install ./cmd/pelican
|
||||||
To create a new Kanban project with Pelican, simply run the following command:
|
To create a new Kanban project with Pelican, simply run the following command:
|
||||||
[source,console]
|
[source,console]
|
||||||
----
|
----
|
||||||
pelican ./project.pelican
|
pelican project.pelican
|
||||||
----
|
----
|
||||||
|
|
||||||
This will create a new BoltDB database file called `project.pelican` in your current directory
|
This will create a new BoltDB database file called `project.pelican` in your current directory
|
||||||
|
|
|
@ -2,119 +2,168 @@ package ui
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"strconv"
|
|
||||||
|
|
||||||
"codeflow.dananglin.me.uk/apollo/pelican/internal/board"
|
"codeflow.dananglin.me.uk/apollo/pelican/internal/board"
|
||||||
"github.com/gdamore/tcell/v2"
|
"github.com/gdamore/tcell/v2"
|
||||||
"github.com/rivo/tview"
|
"github.com/rivo/tview"
|
||||||
)
|
)
|
||||||
|
|
||||||
type column struct {
|
type card struct {
|
||||||
statusID int
|
id int
|
||||||
cards *tview.List
|
wrappedTitle []string
|
||||||
|
created string
|
||||||
|
height int
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *UI) newColumn(status board.Status) (column, error) {
|
type column struct {
|
||||||
cardList := tview.NewList()
|
*tview.Box
|
||||||
|
|
||||||
cardList.SetBorder(true)
|
statusID int
|
||||||
cardList.ShowSecondaryText(false)
|
cards []card
|
||||||
cardList.SetTitle(" " + status.Name + " ")
|
focusedCard int
|
||||||
cardList.SetHighlightFullLine(true)
|
}
|
||||||
cardList.SetSelectedFocusOnly(true)
|
|
||||||
cardList.SetWrapAround(false)
|
|
||||||
|
|
||||||
cardList.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey {
|
func newColumn(statusID int, statusName string) *column {
|
||||||
switch event.Rune() {
|
column := column{
|
||||||
case 'a':
|
Box: tview.NewBox(),
|
||||||
u.pages.ShowPage(addPageName)
|
statusID: statusID,
|
||||||
u.SetFocus(u.addModal)
|
cards: nil,
|
||||||
case 'h':
|
focusedCard: 0,
|
||||||
u.shiftColumnFocus(shiftLeft)
|
}
|
||||||
case 'l':
|
|
||||||
u.shiftColumnFocus(shiftRight)
|
column.SetBorder(true)
|
||||||
case 'j':
|
column.SetTitle(" " + statusName + " ")
|
||||||
cur := cardList.GetCurrentItem()
|
column.SetBorderColor(tcell.ColorOrangeRed.TrueColor())
|
||||||
if cur == cardList.GetItemCount()-1 {
|
|
||||||
cur = 0
|
return &column
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *column) addCard(cardID int, title, created string) {
|
||||||
|
wrappedTitle := tview.WordWrap(title, 40)
|
||||||
|
metadataHeight := 1
|
||||||
|
height := len(wrappedTitle) + metadataHeight
|
||||||
|
|
||||||
|
card := card{
|
||||||
|
id: cardID,
|
||||||
|
wrappedTitle: wrappedTitle,
|
||||||
|
created: created,
|
||||||
|
height: height,
|
||||||
|
}
|
||||||
|
|
||||||
|
c.cards = append(c.cards, card)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *column) shiftCardFocus(shift int) {
|
||||||
|
numCards := len(c.cards)
|
||||||
|
|
||||||
|
switch shift {
|
||||||
|
case shiftToNext:
|
||||||
|
if c.focusedCard == numCards-1 {
|
||||||
|
c.focusedCard = 0
|
||||||
} else {
|
} else {
|
||||||
cur++
|
c.focusedCard++
|
||||||
}
|
}
|
||||||
cardList.SetCurrentItem(cur)
|
case shiftToPrevious:
|
||||||
case 'k':
|
if c.focusedCard == 0 {
|
||||||
cur := cardList.GetCurrentItem()
|
c.focusedCard = numCards - 1
|
||||||
cur--
|
} else {
|
||||||
cardList.SetCurrentItem(cur)
|
c.focusedCard--
|
||||||
case 'm':
|
|
||||||
u.pages.ShowPage(movePageName)
|
|
||||||
u.SetFocus(u.move)
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
switch event.Key() {
|
func (c *column) clear() {
|
||||||
case tcell.KeyCtrlQ:
|
c.cards = nil
|
||||||
u.pages.ShowPage(quitPageName)
|
c.focusedCard = 0
|
||||||
u.SetFocus(u.quitModal)
|
}
|
||||||
case tcell.KeyCtrlD:
|
|
||||||
u.pages.ShowPage(deleteCardPageName)
|
|
||||||
u.SetFocus(u.deleteCardModal)
|
|
||||||
}
|
|
||||||
|
|
||||||
return event
|
func (c *column) update(kanban board.Board) error {
|
||||||
})
|
c.clear()
|
||||||
|
|
||||||
|
status, err := kanban.Status(c.statusID)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("unable to retrieve details about Status %d from the database; %w", c.statusID, err)
|
||||||
|
}
|
||||||
|
|
||||||
if status.CardIds != nil && len(status.CardIds) > 0 {
|
if status.CardIds != nil && len(status.CardIds) > 0 {
|
||||||
cards, err := u.board.CardList(status.CardIds)
|
cards, err := kanban.CardList(status.CardIds)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return column{}, fmt.Errorf("unable to get the card list. %w", err)
|
return fmt.Errorf("unable to get the list of cards from Status %d; %w", c.statusID, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, c := range cards {
|
for _, card := range cards {
|
||||||
id := strconv.Itoa(c.ID)
|
c.addCard(card.ID, card.Title, card.Created)
|
||||||
cardList.AddItem(fmt.Sprintf("[%s] %s", id, c.Title), id, 0, nil)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
u.flex.AddItem(cardList, 0, 1, false)
|
return nil
|
||||||
|
|
||||||
c := column{
|
|
||||||
statusID: status.ID,
|
|
||||||
cards: cardList,
|
|
||||||
}
|
|
||||||
|
|
||||||
return c, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *UI) setColumnFocus() {
|
// InputHandler returns the handler for this primitive.
|
||||||
u.SetFocus(u.columns[u.focusedColumn].cards)
|
func (c *column) InputHandler() func(event *tcell.EventKey, setFocus func(p tview.Primitive)) {
|
||||||
|
return c.WrapInputHandler(func(event *tcell.EventKey, _ func(p tview.Primitive)) {
|
||||||
|
key, letter := event.Key(), event.Rune()
|
||||||
|
switch {
|
||||||
|
case key == tcell.KeyDown || letter == 'j':
|
||||||
|
c.shiftCardFocus(shiftToNext)
|
||||||
|
case key == tcell.KeyUp || letter == 'k':
|
||||||
|
c.shiftCardFocus(shiftToPrevious)
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *UI) shiftColumnFocus(s int) {
|
func (c *column) Draw(screen tcell.Screen) {
|
||||||
switch s {
|
c.DrawForSubclass(screen, c)
|
||||||
case shiftRight:
|
x, y, width, _ := c.GetInnerRect()
|
||||||
if u.focusedColumn == len(u.columns)-1 {
|
|
||||||
u.focusedColumn = 0
|
xOffset := x + 1
|
||||||
|
cursor := y
|
||||||
|
gap := 1
|
||||||
|
|
||||||
|
for i := 0; i < len(c.cards); i++ {
|
||||||
|
var style tcell.Style
|
||||||
|
var vertLine rune
|
||||||
|
|
||||||
|
if c.HasFocus() && i == c.focusedCard {
|
||||||
|
vertLine = tview.BoxDrawingsHeavyVertical
|
||||||
|
style = tcell.StyleDefault.Foreground(tcell.ColorBlue.TrueColor())
|
||||||
} else {
|
} else {
|
||||||
u.focusedColumn++
|
vertLine = tview.BoxDrawingsLightVertical
|
||||||
}
|
style = tcell.StyleDefault.Foreground(tcell.ColorYellow.TrueColor())
|
||||||
case shiftLeft:
|
|
||||||
if u.focusedColumn == 0 {
|
|
||||||
u.focusedColumn = len(u.columns) - 1
|
|
||||||
} else {
|
|
||||||
u.focusedColumn--
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
u.setColumnFocus()
|
// Draw a vertical line down the left side of each card
|
||||||
}
|
for cy := cursor; cy < cursor+c.cards[i].height; cy++ {
|
||||||
|
screen.SetContent(xOffset, cy, vertLine, nil, style)
|
||||||
func (u *UI) updateColumns(statusList []board.Status) {
|
}
|
||||||
u.flex.Clear()
|
|
||||||
|
// Print the card's title
|
||||||
columns := make([]column, len(statusList))
|
for j := range c.cards[i].wrappedTitle {
|
||||||
|
tview.Print(
|
||||||
for i := range statusList {
|
screen,
|
||||||
columns[i], _ = u.newColumn(statusList[i])
|
c.cards[i].wrappedTitle[j],
|
||||||
}
|
xOffset+2,
|
||||||
|
cursor+j,
|
||||||
u.columns = columns
|
width,
|
||||||
|
tview.AlignLeft|tview.AlignTop,
|
||||||
|
tcell.ColorGreen.TrueColor(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Print the card's metadata
|
||||||
|
cal := "đź“…"
|
||||||
|
cardTextFmt := "#%d %s%s"
|
||||||
|
|
||||||
|
tview.Print(
|
||||||
|
screen,
|
||||||
|
fmt.Sprintf(cardTextFmt, c.cards[i].id, cal, c.cards[i].created),
|
||||||
|
xOffset+2,
|
||||||
|
cursor+len(c.cards[i].wrappedTitle),
|
||||||
|
width,
|
||||||
|
tview.AlignLeft|tview.AlignTop,
|
||||||
|
tcell.ColorGrey.TrueColor(),
|
||||||
|
)
|
||||||
|
|
||||||
|
cursor = cursor + c.cards[i].height + gap
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,26 +5,27 @@ import (
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"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"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
shiftLeft int = iota
|
shiftToNext int = iota
|
||||||
shiftRight
|
shiftToPrevious
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
mainPageName string = "main"
|
mainPage string = "main"
|
||||||
quitPageName string = "quit"
|
quitPage string = "quit"
|
||||||
addPageName string = "add"
|
addPage string = "add"
|
||||||
movePageName string = "move"
|
movePage string = "move"
|
||||||
deleteCardPageName string = "delete card"
|
deleteCardPage string = "delete card"
|
||||||
)
|
)
|
||||||
|
|
||||||
type UI struct {
|
type UI struct {
|
||||||
*tview.Application
|
*tview.Application
|
||||||
|
|
||||||
columns []column
|
columns []*column
|
||||||
flex *tview.Flex
|
flex *tview.Flex
|
||||||
pages *tview.Pages
|
pages *tview.Pages
|
||||||
focusedColumn int
|
focusedColumn int
|
||||||
|
@ -67,39 +68,53 @@ func (u *UI) closeBoard() {
|
||||||
_ = u.board.Close()
|
_ = 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.
|
// init initialises the UI.
|
||||||
func (u *UI) init() error {
|
func (u *UI) init() error {
|
||||||
u.pages.AddPage(mainPageName, u.flex, true, true)
|
err := u.initColumns()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error initialising the status columns; %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
u.flex.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey {
|
||||||
|
key, letter := event.Key(), event.Rune()
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case letter == 'a':
|
||||||
|
u.pages.ShowPage(addPage)
|
||||||
|
u.SetFocus(u.addModal)
|
||||||
|
case letter == 'h' || key == tcell.KeyLeft:
|
||||||
|
u.shiftColumnFocus(shiftToPrevious)
|
||||||
|
case letter == 'l' || key == tcell.KeyRight:
|
||||||
|
u.shiftColumnFocus(shiftToNext)
|
||||||
|
case letter == 'm':
|
||||||
|
u.pages.ShowPage(movePage)
|
||||||
|
u.SetFocus(u.move)
|
||||||
|
case key == tcell.KeyCtrlD:
|
||||||
|
u.pages.ShowPage(deleteCardPage)
|
||||||
|
u.SetFocus(u.deleteCardModal)
|
||||||
|
case key == tcell.KeyCtrlQ:
|
||||||
|
u.pages.ShowPage(quitPage)
|
||||||
|
u.SetFocus(u.quitModal)
|
||||||
|
}
|
||||||
|
|
||||||
|
return event
|
||||||
|
})
|
||||||
|
|
||||||
|
u.pages.AddPage(mainPage, u.flex, true, true)
|
||||||
|
|
||||||
u.initQuitModal()
|
u.initQuitModal()
|
||||||
u.pages.AddPage(quitPageName, u.quitModal, false, false)
|
u.pages.AddPage(quitPage, u.quitModal, false, false)
|
||||||
|
|
||||||
u.initAddInputModal()
|
u.initAddInputModal()
|
||||||
u.pages.AddPage(addPageName, u.addModal, false, false)
|
u.pages.AddPage(addPage, u.addModal, false, false)
|
||||||
|
|
||||||
u.initDeleteCardModal()
|
u.initDeleteCardModal()
|
||||||
u.pages.AddPage(deleteCardPageName, u.deleteCardModal, false, false)
|
u.pages.AddPage(deleteCardPage, u.deleteCardModal, false, false)
|
||||||
|
|
||||||
u.SetRoot(u.pages, true)
|
u.SetRoot(u.pages, true)
|
||||||
|
|
||||||
if err := u.refresh(); err != nil {
|
if err := u.refresh(false, true); err != nil {
|
||||||
return fmt.Errorf("error refreshing the board, %w", err)
|
return fmt.Errorf("error refreshing the board; %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
@ -112,7 +127,7 @@ func (u *UI) initAddInputModal() {
|
||||||
_ = u.newCard(text, "")
|
_ = u.newCard(text, "")
|
||||||
}
|
}
|
||||||
|
|
||||||
u.pages.HidePage(addPageName)
|
u.pages.HidePage(addPage)
|
||||||
u.setColumnFocus()
|
u.setColumnFocus()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -124,10 +139,10 @@ func (u *UI) initDeleteCardModal() {
|
||||||
doneFunc := func(_ int, buttonLabel string) {
|
doneFunc := func(_ int, buttonLabel string) {
|
||||||
if buttonLabel == "Confirm" {
|
if buttonLabel == "Confirm" {
|
||||||
u.deleteCard()
|
u.deleteCard()
|
||||||
_ = u.refresh()
|
_ = u.refresh(true, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
u.pages.HidePage(deleteCardPageName)
|
u.pages.HidePage(deleteCardPage)
|
||||||
u.setColumnFocus()
|
u.setColumnFocus()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -136,6 +151,20 @@ func (u *UI) initDeleteCardModal() {
|
||||||
SetDoneFunc(doneFunc)
|
SetDoneFunc(doneFunc)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// deleteCard deletes a card from the board.
|
||||||
|
func (u *UI) deleteCard() {
|
||||||
|
statusID := u.columns[u.focusedColumn].statusID
|
||||||
|
focusedCard := u.columns[u.focusedColumn].focusedCard
|
||||||
|
cardID := u.columns[u.focusedColumn].cards[focusedCard].id
|
||||||
|
|
||||||
|
args := board.DeleteCardArgs{
|
||||||
|
CardID: cardID,
|
||||||
|
StatusID: statusID,
|
||||||
|
}
|
||||||
|
|
||||||
|
_ = u.board.DeleteCard(args)
|
||||||
|
}
|
||||||
|
|
||||||
// initQuitModal initialises the quit modal.
|
// initQuitModal initialises the quit modal.
|
||||||
func (u *UI) initQuitModal() {
|
func (u *UI) initQuitModal() {
|
||||||
doneFunc := func(_ int, buttonLabel string) {
|
doneFunc := func(_ int, buttonLabel string) {
|
||||||
|
@ -143,7 +172,7 @@ func (u *UI) initQuitModal() {
|
||||||
case "Quit":
|
case "Quit":
|
||||||
u.shutdown()
|
u.shutdown()
|
||||||
default:
|
default:
|
||||||
u.pages.HidePage(quitPageName)
|
u.pages.HidePage(quitPage)
|
||||||
u.setColumnFocus()
|
u.setColumnFocus()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -164,36 +193,14 @@ func (u *UI) newCard(title, content string) error {
|
||||||
return fmt.Errorf("unable to create card, %w", err)
|
return fmt.Errorf("unable to create card, %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
_ = u.refresh()
|
_ = u.refresh(false, false)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// refresh refreshes the UI.
|
func (u *UI) updateMovePage() error {
|
||||||
func (u *UI) refresh() error {
|
if u.pages.HasPage(movePage) {
|
||||||
statusList, err := u.board.StatusList()
|
u.pages.RemovePage(movePage)
|
||||||
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()
|
move := tview.NewFlex()
|
||||||
|
@ -206,7 +213,7 @@ func (u *UI) updateMovePage(statusList []board.Status) {
|
||||||
statusSelection.SetWrapAround(false)
|
statusSelection.SetWrapAround(false)
|
||||||
|
|
||||||
doneFunc := func() {
|
doneFunc := func() {
|
||||||
u.pages.HidePage(movePageName)
|
u.pages.HidePage(movePage)
|
||||||
u.setColumnFocus()
|
u.setColumnFocus()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -220,9 +227,8 @@ func (u *UI) updateMovePage(statusList []board.Status) {
|
||||||
nextStatusID = 0
|
nextStatusID = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
currentItem := u.columns[u.focusedColumn].cards.GetCurrentItem()
|
focusedCard := u.columns[u.focusedColumn].focusedCard
|
||||||
_, cardIDText := u.columns[u.focusedColumn].cards.GetItemText(currentItem)
|
cardID := u.columns[u.focusedColumn].cards[focusedCard].id
|
||||||
cardID, _ := strconv.Atoi(cardIDText)
|
|
||||||
|
|
||||||
args := board.MoveToStatusArgs{
|
args := board.MoveToStatusArgs{
|
||||||
CardID: cardID,
|
CardID: cardID,
|
||||||
|
@ -231,12 +237,17 @@ func (u *UI) updateMovePage(statusList []board.Status) {
|
||||||
}
|
}
|
||||||
_ = u.board.MoveToStatus(args)
|
_ = u.board.MoveToStatus(args)
|
||||||
|
|
||||||
u.pages.HidePage(movePageName)
|
u.pages.HidePage(movePage)
|
||||||
_ = u.refresh()
|
_ = u.refresh(false, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
statusSelection.SetSelectedFunc(selectedFunc)
|
statusSelection.SetSelectedFunc(selectedFunc)
|
||||||
|
|
||||||
|
statusList, err := u.board.StatusList()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("unable to get the list of statuses; %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
for _, status := range statusList {
|
for _, status := range statusList {
|
||||||
id := strconv.Itoa(status.ID)
|
id := strconv.Itoa(status.ID)
|
||||||
statusSelection.AddItem(fmt.Sprintf("\u25C9 %s", status.Name), id, 0, nil)
|
statusSelection.AddItem(fmt.Sprintf("\u25C9 %s", status.Name), id, 0, nil)
|
||||||
|
@ -246,5 +257,96 @@ func (u *UI) updateMovePage(statusList []board.Status) {
|
||||||
|
|
||||||
u.move = move
|
u.move = move
|
||||||
|
|
||||||
u.pages.AddPage(movePageName, move, false, false)
|
u.pages.AddPage(movePage, move, false, false)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *UI) initColumns() error {
|
||||||
|
u.flex.Clear()
|
||||||
|
|
||||||
|
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.flex.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 nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *UI) updateColumn(index int) error {
|
||||||
|
if err := u.columns[index].update(u.board); err != nil {
|
||||||
|
return fmt.Errorf("unable to update column; %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *UI) shiftColumnFocus(s int) {
|
||||||
|
switch s {
|
||||||
|
case shiftToNext:
|
||||||
|
if u.focusedColumn == len(u.columns)-1 {
|
||||||
|
u.focusedColumn = 0
|
||||||
|
} else {
|
||||||
|
u.focusedColumn++
|
||||||
|
}
|
||||||
|
case shiftToPrevious:
|
||||||
|
if u.focusedColumn == 0 {
|
||||||
|
u.focusedColumn = len(u.columns) - 1
|
||||||
|
} else {
|
||||||
|
u.focusedColumn--
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
u.setColumnFocus()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *UI) setColumnFocus() {
|
||||||
|
u.SetFocus(u.columns[u.focusedColumn])
|
||||||
|
}
|
||||||
|
|
||||||
|
// refresh refreshes the UI.
|
||||||
|
func (u *UI) refresh(updateFocusedColumnOnly, updateMovePage bool) error {
|
||||||
|
if updateFocusedColumnOnly {
|
||||||
|
if err := u.updateColumn(u.focusedColumn); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if err := u.updateAllColumns(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if updateMovePage {
|
||||||
|
_ = u.updateMovePage()
|
||||||
|
}
|
||||||
|
|
||||||
|
u.setColumnFocus()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// shutdown shuts down the application.
|
||||||
|
func (u *UI) shutdown() {
|
||||||
|
u.closeBoard()
|
||||||
|
u.Stop()
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue