feat(ui): add new board mode 'Status Selection'
Replace the existing 'move' tview Page with a new board mode called 'Status Selection'. This mode is used to move cards between statuses. Resolves apollo/pelican#11
This commit is contained in:
parent
c7bb499f0d
commit
ff676e8dc5
3 changed files with 157 additions and 124 deletions
|
@ -18,22 +18,23 @@ type card struct {
|
||||||
type column struct {
|
type column struct {
|
||||||
*tview.Box
|
*tview.Box
|
||||||
|
|
||||||
statusID int
|
statusID int
|
||||||
cards []card
|
cards []card
|
||||||
focusedCard int
|
focusedCard int
|
||||||
|
getBoardModeFunc func() boardMode
|
||||||
}
|
}
|
||||||
|
|
||||||
func newColumn(statusID int, statusName string) *column {
|
func newColumn(statusID int, statusName string, getBoardModeFunc func() boardMode) *column {
|
||||||
column := column{
|
column := column{
|
||||||
Box: tview.NewBox(),
|
Box: tview.NewBox(),
|
||||||
statusID: statusID,
|
statusID: statusID,
|
||||||
cards: nil,
|
cards: nil,
|
||||||
focusedCard: 0,
|
focusedCard: 0,
|
||||||
|
getBoardModeFunc: getBoardModeFunc,
|
||||||
}
|
}
|
||||||
|
|
||||||
column.SetBorder(true)
|
column.SetBorder(true)
|
||||||
column.SetTitle(" " + statusName + " ")
|
column.SetTitle(" " + statusName + " ")
|
||||||
column.SetBorderColor(tcell.ColorOrangeRed.TrueColor())
|
|
||||||
|
|
||||||
return &column
|
return &column
|
||||||
}
|
}
|
||||||
|
@ -53,17 +54,17 @@ func (c *column) addCard(cardID int, title, created string) {
|
||||||
c.cards = append(c.cards, card)
|
c.cards = append(c.cards, card)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *column) shiftCardFocus(shift int) {
|
func (c *column) shiftCardFocus(movement boardMovement) {
|
||||||
numCards := len(c.cards)
|
numCards := len(c.cards)
|
||||||
|
|
||||||
switch shift {
|
switch movement {
|
||||||
case shiftToNext:
|
case next:
|
||||||
if c.focusedCard == numCards-1 {
|
if c.focusedCard == numCards-1 {
|
||||||
c.focusedCard = 0
|
c.focusedCard = 0
|
||||||
} else {
|
} else {
|
||||||
c.focusedCard++
|
c.focusedCard++
|
||||||
}
|
}
|
||||||
case shiftToPrevious:
|
case previous:
|
||||||
if c.focusedCard == 0 {
|
if c.focusedCard == 0 {
|
||||||
c.focusedCard = numCards - 1
|
c.focusedCard = numCards - 1
|
||||||
} else {
|
} else {
|
||||||
|
@ -105,26 +106,48 @@ func (c *column) InputHandler() func(event *tcell.EventKey, setFocus func(p tvie
|
||||||
key, letter := event.Key(), event.Rune()
|
key, letter := event.Key(), event.Rune()
|
||||||
switch {
|
switch {
|
||||||
case key == tcell.KeyDown || letter == 'j':
|
case key == tcell.KeyDown || letter == 'j':
|
||||||
c.shiftCardFocus(shiftToNext)
|
c.shiftCardFocus(next)
|
||||||
case key == tcell.KeyUp || letter == 'k':
|
case key == tcell.KeyUp || letter == 'k':
|
||||||
c.shiftCardFocus(shiftToPrevious)
|
c.shiftCardFocus(previous)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *column) Draw(screen tcell.Screen) {
|
func (c *column) Draw(screen tcell.Screen) {
|
||||||
|
var mode boardMode
|
||||||
|
|
||||||
|
// Update border before calling c.DrawForSubclass()
|
||||||
|
if c.getBoardModeFunc == nil {
|
||||||
|
mode = normal
|
||||||
|
} else {
|
||||||
|
mode = c.getBoardModeFunc()
|
||||||
|
}
|
||||||
|
|
||||||
|
if mode == selection {
|
||||||
|
if c.HasFocus() {
|
||||||
|
c.SetBorderColor(tcell.ColorTurquoise.TrueColor())
|
||||||
|
} else {
|
||||||
|
c.SetBorderColor(tcell.ColorOrangeRed.TrueColor())
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
c.SetBorderColor(tcell.ColorOrangeRed.TrueColor())
|
||||||
|
}
|
||||||
|
|
||||||
c.DrawForSubclass(screen, c)
|
c.DrawForSubclass(screen, c)
|
||||||
|
|
||||||
x, y, width, _ := c.GetInnerRect()
|
x, y, width, _ := c.GetInnerRect()
|
||||||
|
|
||||||
xOffset := x + 1
|
xOffset := x + 1
|
||||||
cursor := y
|
cursor := y
|
||||||
gap := 1
|
gap := 1
|
||||||
|
|
||||||
for i := 0; i < len(c.cards); i++ {
|
for ind := 0; ind < len(c.cards); ind++ {
|
||||||
var style tcell.Style
|
var (
|
||||||
var vertLine rune
|
style tcell.Style
|
||||||
|
vertLine rune
|
||||||
|
)
|
||||||
|
|
||||||
if c.HasFocus() && i == c.focusedCard {
|
if c.HasFocus() && ind == c.focusedCard && mode == normal {
|
||||||
vertLine = tview.BoxDrawingsHeavyVertical
|
vertLine = tview.BoxDrawingsHeavyVertical
|
||||||
style = tcell.StyleDefault.Foreground(tcell.ColorBlue.TrueColor())
|
style = tcell.StyleDefault.Foreground(tcell.ColorBlue.TrueColor())
|
||||||
} else {
|
} else {
|
||||||
|
@ -133,15 +156,15 @@ func (c *column) Draw(screen tcell.Screen) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Draw a vertical line down the left side of each card
|
// Draw a vertical line down the left side of each card
|
||||||
for cy := cursor; cy < cursor+c.cards[i].height; cy++ {
|
for cy := cursor; cy < cursor+c.cards[ind].height; cy++ {
|
||||||
screen.SetContent(xOffset, cy, vertLine, nil, style)
|
screen.SetContent(xOffset, cy, vertLine, nil, style)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Print the card's title
|
// Print the card's title
|
||||||
for j := range c.cards[i].wrappedTitle {
|
for j := range c.cards[ind].wrappedTitle {
|
||||||
tview.Print(
|
tview.Print(
|
||||||
screen,
|
screen,
|
||||||
c.cards[i].wrappedTitle[j],
|
c.cards[ind].wrappedTitle[j],
|
||||||
xOffset+2,
|
xOffset+2,
|
||||||
cursor+j,
|
cursor+j,
|
||||||
width,
|
width,
|
||||||
|
@ -156,14 +179,34 @@ func (c *column) Draw(screen tcell.Screen) {
|
||||||
|
|
||||||
tview.Print(
|
tview.Print(
|
||||||
screen,
|
screen,
|
||||||
fmt.Sprintf(cardTextFmt, c.cards[i].id, cal, c.cards[i].created),
|
fmt.Sprintf(cardTextFmt, c.cards[ind].id, cal, c.cards[ind].created),
|
||||||
xOffset+2,
|
xOffset+2,
|
||||||
cursor+len(c.cards[i].wrappedTitle),
|
cursor+len(c.cards[ind].wrappedTitle),
|
||||||
width,
|
width,
|
||||||
tview.AlignLeft|tview.AlignTop,
|
tview.AlignLeft|tview.AlignTop,
|
||||||
tcell.ColorGrey.TrueColor(),
|
tcell.ColorGrey.TrueColor(),
|
||||||
)
|
)
|
||||||
|
|
||||||
cursor = cursor + c.cards[i].height + gap
|
cursor = cursor + c.cards[ind].height + gap
|
||||||
|
}
|
||||||
|
|
||||||
|
// draw a 'placeholder card' if in status selection board mode
|
||||||
|
if mode == selection && c.HasFocus() {
|
||||||
|
vertLine := tview.BoxDrawingsHeavyVertical
|
||||||
|
style := tcell.StyleDefault.Foreground(tcell.ColorWhite.TrueColor())
|
||||||
|
|
||||||
|
for cy := cursor; cy < cursor+1; cy++ {
|
||||||
|
screen.SetContent(xOffset, cy, vertLine, nil, style)
|
||||||
|
}
|
||||||
|
|
||||||
|
tview.Print(
|
||||||
|
screen,
|
||||||
|
"Move the card to this column",
|
||||||
|
xOffset+2,
|
||||||
|
cursor,
|
||||||
|
width,
|
||||||
|
tview.AlignLeft|tview.AlignTop,
|
||||||
|
tcell.ColorWhite.TrueColor(),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
20
internal/ui/statusselection.go
Normal file
20
internal/ui/statusselection.go
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
package ui
|
||||||
|
|
||||||
|
import "codeflow.dananglin.me.uk/apollo/pelican/internal/board"
|
||||||
|
|
||||||
|
type statusSelection struct {
|
||||||
|
cardID int
|
||||||
|
currentStatusID int
|
||||||
|
nextStatusID int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s statusSelection) moveCard(kanban board.Board) {
|
||||||
|
moveArgs := board.MoveToStatusArgs{
|
||||||
|
CardID: s.cardID,
|
||||||
|
CurrentStatusID: s.currentStatusID,
|
||||||
|
NextStatusID: s.nextStatusID,
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: grab error for status line.
|
||||||
|
_ = kanban.MoveToStatus(moveArgs)
|
||||||
|
}
|
|
@ -2,23 +2,31 @@ 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 (
|
||||||
|
boardMovement int
|
||||||
|
boardMode int
|
||||||
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
shiftToNext int = iota
|
next boardMovement = iota
|
||||||
shiftToPrevious
|
previous
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
normal boardMode = iota
|
||||||
|
selection
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
mainPage string = "main"
|
mainPage string = "main"
|
||||||
quitPage string = "quit"
|
quitPage string = "quit"
|
||||||
addPage string = "add"
|
addPage string = "add"
|
||||||
movePage string = "move"
|
|
||||||
deleteCardPage string = "delete card"
|
deleteCardPage string = "delete card"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -30,20 +38,21 @@ type UI struct {
|
||||||
pages *tview.Pages
|
pages *tview.Pages
|
||||||
focusedColumn int
|
focusedColumn int
|
||||||
board board.Board
|
board board.Board
|
||||||
|
mode boardMode
|
||||||
quitModal *tview.Modal
|
quitModal *tview.Modal
|
||||||
addModal *modalInput
|
addModal *modalInput
|
||||||
move *tview.Flex
|
|
||||||
deleteCardModal *tview.Modal
|
deleteCardModal *tview.Modal
|
||||||
|
statusSelection statusSelection
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewUI returns a new UI value.
|
// NewUI returns a new UI value.
|
||||||
func NewUI(path string) (UI, error) {
|
func NewUI(path string) (UI, error) {
|
||||||
b, 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 UI{}, fmt.Errorf("unable to open the project's board; %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
ui := UI{
|
userInterface := UI{
|
||||||
Application: tview.NewApplication(),
|
Application: tview.NewApplication(),
|
||||||
pages: tview.NewPages(),
|
pages: tview.NewPages(),
|
||||||
flex: tview.NewFlex(),
|
flex: tview.NewFlex(),
|
||||||
|
@ -51,16 +60,17 @@ func NewUI(path string) (UI, error) {
|
||||||
addModal: NewModalInput(),
|
addModal: NewModalInput(),
|
||||||
focusedColumn: 0,
|
focusedColumn: 0,
|
||||||
columns: nil,
|
columns: nil,
|
||||||
move: nil,
|
board: kanban,
|
||||||
board: b,
|
|
||||||
deleteCardModal: tview.NewModal(),
|
deleteCardModal: tview.NewModal(),
|
||||||
|
mode: normal,
|
||||||
|
statusSelection: statusSelection{0, 0, 0},
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := ui.init(); err != nil {
|
if err := userInterface.init(); err != nil {
|
||||||
return UI{}, fmt.Errorf("received an error after running the initialisation; %w", err)
|
return UI{}, fmt.Errorf("received an error after running the initialisation; %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return ui, nil
|
return userInterface, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// closeBoard closes the board.
|
// closeBoard closes the board.
|
||||||
|
@ -79,22 +89,46 @@ func (u *UI) init() error {
|
||||||
key, letter := event.Key(), event.Rune()
|
key, letter := event.Key(), event.Rune()
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
case letter == 'a':
|
|
||||||
u.pages.ShowPage(addPage)
|
|
||||||
u.SetFocus(u.addModal)
|
|
||||||
case letter == 'h' || key == tcell.KeyLeft:
|
case letter == 'h' || key == tcell.KeyLeft:
|
||||||
u.shiftColumnFocus(shiftToPrevious)
|
u.shiftColumnFocus(previous)
|
||||||
case letter == 'l' || key == tcell.KeyRight:
|
case letter == 'l' || key == tcell.KeyRight:
|
||||||
u.shiftColumnFocus(shiftToNext)
|
u.shiftColumnFocus(next)
|
||||||
|
case letter == 'a':
|
||||||
|
if u.mode == normal {
|
||||||
|
u.pages.ShowPage(addPage)
|
||||||
|
u.SetFocus(u.addModal)
|
||||||
|
}
|
||||||
case letter == 'm':
|
case letter == 'm':
|
||||||
u.pages.ShowPage(movePage)
|
if u.mode == normal {
|
||||||
u.SetFocus(u.move)
|
focusedCard := u.columns[u.focusedColumn].focusedCard
|
||||||
|
u.statusSelection.cardID = u.columns[u.focusedColumn].cards[focusedCard].id
|
||||||
|
u.statusSelection.currentStatusID = u.columns[u.focusedColumn].statusID
|
||||||
|
u.mode = selection
|
||||||
|
}
|
||||||
case key == tcell.KeyCtrlD:
|
case key == tcell.KeyCtrlD:
|
||||||
u.pages.ShowPage(deleteCardPage)
|
if u.mode == normal {
|
||||||
u.SetFocus(u.deleteCardModal)
|
u.pages.ShowPage(deleteCardPage)
|
||||||
|
u.SetFocus(u.deleteCardModal)
|
||||||
|
}
|
||||||
case key == tcell.KeyCtrlQ:
|
case key == tcell.KeyCtrlQ:
|
||||||
u.pages.ShowPage(quitPage)
|
if u.mode == normal {
|
||||||
u.SetFocus(u.quitModal)
|
u.pages.ShowPage(quitPage)
|
||||||
|
u.SetFocus(u.quitModal)
|
||||||
|
}
|
||||||
|
case key == tcell.KeyESC:
|
||||||
|
if u.mode != normal {
|
||||||
|
u.mode = normal
|
||||||
|
}
|
||||||
|
case key == tcell.KeyEnter:
|
||||||
|
if u.mode == selection {
|
||||||
|
u.statusSelection.nextStatusID = u.columns[u.focusedColumn].statusID
|
||||||
|
if u.statusSelection.currentStatusID != u.statusSelection.nextStatusID {
|
||||||
|
u.statusSelection.moveCard(u.board)
|
||||||
|
}
|
||||||
|
u.statusSelection = statusSelection{0, 0, 0}
|
||||||
|
u.mode = normal
|
||||||
|
_ = u.refresh(false)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return event
|
return event
|
||||||
|
@ -113,7 +147,7 @@ func (u *UI) init() error {
|
||||||
|
|
||||||
u.SetRoot(u.pages, true)
|
u.SetRoot(u.pages, true)
|
||||||
|
|
||||||
if err := u.refresh(false, true); err != nil {
|
if err := u.refresh(false); err != nil {
|
||||||
return fmt.Errorf("error refreshing the board; %w", err)
|
return fmt.Errorf("error refreshing the board; %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -139,7 +173,7 @@ 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(true, false)
|
_ = u.refresh(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
u.pages.HidePage(deleteCardPage)
|
u.pages.HidePage(deleteCardPage)
|
||||||
|
@ -193,71 +227,7 @@ 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(false, false)
|
_ = u.refresh(false)
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (u *UI) updateMovePage() error {
|
|
||||||
if u.pages.HasPage(movePage) {
|
|
||||||
u.pages.RemovePage(movePage)
|
|
||||||
}
|
|
||||||
|
|
||||||
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(movePage)
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
focusedCard := u.columns[u.focusedColumn].focusedCard
|
|
||||||
cardID := u.columns[u.focusedColumn].cards[focusedCard].id
|
|
||||||
|
|
||||||
args := board.MoveToStatusArgs{
|
|
||||||
CardID: cardID,
|
|
||||||
CurrentStatusID: currentStatusID,
|
|
||||||
NextStatusID: nextStatusID,
|
|
||||||
}
|
|
||||||
_ = u.board.MoveToStatus(args)
|
|
||||||
|
|
||||||
u.pages.HidePage(movePage)
|
|
||||||
_ = u.refresh(false, false)
|
|
||||||
}
|
|
||||||
|
|
||||||
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 {
|
|
||||||
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(movePage, move, false, false)
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -273,7 +243,7 @@ func (u *UI) initColumns() error {
|
||||||
columns := make([]*column, len(statusList))
|
columns := make([]*column, len(statusList))
|
||||||
|
|
||||||
for i := range statusList {
|
for i := range statusList {
|
||||||
column := newColumn(statusList[i].ID, statusList[i].Name)
|
column := newColumn(statusList[i].ID, statusList[i].Name, u.boardMode)
|
||||||
u.flex.AddItem(column, 50, 1, true)
|
u.flex.AddItem(column, 50, 1, true)
|
||||||
columns[i] = column
|
columns[i] = column
|
||||||
}
|
}
|
||||||
|
@ -301,15 +271,15 @@ func (u *UI) updateColumn(index int) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *UI) shiftColumnFocus(s int) {
|
func (u *UI) shiftColumnFocus(movement boardMovement) {
|
||||||
switch s {
|
switch movement {
|
||||||
case shiftToNext:
|
case next:
|
||||||
if u.focusedColumn == len(u.columns)-1 {
|
if u.focusedColumn == len(u.columns)-1 {
|
||||||
u.focusedColumn = 0
|
u.focusedColumn = 0
|
||||||
} else {
|
} else {
|
||||||
u.focusedColumn++
|
u.focusedColumn++
|
||||||
}
|
}
|
||||||
case shiftToPrevious:
|
case previous:
|
||||||
if u.focusedColumn == 0 {
|
if u.focusedColumn == 0 {
|
||||||
u.focusedColumn = len(u.columns) - 1
|
u.focusedColumn = len(u.columns) - 1
|
||||||
} else {
|
} else {
|
||||||
|
@ -325,7 +295,7 @@ func (u *UI) setColumnFocus() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// refresh refreshes the UI.
|
// refresh refreshes the UI.
|
||||||
func (u *UI) refresh(updateFocusedColumnOnly, updateMovePage bool) error {
|
func (u *UI) refresh(updateFocusedColumnOnly bool) error {
|
||||||
if updateFocusedColumnOnly {
|
if updateFocusedColumnOnly {
|
||||||
if err := u.updateColumn(u.focusedColumn); err != nil {
|
if err := u.updateColumn(u.focusedColumn); err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -336,10 +306,6 @@ func (u *UI) refresh(updateFocusedColumnOnly, updateMovePage bool) error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if updateMovePage {
|
|
||||||
_ = u.updateMovePage()
|
|
||||||
}
|
|
||||||
|
|
||||||
u.setColumnFocus()
|
u.setColumnFocus()
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
@ -350,3 +316,7 @@ func (u *UI) shutdown() {
|
||||||
u.closeBoard()
|
u.closeBoard()
|
||||||
u.Stop()
|
u.Stop()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (u *UI) boardMode() boardMode {
|
||||||
|
return u.mode
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue