@@ -35,14 +35,38 @@ type treeNode struct {
3535 selectable bool
3636 children []* treeNode
3737 parent * treeNode
38+ depth int
3839}
3940
4041type model struct {
4142 tree * treeNode
4243 currentNode * treeNode
44+ currentIndex int
4345 selectedWorkspace * logicalcluster.Path
4446 width int
4547 height int
48+ visibleNodesCache []* treeNode
49+ cacheValid bool
50+ }
51+
52+ func (m * model ) invalidateCache () {
53+ m .cacheValid = false
54+ }
55+
56+ func (m * model ) getVisibleNodes () []* treeNode {
57+ if ! m .cacheValid {
58+ m .visibleNodesCache = nil
59+ m .collectVisibleNodes (m .tree , & m .visibleNodesCache )
60+ m .cacheValid = true
61+
62+ for i , node := range m .visibleNodesCache {
63+ if node == m .currentNode {
64+ m .currentIndex = i
65+ break
66+ }
67+ }
68+ }
69+ return m .visibleNodesCache
4670}
4771
4872func (m model ) Init () tea.Cmd {
@@ -72,12 +96,14 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
7296 case "right" , "l" , " " :
7397 if m .currentNode != nil && ! m .currentNode .expanded && len (m .currentNode .children ) > 0 {
7498 m .currentNode .expanded = true
99+ m .invalidateCache ()
75100 }
76101 return m , nil
77102
78103 case "left" , "h" , "backspace" :
79104 if m .currentNode != nil && m .currentNode .expanded {
80105 m .currentNode .expanded = false
106+ m .invalidateCache ()
81107 }
82108 return m , nil
83109
@@ -130,6 +156,15 @@ func (m model) View() string {
130156 if m .currentNode .info .Cluster != "" {
131157 detailParts = append (detailParts , fmt .Sprintf ("cluster:%s" , m .currentNode .info .Cluster ))
132158 }
159+ if len (m .currentNode .info .APIExports ) > 0 {
160+ detailParts = append (detailParts , fmt .Sprintf ("exports:%d" , len (m .currentNode .info .APIExports )))
161+ }
162+ if len (m .currentNode .info .APIExportEndpointSlices ) > 0 {
163+ detailParts = append (detailParts , fmt .Sprintf ("endpointslices:%d" , len (m .currentNode .info .APIExportEndpointSlices )))
164+ }
165+ if len (m .currentNode .info .APIBindings ) > 0 {
166+ detailParts = append (detailParts , fmt .Sprintf ("bindings:%d" , len (m .currentNode .info .APIBindings )))
167+ }
133168 details = detailStyle .Render (strings .Join (detailParts , " | " ))
134169 }
135170
@@ -147,11 +182,127 @@ func (m model) View() string {
147182 details ,
148183 )
149184
185+ visibleNodes := m .getVisibleNodes ()
150186 var treeLines []string
151187 treeLines = append (treeLines , "" )
152- m .flattenTree (m .tree , 0 , & treeLines )
188+
189+ for _ , node := range visibleNodes {
190+ treeLines = append (treeLines , m .renderNode (node ))
191+ }
192+
153193 treeLines = append (treeLines , "" )
154194
195+ sectionStyle := lipgloss .NewStyle ().
196+ Foreground (lipgloss .Color ("245" )).
197+ MarginTop (1 )
198+
199+ itemStyle := lipgloss .NewStyle ().
200+ Foreground (lipgloss .Color ("252" )).
201+ MarginLeft (2 )
202+
203+ exportNameStyle := lipgloss .NewStyle ().
204+ Foreground (lipgloss .Color ("39" ))
205+
206+ endpointSliceStyle := lipgloss .NewStyle ().
207+ Foreground (lipgloss .Color ("135" ))
208+
209+ bindingNameStyle := lipgloss .NewStyle ().
210+ Foreground (lipgloss .Color ("82" ))
211+
212+ bindingSourceStyle := lipgloss .NewStyle ().
213+ Foreground (lipgloss .Color ("248" )).
214+ Italic (true )
215+
216+ boundResourceStyle := lipgloss .NewStyle ().
217+ Foreground (lipgloss .Color ("220" ))
218+
219+ var apiDetails []string
220+ if m .currentNode != nil && m .currentNode .info != nil {
221+ var exportsColumn []string
222+ var slicesColumn []string
223+ var bindingsSection []string
224+
225+ if len (m .currentNode .info .APIExports ) > 0 || len (m .currentNode .info .APIExportEndpointSlices ) > 0 {
226+ availableWidth := m .width - 12
227+ columnWidth := (availableWidth - 4 ) / 2
228+
229+ if len (m .currentNode .info .APIExports ) > 0 {
230+ exportsColumn = append (exportsColumn , sectionStyle .Render (fmt .Sprintf ("APIExports (%d):" , len (m .currentNode .info .APIExports ))))
231+ for _ , export := range m .currentNode .info .APIExports {
232+ exportsColumn = append (exportsColumn , itemStyle .Render (fmt .Sprintf ("• %s" , exportNameStyle .Render (export .Name ))))
233+ }
234+ }
235+
236+ if len (m .currentNode .info .APIExportEndpointSlices ) > 0 {
237+ slicesColumn = append (slicesColumn , sectionStyle .Render (fmt .Sprintf ("APIExportEndpointSlices (%d):" , len (m .currentNode .info .APIExportEndpointSlices ))))
238+ for _ , slice := range m .currentNode .info .APIExportEndpointSlices {
239+ sliceLine := fmt .Sprintf ("• %s" , endpointSliceStyle .Render (slice .Name ))
240+ if slice .Spec .APIExport .Name != "" {
241+ exportPath := slice .Spec .APIExport .Path
242+ if exportPath != "" {
243+ sliceLine += fmt .Sprintf (" → %s:%s" , bindingSourceStyle .Render (exportPath ), slice .Spec .APIExport .Name )
244+ } else {
245+ sliceLine += fmt .Sprintf (" → %s" , slice .Spec .APIExport .Name )
246+ }
247+ }
248+ if len (slice .Status .APIExportEndpoints ) > 0 {
249+ sliceLine += fmt .Sprintf (" (%d endpoints)" , len (slice .Status .APIExportEndpoints ))
250+ }
251+ slicesColumn = append (slicesColumn , itemStyle .Render (sliceLine ))
252+ }
253+ }
254+
255+ maxLines := len (exportsColumn )
256+ if len (slicesColumn ) > maxLines {
257+ maxLines = len (slicesColumn )
258+ }
259+
260+ for i := 0 ; i < maxLines ; i ++ {
261+ leftLine := ""
262+ rightLine := ""
263+ if i < len (exportsColumn ) {
264+ leftLine = exportsColumn [i ]
265+ }
266+ if i < len (slicesColumn ) {
267+ rightLine = slicesColumn [i ]
268+ }
269+ leftPadded := leftLine
270+ if lipgloss .Width (leftLine ) < columnWidth {
271+ leftPadded = leftLine + strings .Repeat (" " , columnWidth - lipgloss .Width (leftLine ))
272+ }
273+ combinedLine := lipgloss .JoinHorizontal (lipgloss .Top , leftPadded , strings .Repeat (" " , 4 ), rightLine )
274+ apiDetails = append (apiDetails , combinedLine )
275+ }
276+ }
277+
278+ if len (m .currentNode .info .APIBindings ) > 0 {
279+ bindingsSection = append (bindingsSection , sectionStyle .Render (fmt .Sprintf ("APIBindings (%d):" , len (m .currentNode .info .APIBindings ))))
280+ for _ , binding := range m .currentNode .info .APIBindings {
281+ bindingLine := fmt .Sprintf ("• %s" , bindingNameStyle .Render (binding .Name ))
282+ if binding .Spec .Reference .Export != nil {
283+ exportPath := binding .Spec .Reference .Export .Path
284+ exportName := binding .Spec .Reference .Export .Name
285+ if exportPath != "" {
286+ bindingLine += fmt .Sprintf (" → %s:%s" , bindingSourceStyle .Render (exportPath ), exportName )
287+ } else {
288+ bindingLine += fmt .Sprintf (" → %s" , bindingSourceStyle .Render (exportName ))
289+ }
290+ }
291+ bindingsSection = append (bindingsSection , itemStyle .Render (bindingLine ))
292+ if len (binding .Status .BoundResources ) > 0 {
293+ for _ , resource := range binding .Status .BoundResources {
294+ groupResource := resource .Resource
295+ if resource .Group != "" {
296+ groupResource = fmt .Sprintf ("%s.%s" , resource .Resource , resource .Group )
297+ }
298+ bindingsSection = append (bindingsSection , itemStyle .Render (fmt .Sprintf (" └─ %s" , boundResourceStyle .Render (groupResource ))))
299+ }
300+ }
301+ }
302+ apiDetails = append (apiDetails , bindingsSection ... )
303+ }
304+ }
305+
155306 help := helpStyle .Render ("↑/↓: navigate →/←: expand/collapse q: quit" )
156307
157308 var prompt string
@@ -161,22 +312,25 @@ func (m model) View() string {
161312 Render ("\n Press Enter to switch to this workspace" )
162313 }
163314
164- content := lipgloss .JoinVertical (lipgloss .Left ,
165- header ,
166- strings .Join (treeLines , "\n " ),
167- help ,
168- prompt ,
169- )
315+ var contentParts []string
316+ contentParts = append (contentParts , header )
317+ contentParts = append (contentParts , strings .Join (treeLines , "\n " ))
318+
319+ if len (apiDetails ) > 0 {
320+ contentParts = append (contentParts , strings .Repeat ("─" , m .width - 8 ))
321+ contentParts = append (contentParts , strings .Join (apiDetails , "\n " ))
322+ }
323+
324+ contentParts = append (contentParts , help )
325+ contentParts = append (contentParts , prompt )
326+
327+ content := lipgloss .JoinVertical (lipgloss .Left , contentParts ... )
170328
171329 return borderStyle .Render (content )
172330}
173331
174- func (m * model ) flattenTree (node * treeNode , depth int , lines * []string ) {
175- if node == nil {
176- return
177- }
178-
179- prefix := strings .Repeat (" " , depth )
332+ func (m * model ) renderNode (node * treeNode ) string {
333+ prefix := strings .Repeat (" " , node .depth )
180334
181335 icon := " "
182336 if len (node .children ) > 0 {
@@ -210,44 +364,22 @@ func (m *model) flattenTree(node *treeNode, depth int, lines *[]string) {
210364 Render (" " + nodeName )
211365 }
212366
213- * lines = append (* lines , line )
214-
215- if node .expanded {
216- for _ , child := range node .children {
217- m .flattenTree (child , depth + 1 , lines )
218- }
219- }
367+ return line
220368}
221369
222370func (m * model ) moveToPrevious () {
223- if m .currentNode == nil {
224- return
225- }
226-
227- var allNodes []* treeNode
228- m .collectVisibleNodes (m .tree , & allNodes )
229-
230- for i , node := range allNodes {
231- if node == m .currentNode && i > 0 {
232- m .currentNode = allNodes [i - 1 ]
233- return
234- }
371+ allNodes := m .getVisibleNodes ()
372+ if m .currentIndex > 0 {
373+ m .currentIndex --
374+ m .currentNode = allNodes [m .currentIndex ]
235375 }
236376}
237377
238378func (m * model ) moveToNext () {
239- if m .currentNode == nil {
240- return
241- }
242-
243- var allNodes []* treeNode
244- m .collectVisibleNodes (m .tree , & allNodes )
245-
246- for i , node := range allNodes {
247- if node == m .currentNode && i < len (allNodes )- 1 {
248- m .currentNode = allNodes [i + 1 ]
249- return
250- }
379+ allNodes := m .getVisibleNodes ()
380+ if m .currentIndex < len (allNodes )- 1 {
381+ m .currentIndex ++
382+ m .currentNode = allNodes [m .currentIndex ]
251383 }
252384}
253385
@@ -260,6 +392,7 @@ func (m *model) collectVisibleNodes(node *treeNode, nodes *[]*treeNode) {
260392
261393 if node .expanded {
262394 for _ , child := range node .children {
395+ child .depth = node .depth + 1
263396 m .collectVisibleNodes (child , nodes )
264397 }
265398 }
0 commit comments