item.go

  1package selection
  2
  3import (
  4	"fmt"
  5	"io"
  6	"strings"
  7	"time"
  8
  9	"github.com/charmbracelet/bubbles/list"
 10	tea "github.com/charmbracelet/bubbletea"
 11	"github.com/charmbracelet/lipgloss"
 12	"github.com/charmbracelet/soft-serve/ui/components/yankable"
 13	"github.com/charmbracelet/soft-serve/ui/styles"
 14	"github.com/dustin/go-humanize"
 15)
 16
 17// Item represents a single item in the selector.
 18type Item struct {
 19	name       string
 20	repo       string
 21	desc       string
 22	lastUpdate time.Time
 23	url        *yankable.Yankable
 24}
 25
 26// ID implements selector.IdentifiableItem.
 27func (i Item) ID() string {
 28	return i.repo
 29}
 30
 31// Title returns the item title. Implements list.DefaultItem.
 32func (i Item) Title() string { return i.name }
 33
 34// Description returns the item description. Implements list.DefaultItem.
 35func (i Item) Description() string { return i.desc }
 36
 37// FilterValue implements list.Item.
 38func (i Item) FilterValue() string { return i.name }
 39
 40// ItemDelegate is the delegate for the item.
 41type ItemDelegate struct {
 42	styles    *styles.Styles
 43	activeBox *box
 44}
 45
 46// Width returns the item width.
 47func (d ItemDelegate) Width() int {
 48	width := d.styles.MenuItem.GetHorizontalFrameSize() + d.styles.MenuItem.GetWidth()
 49	return width
 50}
 51
 52// Height returns the item height. Implements list.ItemDelegate.
 53func (d ItemDelegate) Height() int {
 54	height := d.styles.MenuItem.GetVerticalFrameSize() + d.styles.MenuItem.GetHeight()
 55	return height
 56}
 57
 58// Spacing returns the spacing between items. Implements list.ItemDelegate.
 59func (d ItemDelegate) Spacing() int { return 1 }
 60
 61// Update implements list.ItemDelegate.
 62func (d ItemDelegate) Update(msg tea.Msg, m *list.Model) tea.Cmd {
 63	cmds := make([]tea.Cmd, 0)
 64	// if d.activeBox == nil || *d.activeBox != selectorBox {
 65	// 	return nil
 66	// }
 67	for i, item := range m.VisibleItems() {
 68		itm, ok := item.(Item)
 69		if !ok {
 70			continue
 71		}
 72		// FIXME check if X & Y are within the item box
 73		switch msg := msg.(type) {
 74		case tea.MouseMsg:
 75			// x := msg.X
 76			y := msg.Y
 77			// minX := (i * d.Width())
 78			// maxX := minX + d.Width()
 79			minY := (i * d.Height())
 80			maxY := minY + d.Height()
 81			// log.Printf("i: %d, x: %d, y: %d", i, x, y)
 82			if y < minY || y > maxY {
 83				continue
 84			}
 85		}
 86		y, cmd := itm.url.Update(msg)
 87		itm.url = y.(*yankable.Yankable)
 88		if cmd != nil {
 89			cmds = append(cmds, cmd)
 90		}
 91	}
 92	return tea.Batch(cmds...)
 93}
 94
 95// Render implements list.ItemDelegate.
 96func (d ItemDelegate) Render(w io.Writer, m list.Model, index int, listItem list.Item) {
 97	i := listItem.(Item)
 98	s := strings.Builder{}
 99	var matchedRunes []int
100
101	// Conditions
102	var (
103		isSelected = index == m.Index()
104		// emptyFilter = m.FilterState() == list.Filtering && m.FilterValue() == ""
105		isFiltered = m.FilterState() == list.Filtering || m.FilterState() == list.FilterApplied
106	)
107
108	itemStyle := d.styles.MenuItem.Copy()
109	if isSelected {
110		itemStyle = itemStyle.BorderForeground(d.styles.ActiveBorderColor)
111		if d.activeBox != nil && *d.activeBox == readmeBox {
112			// TODO make this into its own color
113			itemStyle = itemStyle.BorderForeground(lipgloss.Color("15"))
114		}
115	}
116
117	title := i.name
118	updatedStr := fmt.Sprintf(" Updated %s", humanize.Time(i.lastUpdate))
119	updated := d.styles.MenuLastUpdate.
120		Copy().
121		Width(m.Width() - itemStyle.GetHorizontalFrameSize() - lipgloss.Width(title)).
122		Render(updatedStr)
123	titleStyle := lipgloss.NewStyle().
124		Align(lipgloss.Left).
125		Width(m.Width() - itemStyle.GetHorizontalFrameSize() - lipgloss.Width(updated))
126
127	if isFiltered && index < len(m.VisibleItems()) {
128		// Get indices of matched characters
129		matchedRunes = m.MatchesForItem(index)
130	}
131
132	if isFiltered {
133		unmatched := lipgloss.NewStyle().Inline(true)
134		matched := unmatched.Copy().Underline(true)
135		title = lipgloss.StyleRunes(title, matchedRunes, matched, unmatched)
136	}
137	title = titleStyle.Render(title)
138
139	s.WriteString(lipgloss.JoinHorizontal(lipgloss.Bottom, title, updated))
140	s.WriteString("\n")
141	s.WriteString(i.desc)
142	s.WriteString("\n\n")
143	s.WriteString(i.url.View())
144	w.Write([]byte(itemStyle.Render(s.String())))
145}