pelican/internal/ui/column.go

218 lines
4.7 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/gdamore/tcell/v2"
"github.com/rivo/tview"
)
type card struct {
id int
wrappedTitle []string
created string
height int
2023-05-06 12:49:40 +01:00
}
type column struct {
*tview.Box
2023-05-06 12:49:40 +01:00
statusID int
statusName string
cards []card
focusedCard int
getBoardModeFunc func() boardMode
}
2023-05-06 12:49:40 +01:00
func newColumn(statusID int, statusName string, getBoardModeFunc func() boardMode) *column {
column := column{
Box: tview.NewBox(),
statusID: statusID,
statusName: statusName,
cards: nil,
focusedCard: 0,
getBoardModeFunc: getBoardModeFunc,
}
2023-05-06 12:49:40 +01:00
column.SetBorder(true)
2023-05-06 12:49:40 +01:00
return &column
}
2023-05-06 12:49:40 +01:00
func (c *column) addCard(cardID int, title, created string) {
wrappedTitle := tview.WordWrap(title, 40)
metadataHeight := 1
height := len(wrappedTitle) + metadataHeight
2023-05-06 12:49:40 +01:00
card := card{
id: cardID,
wrappedTitle: wrappedTitle,
created: created,
height: height,
2023-05-06 12:49:40 +01:00
}
c.cards = append(c.cards, card)
2023-05-06 12:49:40 +01:00
}
func (c *column) shiftCardFocus(movement boardMovement) {
numCards := len(c.cards)
2023-05-06 12:49:40 +01:00
switch movement {
case next:
if c.focusedCard == numCards-1 {
c.focusedCard = 0
2023-05-06 12:49:40 +01:00
} else {
c.focusedCard++
2023-05-06 12:49:40 +01:00
}
case previous:
if c.focusedCard == 0 {
c.focusedCard = numCards - 1
2023-05-06 12:49:40 +01:00
} else {
c.focusedCard--
2023-05-06 12:49:40 +01:00
}
}
}
2023-05-06 12:49:40 +01:00
func (c *column) clear() {
c.cards = nil
c.focusedCard = 0
2023-05-06 12:49:40 +01:00
}
func (c *column) update(kanban board.Board) error {
c.clear()
2023-05-06 12:49:40 +01:00
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)
}
numCardIDs := len(status.CardIds)
if status.CardIds != nil && numCardIDs > 0 {
cards, err := kanban.CardList(status.CardIds)
if err != nil {
return fmt.Errorf("unable to get the list of cards from Status %d; %w", c.statusID, err)
}
2023-05-06 12:49:40 +01:00
for _, card := range cards {
c.addCard(card.ID, card.Title, card.Created)
}
2023-05-06 12:49:40 +01:00
}
c.SetTitle(fmt.Sprintf(" %s (%d) ", c.statusName, numCardIDs))
return nil
}
// InputHandler returns the handler for this primitive.
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(next)
case key == tcell.KeyUp || letter == 'k':
c.shiftCardFocus(previous)
}
})
}
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)
x, y, width, _ := c.GetInnerRect()
xOffset := x + 1
cursor := y
gap := 1
for ind := 0; ind < len(c.cards); ind++ {
var (
style tcell.Style
vertLine rune
)
if c.HasFocus() && ind == c.focusedCard && mode == normal {
vertLine = tview.BoxDrawingsHeavyVertical
style = tcell.StyleDefault.Foreground(tcell.ColorBlue.TrueColor())
} else {
vertLine = tview.BoxDrawingsLightVertical
style = tcell.StyleDefault.Foreground(tcell.ColorYellow.TrueColor())
}
// Draw a vertical line down the left side of each card
for cy := cursor; cy < cursor+c.cards[ind].height; cy++ {
screen.SetContent(xOffset, cy, vertLine, nil, style)
}
// Print the card's title
for j := range c.cards[ind].wrappedTitle {
tview.Print(
screen,
c.cards[ind].wrappedTitle[j],
xOffset+2,
cursor+j,
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[ind].id, cal, c.cards[ind].created),
xOffset+2,
cursor+len(c.cards[ind].wrappedTitle),
width,
tview.AlignLeft|tview.AlignTop,
tcell.ColorGrey.TrueColor(),
)
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(),
)
}
2023-05-06 12:49:40 +01:00
}