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}