Files
hobokenchicken 2a6b3830d5 Phase 5: TUI client (Bubbletea)
- cmd/tui/main.go: entry point with --server, --token, --version flags
- cmd/tui/config.go: config from ~/.config/dumpster/config.yaml or env vars
- cmd/tui/auth.go: login flow, session cookie management
- cmd/tui/api.go: HTTP client for servers, channels, messages, voice
- cmd/tui/model.go: root bubbletea model with state management
- cmd/tui/theme.go: Gruvbox dark lipgloss styles
- cmd/tui/servers.go: server bar with [D] style initials
- cmd/tui/channels.go: channel list with #text and 🔊 voice
- cmd/tui/chat.go: IRC-style message viewport with colored usernames
- cmd/tui/input.go: message input with > _ prompt
- cmd/tui/members.go: member list with ●/◐/○ status
- cmd/tui/ws.go: WebSocket client for real-time events
- cmd/tui/voice.go: voice state tracking and mute toggle
- cmd/tui/keys.go: vim-style keybindings (j/k, g/G, Tab, /, etc)
- cmd/tui/views.go: multi-pane layout rendering
- Makefile: added build-tui and run-tui targets
- go.mod/go.sum: added charmbracelet dependencies
2026-06-28 17:46:19 -04:00

151 lines
3.2 KiB
Go

package main
import (
"fmt"
"sort"
"strings"
)
// ChannelList renders the channel sidebar for a server.
func ChannelList(channels []Channel, activeIdx, hoverIdx, width, height int) string {
if len(channels) == 0 {
return ChannelListStyle.
Width(width).
Height(height).
Render(HelpStyle.Render(" No channels"))
}
// Group channels by category
type catGroup struct {
name string
channels []Channel
}
catOrder := make([]string, 0)
catMap := make(map[string]*catGroup)
for _, ch := range channels {
cat := ch.Category
if cat == "" {
cat = "general"
}
if _, ok := catMap[cat]; !ok {
catMap[cat] = &catGroup{name: cat}
catOrder = append(catOrder, cat)
}
catMap[cat].channels = append(catMap[cat].channels, ch)
}
// Stable sort categories
sort.Strings(catOrder)
// Build flat list for index mapping
var lines []string
flatIdx := 0
for _, catName := range catOrder {
group := catMap[catName]
lines = append(lines, CategoryStyle.Render(strings.ToUpper(group.name)))
for _, ch := range group.channels {
prefix := " "
if ch.Type == "voice" {
prefix = VoiceChannelPrefix.Render("🔊 ")
} else {
prefix = TextChannelPrefix.Render("# ")
}
style := ChannelInactive
if flatIdx == activeIdx {
style = ChannelActive
} else if flatIdx == hoverIdx {
style = ChannelHover
}
label := ch.Name
if len(label) > width-6 {
label = label[:width-9] + "..."
}
lines = append(lines, style.Render(prefix+label))
flatIdx++
}
}
return ChannelListStyle.
Width(width).
Height(height).
Render(strings.Join(lines, "\n"))
}
// ChannelFlatIndex converts a (category, channel index in category) to flat index.
func ChannelFlatIndex(channels []Channel, targetID string) int {
catOrder := getCatOrder(channels)
flatIdx := 0
for _, catName := range catOrder {
for _, ch := range groupByCategory(channels)[catName] {
if ch.ID == targetID {
return flatIdx
}
flatIdx++
}
}
return 0
}
// ChannelByFlatIndex returns the channel at a flat index in the grouped view.
func ChannelByFlatIndex(channels []Channel, flatIdx int) *Channel {
catOrder := getCatOrder(channels)
idx := 0
for _, catName := range catOrder {
for _, ch := range groupByCategory(channels)[catName] {
if idx == flatIdx {
return &ch
}
idx++
}
}
return nil
}
func groupByCategory(channels []Channel) map[string][]Channel {
m := make(map[string][]Channel)
for _, ch := range channels {
cat := ch.Category
if cat == "" {
cat = "general"
}
m[cat] = append(m[cat], ch)
}
return m
}
func getCatOrder(channels []Channel) []string {
seen := make(map[string]bool)
var order []string
for _, ch := range channels {
cat := ch.Category
if cat == "" {
cat = "general"
}
if !seen[cat] {
seen[cat] = true
order = append(order, cat)
}
}
sort.Strings(order)
return order
}
// ChannelCount returns total number of channels (for navigation limits).
func ChannelCount(channels []Channel) int {
return len(channels)
}
// formatChannelHeader renders the channel header for the chat pane.
func formatChannelHeader(ch Channel) string {
icon := "#"
if ch.Type == "voice" {
icon = "🔊"
}
return fmt.Sprintf("%s %s", icon, ch.Name)
}