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 } type column struct { *tview.Box statusID int cards []card focusedCard int } func newColumn(statusID int, statusName string) *column { column := column{ Box: tview.NewBox(), statusID: statusID, cards: nil, focusedCard: 0, } column.SetBorder(true) column.SetTitle(" " + statusName + " ") column.SetBorderColor(tcell.ColorOrangeRed.TrueColor()) 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 { c.focusedCard++ } case shiftToPrevious: if c.focusedCard == 0 { c.focusedCard = numCards - 1 } else { c.focusedCard-- } } } func (c *column) clear() { c.cards = nil c.focusedCard = 0 } 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 { 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) } for _, card := range cards { c.addCard(card.ID, card.Title, card.Created) } } 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(shiftToNext) case key == tcell.KeyUp || letter == 'k': c.shiftCardFocus(shiftToPrevious) } }) } func (c *column) Draw(screen tcell.Screen) { c.DrawForSubclass(screen, c) x, y, width, _ := c.GetInnerRect() 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 { 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[i].height; cy++ { screen.SetContent(xOffset, cy, vertLine, nil, style) } // Print the card's title for j := range c.cards[i].wrappedTitle { tview.Print( screen, c.cards[i].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[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 } }