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
This commit is contained in:
@@ -0,0 +1,88 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/charmbracelet/lipgloss"
|
||||
)
|
||||
|
||||
// MemberEntry renders a single member in the member list.
|
||||
func MemberEntry(username string, status string) string {
|
||||
var statusDot string
|
||||
switch status {
|
||||
case "online":
|
||||
statusDot = StatusOnline.Render("●")
|
||||
case "idle":
|
||||
statusDot = StatusIdle.Render("◐")
|
||||
default:
|
||||
statusDot = StatusOffline.Render("○")
|
||||
}
|
||||
|
||||
return fmt.Sprintf(" %s %s", statusDot, username)
|
||||
}
|
||||
|
||||
// MemberList renders the member sidebar.
|
||||
func MemberList(user UserProfile, members []MemberEntryData, width, height int, visible bool) string {
|
||||
if !visible {
|
||||
return ""
|
||||
}
|
||||
|
||||
var lines []string
|
||||
lines = append(lines, CategoryStyle.Render("ONLINE"))
|
||||
|
||||
if user.Username != "" {
|
||||
lines = append(lines, MemberEntry(
|
||||
user.DisplayName,
|
||||
"online",
|
||||
))
|
||||
}
|
||||
|
||||
for _, m := range members {
|
||||
status := m.Status
|
||||
if status == "" {
|
||||
status = "offline"
|
||||
}
|
||||
lines = append(lines, MemberEntry(m.DisplayName, status))
|
||||
}
|
||||
|
||||
if len(members) == 0 && user.Username != "" {
|
||||
lines = append(lines, "")
|
||||
lines = append(lines, HelpStyle.Render(" No other members"))
|
||||
}
|
||||
|
||||
return MemberListStyle.
|
||||
Width(width).
|
||||
Height(height).
|
||||
Render(strings.Join(lines, "\n"))
|
||||
}
|
||||
|
||||
// MemberEntryData is a lightweight member representation.
|
||||
type MemberEntryData struct {
|
||||
UserID string
|
||||
Username string
|
||||
DisplayName string
|
||||
Status string
|
||||
}
|
||||
|
||||
// VoiceParticipants renders the voice participant list (used in voice channels).
|
||||
func VoiceParticipants(participants []VoiceParticipant, width int) string {
|
||||
if len(participants) == 0 {
|
||||
return ""
|
||||
}
|
||||
|
||||
var lines []string
|
||||
lines = append(lines, CategoryStyle.Render("VOICE"))
|
||||
|
||||
for _, p := range participants {
|
||||
icon := "🔊"
|
||||
if p.State != "connected" {
|
||||
icon = "🔇"
|
||||
}
|
||||
lines = append(lines, fmt.Sprintf(" %s %s", icon, p.Username))
|
||||
}
|
||||
|
||||
return lipgloss.NewStyle().
|
||||
Width(width).
|
||||
Render(strings.Join(lines, "\n"))
|
||||
}
|
||||
Reference in New Issue
Block a user