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 Title string
20 Name string
21 Description string
22 LastUpdate time.Time
23 URL *yankable.Yankable
24}
25
26// ID implements selector.IdentifiableItem.
27func (i Item) ID() string {
28 return i.Name
29}
30
31// FilterValue implements list.Item.
32func (i Item) FilterValue() string { return i.Title }
33
34// ItemDelegate is the delegate for the item.
35type ItemDelegate struct {
36 styles *styles.Styles
37 activeBox *box
38}
39
40// Width returns the item width.
41func (d ItemDelegate) Width() int {
42 width := d.styles.MenuItem.GetHorizontalFrameSize() + d.styles.MenuItem.GetWidth()
43 return width
44}
45
46// Height returns the item height. Implements list.ItemDelegate.
47func (d ItemDelegate) Height() int {
48 height := d.styles.MenuItem.GetVerticalFrameSize() + d.styles.MenuItem.GetHeight()
49 return height
50}
51
52// Spacing returns the spacing between items. Implements list.ItemDelegate.
53func (d ItemDelegate) Spacing() int { return 1 }
54
55// Update implements list.ItemDelegate.
56func (d ItemDelegate) Update(msg tea.Msg, m *list.Model) tea.Cmd {
57 cmds := make([]tea.Cmd, 0)
58 // if d.activeBox == nil || *d.activeBox != selectorBox {
59 // return nil
60 // }
61 for i, item := range m.VisibleItems() {
62 itm, ok := item.(Item)
63 if !ok {
64 continue
65 }
66 // FIXME check if X & Y are within the item box
67 switch msg := msg.(type) {
68 case tea.MouseMsg:
69 // x := msg.X
70 y := msg.Y
71 // minX := (i * d.Width())
72 // maxX := minX + d.Width()
73 minY := (i * d.Height())
74 maxY := minY + d.Height()
75 // log.Printf("i: %d, x: %d, y: %d", i, x, y)
76 if y < minY || y > maxY {
77 continue
78 }
79 }
80 y, cmd := itm.URL.Update(msg)
81 itm.URL = y.(*yankable.Yankable)
82 if cmd != nil {
83 cmds = append(cmds, cmd)
84 }
85 }
86 return tea.Batch(cmds...)
87}
88
89// Render implements list.ItemDelegate.
90func (d ItemDelegate) Render(w io.Writer, m list.Model, index int, listItem list.Item) {
91 i := listItem.(Item)
92 s := strings.Builder{}
93 style := d.styles.MenuItem.Copy()
94 if index == m.Index() {
95 style = style.BorderForeground(d.styles.ActiveBorderColor)
96 if d.activeBox != nil && *d.activeBox == readmeBox {
97 // TODO make this into its own color
98 style = style.BorderForeground(lipgloss.Color("15"))
99 }
100 }
101 titleStr := i.Title
102 updatedStr := fmt.Sprintf(" Updated %s", humanize.Time(i.LastUpdate))
103 updated := d.styles.MenuLastUpdate.
104 Copy().
105 Width(m.Width() - style.GetHorizontalFrameSize() - lipgloss.Width(titleStr)).
106 Render(updatedStr)
107 title := lipgloss.NewStyle().
108 Align(lipgloss.Left).
109 Width(m.Width() - style.GetHorizontalFrameSize() - lipgloss.Width(updated)).
110 Render(titleStr)
111
112 s.WriteString(lipgloss.JoinHorizontal(lipgloss.Bottom, title, updated))
113 s.WriteString("\n")
114 s.WriteString(i.Description)
115 s.WriteString("\n\n")
116 s.WriteString(i.URL.View())
117 w.Write([]byte(style.Render(s.String())))
118}