Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion internal/tui/ui/components/help_overlay.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ func (h *HelpOverlay) View() tea.View {

// Card style
cardStyle := lipgloss.NewStyle().
Border(lipgloss.RoundedBorder()).
Border(lipgloss.NormalBorder()).
BorderForeground(styles.ColorPrimary()).
Padding(1, 2)

Expand Down
9 changes: 0 additions & 9 deletions internal/tui/ui/components/items.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
package components

import (
"fmt"

"github.com/deeploy-sh/deeploy/internal/shared/model"
)

Expand All @@ -12,10 +10,6 @@ type ProjectItem struct {
PodCount int
}

func (i ProjectItem) Title() string { return i.Project.Title }
func (i ProjectItem) Suffix() string { return fmt.Sprintf("(%d)", i.PodCount) }
func (i ProjectItem) FilterValue() string { return i.Project.Title }

// ProjectsToItems converts a slice of Projects to ScrollItems
func ProjectsToItems(projects []model.Project, pods []model.Pod) []ScrollItem {
// Count pods per project
Expand All @@ -36,9 +30,6 @@ type PodItem struct {
model.Pod
}

func (i PodItem) Title() string { return i.Pod.Title }
func (i PodItem) FilterValue() string { return i.Pod.Title }

// PodsToItems converts a slice of Pods to ScrollItems
func PodsToItems(pods []model.Pod) []ScrollItem {
items := make([]ScrollItem, len(pods))
Expand Down
149 changes: 24 additions & 125 deletions internal/tui/ui/components/scrolllist.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,34 +3,21 @@ package components
import (
"strings"

"charm.land/bubbles/v2/textinput"
tea "charm.land/bubbletea/v2"
lipgloss "charm.land/lipgloss/v2"
"github.com/deeploy-sh/deeploy/internal/tui/ui/styles"
)

// ScrollItem interface für Items in der Liste
type ScrollItem interface {
Title() string
FilterValue() string
}
// ScrollItem is a marker interface for list row payloads.
type ScrollItem interface{}

// PrefixedItem optionales Interface für Items mit Prefix (●, [P], etc.)
type PrefixedItem interface {
Prefix() string
}

// SuffixedItem optionales Interface für Items mit Suffix (count, status, etc.)
type SuffixedItem interface {
Suffix() string
}
type RowRenderer func(item ScrollItem, width int, selected bool) string

// ScrollListConfig für NewScrollList
type ScrollListConfig struct {
Width int
Height int
WithInput bool
Placeholder string
Width int
Height int
RenderRow RowRenderer
}

// ScrollList ist eine einfache scrollbare Liste mit echtem Zeile-für-Zeile Scrolling
Expand All @@ -41,26 +28,16 @@ type ScrollList struct {
viewStart int // erstes sichtbares item
width int
height int // anzahl sichtbarer items
input *textinput.Model
renderRow RowRenderer
}

func NewScrollList(items []ScrollItem, cfg ScrollListConfig) ScrollList {
l := ScrollList{
allItems: items,
items: items,
width: cfg.Width,
height: cfg.Height,
}

if cfg.WithInput {
ti := NewTextInput(20)
ti.Placeholder = cfg.Placeholder
if ti.Placeholder == "" {
ti.Placeholder = "Type to search..."
}
ti.Focus()
ti.CharLimit = 100
l.input = &ti
allItems: items,
items: items,
width: cfg.Width,
height: cfg.Height,
renderRow: cfg.RenderRow,
}

return l
Expand Down Expand Up @@ -104,6 +81,9 @@ func (m ScrollList) Items() []ScrollItem { return m.items }

func (m *ScrollList) SetWidth(w int) { m.width = w }
func (m *ScrollList) SetHeight(h int) { m.height = h }
func (m *ScrollList) SetRenderer(r RowRenderer) {
m.renderRow = r
}

func (m *ScrollList) Select(index int) {
if index >= 0 && index < len(m.items) {
Expand All @@ -129,55 +109,19 @@ func (m *ScrollList) SetItems(items []ScrollItem) {
}

func (m ScrollList) Init() tea.Cmd {
if m.input != nil {
return textinput.Blink
}
return nil
}

func (m *ScrollList) filter() {
if m.input == nil {
return
}

query := strings.ToLower(m.input.Value())
if query == "" {
m.items = m.allItems
} else {
var filtered []ScrollItem
for _, item := range m.allItems {
if strings.Contains(strings.ToLower(item.FilterValue()), query) {
filtered = append(filtered, item)
}
}
m.items = filtered
}

// Reset cursor wenn nötig
if m.cursor >= len(m.items) {
m.cursor = max(0, len(m.items)-1)
}
m.viewStart = 0
}

func (m ScrollList) Update(msg tea.Msg) (ScrollList, tea.Cmd) {
var cmd tea.Cmd

// Input updaten wenn vorhanden (für Blink und Typing)
if m.input != nil {
*m.input, cmd = m.input.Update(msg)
m.filter()
}

// Navigation (vim-style + mouse)
switch msg := msg.(type) {
case tea.KeyPressMsg:
key := msg.String()

// Mit Input: Ctrl+P/N, Tab/Shift+Tab, Pfeiltasten
// Ohne Input: zusätzlich j/k
isUp := msg.Code == tea.KeyUp || key == "ctrl+p" || key == "shift+tab" || (m.input == nil && key == "k")
isDown := msg.Code == tea.KeyDown || key == "ctrl+n" || key == "tab" || (m.input == nil && key == "j")
isUp := msg.Code == tea.KeyUp || key == "ctrl+p" || key == "shift+tab" || key == "k"
isDown := msg.Code == tea.KeyDown || key == "ctrl+n" || key == "tab" || key == "j"

switch {
case isUp:
Expand All @@ -197,22 +141,6 @@ func (m ScrollList) Update(msg tea.Msg) (ScrollList, tea.Cmd) {
return m, cmd
}

func (m ScrollList) HasInput() bool {
return m.input != nil
}

func (m ScrollList) InputView() string {
if m.input == nil {
return ""
}
return lipgloss.NewStyle().
Width(m.width).
Background(styles.ColorBackgroundPanel()).
PaddingLeft(1).
PaddingBottom(1).
Render(m.input.View())
}

func (m ScrollList) View() string {
var lines []string

Expand All @@ -228,44 +156,15 @@ func (m ScrollList) View() string {
for i := m.viewStart; i < end; i++ {
item := m.items[i]
selected := i == m.cursor

// Get prefix if item implements PrefixedItem
prefix := ""
pi, ok := item.(PrefixedItem)
if ok {
prefix = pi.Prefix() + " "
}

// Get suffix if item implements SuffixedItem
suffix := ""
si, ok := item.(SuffixedItem)
if ok {
suffix = si.Suffix()
}

title := item.Title()
// Calculate space-between padding (1 leading + 1 trailing space)
usedWidth := 2 + len(prefix) + len(title) + len(suffix)
padding := max(1, m.width-usedWidth)

content := " " + prefix + title + strings.Repeat(" ", padding) + suffix + " "
lineStyle := lipgloss.NewStyle().Width(m.width)

var line string
if selected {
line = lineStyle.
Background(styles.ColorPrimary()).
Foreground(styles.ColorBackground()).
Bold(true).
Render(content)
} else {
line = lineStyle.
if m.renderRow == nil {
lines = append(lines, lipgloss.NewStyle().
Width(m.width).
Background(styles.ColorBackgroundPanel()).
Foreground(styles.ColorForeground()).
Render(content)
Foreground(styles.ColorError()).
Render(" missing row renderer"))
continue
}

lines = append(lines, line)
lines = append(lines, m.renderRow(item, m.width, selected))
}
}

Expand Down
30 changes: 22 additions & 8 deletions internal/tui/ui/components/theme_switcher.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,26 @@ type themeItem struct {
isActive bool
}

func (i themeItem) Title() string { return i.name }
func (i themeItem) FilterValue() string { return i.name }
func (i themeItem) Prefix() string {
if i.isActive {
return "●"
func renderThemeRow(item ScrollItem, width int, selected bool) string {
ti := item.(themeItem)
marker := " "
if ti.isActive {
marker = "●"
}
return " "
content := " " + marker + " " + ti.name

style := lipgloss.NewStyle().Width(width)
if selected {
return style.
Background(styles.ColorPrimary()).
Foreground(styles.ColorBackground()).
Bold(true).
Render(content)
}
return style.
Background(styles.ColorBackgroundPanel()).
Foreground(styles.ColorForeground()).
Render(content)
}

type ThemeSwitcher struct {
Expand All @@ -39,8 +52,9 @@ func NewThemeSwitcher() ThemeSwitcher {

card := styles.CardProps{Width: styles.CardWidthMD, Padding: []int{1, 1}}
l := NewScrollList(items, ScrollListConfig{
Width: card.InnerWidth(),
Height: 15,
Width: card.InnerWidth(),
Height: 15,
RenderRow: renderThemeRow,
})

for i, t := range themes {
Expand Down
2 changes: 1 addition & 1 deletion internal/tui/ui/layout/layout.go
Original file line number Diff line number Diff line change
Expand Up @@ -428,7 +428,7 @@ func (l *Layout) renderContent() string {
style := lipgloss.NewStyle().
Width(mainWidth).
Height(mainHeight).
Border(lipgloss.RoundedBorder()).
Border(lipgloss.NormalBorder()).
BorderForeground(borderColor)

if l.content == nil {
Expand Down
2 changes: 1 addition & 1 deletion internal/tui/ui/layout/sidebar.go
Original file line number Diff line number Diff line change
Expand Up @@ -339,7 +339,7 @@ func (s *Sidebar) renderPanel(title, content string, width, contentHeight int, f
return lipgloss.NewStyle().
Width(width - BorderSize).
Height(contentHeight).
Border(lipgloss.RoundedBorder()).
Border(lipgloss.NormalBorder()).
BorderForeground(borderColor).
Render(titleBar + "\n" + listStyle.Render(content))
}
Expand Down
Loading