-
Notifications
You must be signed in to change notification settings - Fork 42
/
list.go
173 lines (155 loc) · 5.37 KB
/
list.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
package agent
import (
"fmt"
"time"
"github.com/charmbracelet/bubbles/key"
"github.com/charmbracelet/bubbles/list"
"github.com/charmbracelet/bubbles/viewport"
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss"
"github.com/pkg/browser"
)
var agentListStyle = lipgloss.NewStyle().Padding(1, 2)
var viewPortStyle = agentListStyle.Copy()
type AgentListModel struct {
agentList list.Model
agentViewPort viewport.Model
agentDataDisplayed bool
agentCurrentPage int
agentPerPage int
agentLastPage int
agentsLoading bool
agentLoader func(int) tea.Cmd
}
func NewAgentList(loader func(int) tea.Cmd, page, perpage int) AgentListModel {
l := list.New(nil, NewDelegate(), 0, 0)
l.Title = "Buildkite Agents"
l.SetStatusBarItemName("agent", "agents")
v := viewport.New(0, 0)
v.SetContent("")
l.AdditionalShortHelpKeys = func() []key.Binding {
return []key.Binding{
key.NewBinding(key.WithKeys("v"), key.WithHelp("v", "view")),
key.NewBinding(key.WithKeys("w"), key.WithHelp("w", "web")),
}
}
l.AdditionalFullHelpKeys = l.AdditionalShortHelpKeys
return AgentListModel{
agentList: l,
agentViewPort: v,
agentDataDisplayed: false,
agentCurrentPage: page,
agentPerPage: perpage,
agentLoader: loader,
}
}
func (m *AgentListModel) appendAgents() tea.Cmd {
// Set agentsLoading
m.agentsLoading = true
// Set a status message and start the agentList's spinner
startSpiner := m.agentList.StartSpinner()
statusMessage := fmt.Sprintf("Loading more agents: page %d of %d", m.agentCurrentPage, m.agentLastPage)
setStatus := m.agentList.NewStatusMessage(statusMessage)
// Fetch and append more agents
appendAgents := m.agentLoader(m.agentCurrentPage)
return tea.Sequence(tea.Batch(startSpiner, setStatus), appendAgents)
}
func (m *AgentListModel) setComponentSizing(width, height int) {
h, v := agentListStyle.GetFrameSize()
// Set component size
m.agentList.SetSize(width-h, height-v)
m.agentViewPort.Height = height - v
m.agentViewPort.Width = width - h
// Set styles width/height for resizing upon a tea.WindowSizeMsg
viewPortStyle.Width((width - h) / 2)
viewPortStyle.Height(height - v)
agentListStyle.Width((width - h) / 2)
agentListStyle.Height(height - v)
}
func (m *AgentListModel) clearAgentViewPort() {
m.agentViewPort.SetContent("")
m.agentDataDisplayed = false
}
func (m AgentListModel) Init() tea.Cmd {
return m.appendAgents()
}
func (m AgentListModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
var cmds []tea.Cmd
switch msg := msg.(type) {
case tea.WindowSizeMsg:
// When viewport size is reported, start a spinner and show a message to the user indicating agents are loading
m.setComponentSizing(msg.Width, msg.Height)
return m, tea.Batch(m.agentList.StartSpinner(), m.agentList.NewStatusMessage("Loading agents"))
case tea.KeyMsg:
switch msg.String() {
case "v":
if !m.agentDataDisplayed {
if agent, ok := m.agentList.SelectedItem().(AgentListItem); ok {
tableContext := AgentDataTable(agent.Agent)
m.agentViewPort.SetContent(tableContext)
m.agentDataDisplayed = true
}
} else {
m.clearAgentViewPort()
}
case "up":
m.clearAgentViewPort()
case "down":
m.clearAgentViewPort()
// Calculate last element, unfiltered and if the last agent page via the API has been reached
lastListItem := m.agentList.Index() == len(m.agentList.Items())-1
unfilteredState := m.agentList.FilterState() == list.Unfiltered
if !m.agentsLoading && lastListItem && unfilteredState {
lastPageReached := m.agentCurrentPage > m.agentLastPage
// If down is pressed on the last agent item, list state is unfiltered and more agents are available
// to load the API
if !lastPageReached {
return m, m.appendAgents()
} else {
// Append a status message to alert that no more agents are available to load from the API
setStatus := m.agentList.NewStatusMessage("No more agents to load!")
cmds = append(cmds, setStatus)
}
}
case "w":
if agent, ok := m.agentList.SelectedItem().(AgentListItem); ok {
if err := browser.OpenURL(*agent.WebURL); err != nil {
return m, m.agentList.NewStatusMessage(fmt.Sprintf("Failed opening agent web url: %s", err.Error()))
}
}
}
// Custom messages
case AgentItemsMsg:
// When a new page of agents is received, append them to existing agents in the list and stop the loading
// spinner
allItems := append(m.agentList.Items(), msg.ListItems()...)
cmds = append(cmds, m.agentList.SetItems(allItems))
m.agentList.StopSpinner()
// If the message from the initial agent load, set the last page
if m.agentCurrentPage == 1 {
m.agentLastPage = msg.lastPage
}
// Increment the models' current agent page, set agentsLoading to false
m.agentCurrentPage++
m.agentsLoading = false
case error:
m.agentList.StopSpinner()
// Show a status message for a long time
m.agentList.StatusMessageLifetime = time.Duration(time.Hour)
return m, m.agentList.NewStatusMessage(fmt.Sprintf("Failed loading agents: %s", msg.Error()))
}
var cmd tea.Cmd
m.agentList, cmd = m.agentList.Update(msg)
cmds = append(cmds, cmd)
return m, tea.Batch(cmds...)
}
func (m AgentListModel) View() string {
return lipgloss.JoinHorizontal(
lipgloss.Left,
agentListStyle.Render(m.agentList.View()),
lipgloss.JoinVertical(
lipgloss.Top,
viewPortStyle.Render(m.agentViewPort.View()),
),
)
}