Skip to content

Commit 658d494

Browse files
committed
feat(tui): add endpoint sidebar to selected collection view
- Displays a list of endpoints for the selected collection - Introduces EndpointItem component for rendering endpoint list items
1 parent f798733 commit 658d494

6 files changed

Lines changed: 243 additions & 17 deletions

File tree

internal/tui/app/model.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
6969
m.selectedIndex = m.collectionsView.GetSelectedIndex()
7070
m.mode = SelectedCollectionViewMode
7171
m.selectedCollectionView = views.NewSelectedCollectionView(m.ctx.Endpoints, *selectedItem)
72-
return m, nil
72+
return m, m.selectedCollectionView.Init()
7373
} else {
7474
log.Error("issue getting currently selected collection")
7575
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package components
2+
3+
import (
4+
"fmt"
5+
6+
"github.com/maniac-en/req/internal/backend/endpoints"
7+
)
8+
9+
type EndpointItem struct {
10+
endpoint endpoints.EndpointEntity
11+
}
12+
13+
func NewEndpointItem(endpoint endpoints.EndpointEntity) EndpointItem {
14+
return EndpointItem{
15+
endpoint: endpoint,
16+
}
17+
}
18+
19+
func (i EndpointItem) FilterValue() string {
20+
return i.endpoint.Name
21+
}
22+
23+
func (i EndpointItem) GetID() string {
24+
return fmt.Sprintf("%d", i.endpoint.ID)
25+
}
26+
27+
func (i EndpointItem) GetTitle() string {
28+
return fmt.Sprintf("%s %s", i.endpoint.Method, i.endpoint.Name)
29+
}
30+
31+
func (i EndpointItem) GetDescription() string {
32+
return i.endpoint.Url
33+
}
34+
35+
func (i EndpointItem) Title() string {
36+
return i.GetTitle()
37+
}
38+
39+
func (i EndpointItem) Description() string {
40+
return i.GetDescription()
41+
}
42+

internal/tui/components/paginated_list.go

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66

77
"github.com/charmbracelet/bubbles/list"
88
tea "github.com/charmbracelet/bubbletea"
9+
"github.com/charmbracelet/lipgloss"
910
"github.com/maniac-en/req/internal/tui/styles"
1011
)
1112

