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// URL returns the item URL view.
41func (i Item) URL() string {
42 return i.url.View()
43}
44
45// ItemDelegate is the delegate for the item.
46type ItemDelegate struct {
47 styles *styles.Styles
48 activeBox *box
49}
50
51// Width returns the item width.
52func (d ItemDelegate) Width() int {
53 width := d.styles.MenuItem.GetHorizontalFrameSize() + d.styles.MenuItem.GetWidth()
54 return width
55}
56
57// Height returns the item height. Implements list.ItemDelegate.
58func (d ItemDelegate) Height() int {
59 height := d.styles.MenuItem.GetVerticalFrameSize() + d.styles.MenuItem.GetHeight()
60 return height
61}
62
63// Spacing returns the spacing between items. Implements list.ItemDelegate.
64func (d ItemDelegate) Spacing() int { return 0 }
65
66// Update implements list.ItemDelegate.
67func (d ItemDelegate) Update(msg tea.Msg, m *list.Model) tea.Cmd {
68 cmds := make([]tea.Cmd, 0)
69 // if d.activeBox == nil || *d.activeBox != selectorBox {
70 // return nil
71 // }
72 for i, item := range m.VisibleItems() {
73 itm, ok := item.(Item)
74 if !ok {
75 continue
76 }
77 // FIXME check if X & Y are within the item box
78 switch msg := msg.(type) {
79 case tea.MouseMsg:
80 // x := msg.X
81 y := msg.Y
82 // minX := (i * d.Width())
83 // maxX := minX + d.Width()
84 minY := (i * d.Height())
85 maxY := minY + d.Height()
86 // log.Printf("i: %d, x: %d, y: %d", i, x, y)
87 if y < minY || y > maxY {
88 continue
89 }
90 }
91 y, cmd := itm.url.Update(msg)
92 itm.url = y.(*yankable.Yankable)
93 if cmd != nil {
94 cmds = append(cmds, cmd)
95 }
96 }
97 return tea.Batch(cmds...)
98}
99
100// Render implements list.ItemDelegate.
101func (d ItemDelegate) Render(w io.Writer, m list.Model, index int, listItem list.Item) {
102 i := listItem.(Item)
103 s := strings.Builder{}
104 var matchedRunes []int
105
106 // Conditions
107 var (
108 isSelected = index == m.Index()
109 // emptyFilter = m.FilterState() == list.Filtering && m.FilterValue() == ""
110 isFiltered = m.FilterState() == list.Filtering || m.FilterState() == list.FilterApplied
111 )
112
113 itemStyle := d.styles.MenuItem.Copy()
114 if isSelected {
115 itemStyle = itemStyle.BorderForeground(d.styles.ActiveBorderColor)
116 if d.activeBox != nil && *d.activeBox == readmeBox {
117 // TODO make this into its own color
118 itemStyle = itemStyle.BorderForeground(lipgloss.Color("15"))
119 }
120 }
121
122 title := i.name
123 updatedStr := fmt.Sprintf(" Updated %s", humanize.Time(i.lastUpdate))
124 updated := d.styles.MenuLastUpdate.
125 Copy().
126 Width(m.Width() - itemStyle.GetHorizontalFrameSize() - lipgloss.Width(title)).
127 Render(updatedStr)
128 titleStyle := lipgloss.NewStyle().
129 Align(lipgloss.Left).
130 Width(m.Width() - itemStyle.GetHorizontalFrameSize() - lipgloss.Width(updated))
131
132 if isFiltered && index < len(m.VisibleItems()) {
133 // Get indices of matched characters
134 matchedRunes = m.MatchesForItem(index)
135 }
136
137 if isFiltered {
138 unmatched := lipgloss.NewStyle().Inline(true)
139 matched := unmatched.Copy().Underline(true)
140 title = lipgloss.StyleRunes(title, matchedRunes, matched, unmatched)
141 }
142 title = titleStyle.Render(title)
143
144 s.WriteString(lipgloss.JoinHorizontal(lipgloss.Bottom, title, updated))
145 s.WriteString("\n")
146 s.WriteString(i.desc)
147 s.WriteString("\n\n")
148 s.WriteString(i.url.View())
149 w.Write([]byte(itemStyle.Render(s.String())))
150}