@@ -34,9 +35,17 @@ func NewPaginatedList(items []ListItem, title string) PaginatedList {
3435
l := list.New(listItems, paginatedItemDelegate{}, defaultWidth, defaultHeight)
3536
l.Title = title
3637
l.SetShowStatusBar(false)
37-
l.SetFilteringEnabled(true) // Enable filtering
38-
l.SetShowHelp(false) // Disable built-in help text
39-
l.Styles.Title = styles.TitleStyle
38+
l.SetFilteringEnabled(true)
39+
l.SetShowHelp(false)
40+
l.SetShowPagination(false)
41+
l.SetShowTitle(true)
42+
43+
l.Styles.StatusBar = lipgloss.NewStyle()
44+
l.Styles.PaginationStyle = lipgloss.NewStyle()
45+
l.Styles.HelpStyle = lipgloss.NewStyle()
46+
l.Styles.FilterPrompt = lipgloss.NewStyle()
47+
l.Styles.FilterCursor = lipgloss.NewStyle()
48+
l.Styles.Title = styles.TitleStyle.Copy().MarginBottom(0).PaddingBottom(0)
4049

4150
return PaginatedList{
4251
list: l,

internal/tui/styles/layout.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,4 +30,12 @@ var (
3030
MarginBottom(1).
3131
Foreground(Primary).
3232
Bold(true)
33+
34+
SidebarStyle = lipgloss.NewStyle().
35+
BorderRight(true).
36+
BorderStyle(lipgloss.NormalBorder()).
37+
BorderForeground(Secondary)
38+
39+
MainContentStyle = lipgloss.NewStyle().
40+
PaddingLeft(2)
3341
)
Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
package views
2+
3+
import (
4+
"context"
5+
"fmt"
6+
7+
tea "github.com/charmbracelet/bubbletea"
8+
"github.com/maniac-en/req/internal/backend/collections"
9+
"github.com/maniac-en/req/internal/backend/endpoints"
10+
"github.com/maniac-en/req/internal/tui/components"
11+
)
12+
13+
type EndpointSidebarView struct {
14+
list components.PaginatedList
15+
endpointsManager *endpoints.EndpointsManager
16+
collection collections.CollectionEntity
17+
width int
18+
height int
19+
initialized bool
20+
selectedIndex int
21+
endpoints []endpoints.EndpointEntity
22+
}
23+
24+
func NewEndpointSidebarView(endpointsManager *endpoints.EndpointsManager, collection collections.CollectionEntity) EndpointSidebarView {
25+
return EndpointSidebarView{
26+
endpointsManager: endpointsManager,
27+
collection: collection,
28+
selectedIndex: 0,
29+
}
30+
}
31+
32+
func (v EndpointSidebarView) Init() tea.Cmd {
33+
return v.loadEndpoints
34+
}
35+
36+
func (v *EndpointSidebarView) loadEndpoints() tea.Msg {
37+
result, err := v.endpointsManager.ListByCollection(context.Background(), v.collection.ID, 100, 0)
38+
if err != nil {
39+
return endpointsLoadError{err: err}
40+
}
41+
return endpointsLoaded{
42+
endpoints: result.Endpoints,
43+
}
44+
}
45+
46+
func (v EndpointSidebarView) Update(msg tea.Msg) (EndpointSidebarView, tea.Cmd) {
47+
var cmd tea.Cmd
48+
49+
switch msg := msg.(type) {
50+
case tea.WindowSizeMsg:
51+
v.width = msg.Width
52+
v.height = msg.Height
53+
if v.initialized {
54+
v.list.SetSize(v.width, v.height)
55+
}
56+
57+
case endpointsLoaded:
58+
v.endpoints = msg.endpoints
59+
items := make([]components.ListItem, len(msg.endpoints))
60+
for i, endpoint := range msg.endpoints {
61+
items[i] = components.NewEndpointItem(endpoint)
62+
}
63+
64+
title := fmt.Sprintf("Endpoints (%d)", len(msg.endpoints))
65+
v.list = components.NewPaginatedList(items, title)
66+
v.list.SetIndex(v.selectedIndex)
67+
68+
if v.width > 0 && v.height > 0 {
69+
v.list.SetSize(v.width, v.height)
70+
}
71+
v.initialized = true
72+
73+
case endpointsLoadError:
74+
v.initialized = true
75+
76+
case tea.KeyMsg:
77+
if v.initialized {
78+
v.list, cmd = v.list.Update(msg)
79+
}
80+
}
81+
82+
return v, cmd
83+
}
84+
85+
func (v EndpointSidebarView) GetSelectedEndpoint() *endpoints.EndpointEntity {
86+
if !v.initialized || len(v.endpoints) == 0 {
87+
return nil
88+
}
89+
90+
selectedIndex := v.list.SelectedIndex()
91+
if selectedIndex >= 0 && selectedIndex < len(v.endpoints) {
92+
return &v.endpoints[selectedIndex]
93+
}
94+
return nil
95+
}
96+
97+
func (v EndpointSidebarView) GetSelectedIndex() int {
98+
if v.initialized {
99+
return v.list.SelectedIndex()
100+
}
101+
return v.selectedIndex
102+
}
103+
104+
func (v *EndpointSidebarView) SetSelectedIndex(index int) {
105+
v.selectedIndex = index
106+
if v.initialized {
107+
v.list.SetIndex(index)
108+
}
109+
}
110+
111+
func (v EndpointSidebarView) View() string {
112+
if !v.initialized {
113+
title := "Endpoints"
114+
content := "Loading endpoints..."
115+
return v.formatEmptyState(title, content)
116+
}
117+
if len(v.endpoints) == 0 {
118+
title := "Endpoints (0)"
119+
content := "No endpoints found"
120+
return v.formatEmptyState(title, content)
121+
}
122+
return v.list.View()
123+
}
124+
125+
func (v EndpointSidebarView) formatEmptyState(title, content string) string {
126+
var lines []string
127+
lines = append(lines, title)
128+
lines = append(lines, "")
129+
lines = append(lines, content)
130+
131+
for len(lines) < v.height-2 {
132+
lines = append(lines, "")
133+
}
134+
135+
result := ""
136+
for _, line := range lines {
137+
result += line + "\n"
138+
}
139+
return result
140+
}
141+
142+
type endpointsLoaded struct {
143+
endpoints []endpoints.EndpointEntity
144+
}
145+
146+
type endpointsLoadError struct {
147+
err error
148+
}

internal/tui/views/selected_collection.go

Lines changed: 32 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,30 +2,33 @@ package views
22

33
import (
44
tea "github.com/charmbracelet/bubbletea"
5+
"github.com/charmbracelet/lipgloss"
56
"github.com/maniac-en/req/internal/backend/collections"
67
"github.com/maniac-en/req/internal/backend/endpoints"
78
"github.com/maniac-en/req/internal/tui/components"
9+
"github.com/maniac-en/req/internal/tui/styles"
810
)
911

1012
type SelectedCollectionView struct {
1113
layout components.Layout
1214
endpointsManager *endpoints.EndpointsManager
1315
collection collections.CollectionEntity
16+
sidebar EndpointSidebarView
1417
width int
1518
height int
16-
initialized bool
1719
}
1820

1921
func NewSelectedCollectionView(endpointsManager *endpoints.EndpointsManager, collection collections.CollectionEntity) SelectedCollectionView {
2022
return SelectedCollectionView{
2123
layout: components.NewLayout(),
2224
endpointsManager: endpointsManager,
2325
collection: collection,
26+
sidebar: NewEndpointSidebarView(endpointsManager, collection),
2427
}
2528
}
2629

2730
func (v SelectedCollectionView) Init() tea.Cmd {
28-
return nil
31+
return v.sidebar.Init()
2932
}
3033

3134
func (v SelectedCollectionView) Update(msg tea.Msg) (SelectedCollectionView, tea.Cmd) {
@@ -36,7 +39,11 @@ func (v SelectedCollectionView) Update(msg tea.Msg) (SelectedCollectionView, tea
3639
v.width = msg.Width
3740
v.height = msg.Height
3841
v.layout.SetSize(v.width, v.height)
39-
v.initialized = true
42+
43+
sidebarWidth := v.width / 3
44+
v.sidebar.width = sidebarWidth
45+
v.sidebar.height = v.height - 4
46+
4047

4148
case tea.KeyMsg:
4249
switch msg.String() {
@@ -45,21 +52,33 @@ func (v SelectedCollectionView) Update(msg tea.Msg) (SelectedCollectionView, tea
4552
}
4653
}
4754

55+
56+
// Forward messages to sidebar
57+
v.sidebar, cmd = v.sidebar.Update(msg)
58+
4859
return v, cmd
4960
}
5061

5162
func (v SelectedCollectionView) View() string {
52-
if !v.initialized {
53-
return v.layout.FullView(
54-
"Loading...",
55-
"Initializing collection view...",
56-
"Please wait",
57-
)
58-
}
59-
6063
title := "Collection: " + v.collection.Name
61-
content := "Selected collection view - endpoints will be displayed here"
62-
instructions := "esc/q: back to collections"
64+
65+
sidebarContent := v.sidebar.View()
66+
mainContent := "Endpoint details will be displayed here"
67+
sidebarStyle := styles.SidebarStyle.Copy().
68+
Width(v.width / 3).
69+
Height(v.height - 4)
70+
71+
mainStyle := styles.MainContentStyle.Copy().
72+
Width((v.width * 2 / 3) - 1).
73+
Height(v.height - 4)
74+
75+
content := lipgloss.JoinHorizontal(
76+
lipgloss.Top,
77+
sidebarStyle.Render(sidebarContent),
78+
mainStyle.Render(mainContent),
79+
)
80+
81+
instructions := "↑↓: navigate endpoints • esc/q: back to collections"
6382

6483
return v.layout.FullView(
6584
title,

0 commit comments

Comments
 (0)