162 lines
4.6 KiB
Go
162 lines
4.6 KiB
Go
package tui
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
"time"
|
|
|
|
"charm.land/lipgloss/v2"
|
|
)
|
|
|
|
// ProgressItem tracks progress for a long-running operation.
|
|
type ProgressItem struct {
|
|
ID string
|
|
Name string
|
|
Total float64
|
|
Completed float64
|
|
Started int64 // Unix timestamp
|
|
}
|
|
|
|
// ProgressTracker manages multiple progress items.
|
|
type ProgressTracker struct {
|
|
items map[string]*ProgressItem
|
|
isDark bool
|
|
styles ProgressStyles
|
|
}
|
|
|
|
// ProgressStyles holds styling for progress display.
|
|
type ProgressStyles struct {
|
|
Bar lipgloss.Style
|
|
Label lipgloss.Style
|
|
Percent lipgloss.Style
|
|
Completed lipgloss.Style
|
|
Empty lipgloss.Style
|
|
}
|
|
|
|
// DefaultProgressStyles returns default styles.
|
|
func DefaultProgressStyles(isDark bool) ProgressStyles {
|
|
if isDark {
|
|
return ProgressStyles{
|
|
Bar: lipgloss.NewStyle().Foreground(lipgloss.Color("#88c0d0")),
|
|
Label: lipgloss.NewStyle().Foreground(lipgloss.Color("#d8dee9")),
|
|
Percent: lipgloss.NewStyle().Foreground(lipgloss.Color("#81a1c1")),
|
|
Completed: lipgloss.NewStyle().Foreground(lipgloss.Color("#a3be8c")),
|
|
Empty: lipgloss.NewStyle().Foreground(lipgloss.Color("#4c566a")),
|
|
}
|
|
}
|
|
return ProgressStyles{
|
|
Bar: lipgloss.NewStyle().Foreground(lipgloss.Color("#4f8f8f")),
|
|
Label: lipgloss.NewStyle().Foreground(lipgloss.Color("#4c566a")),
|
|
Percent: lipgloss.NewStyle().Foreground(lipgloss.Color("#5e81ac")),
|
|
Completed: lipgloss.NewStyle().Foreground(lipgloss.Color("#4f8f38")),
|
|
Empty: lipgloss.NewStyle().Foreground(lipgloss.Color("#9ca0a8")),
|
|
}
|
|
}
|
|
|
|
// NewProgressTracker creates a new progress tracker.
|
|
func NewProgressTracker(isDark bool) *ProgressTracker {
|
|
return &ProgressTracker{
|
|
items: make(map[string]*ProgressItem),
|
|
isDark: isDark,
|
|
styles: DefaultProgressStyles(isDark),
|
|
}
|
|
}
|
|
|
|
// SetDark updates theme.
|
|
func (pt *ProgressTracker) SetDark(isDark bool) {
|
|
pt.isDark = isDark
|
|
pt.styles = DefaultProgressStyles(isDark)
|
|
}
|
|
|
|
// Start begins tracking a new progress item.
|
|
func (pt *ProgressTracker) Start(id, name string, total float64) {
|
|
pt.items[id] = &ProgressItem{
|
|
ID: id,
|
|
Name: name,
|
|
Total: total,
|
|
Started: time.Now().Unix(),
|
|
}
|
|
}
|
|
|
|
// Update sets the current progress.
|
|
func (pt *ProgressTracker) Update(id string, completed float64) {
|
|
if item, ok := pt.items[id]; ok {
|
|
item.Completed = completed
|
|
}
|
|
}
|
|
|
|
// Complete marks an item as done.
|
|
func (pt *ProgressTracker) Complete(id string) {
|
|
if item, ok := pt.items[id]; ok {
|
|
item.Completed = item.Total
|
|
}
|
|
}
|
|
|
|
// Remove stops tracking an item.
|
|
func (pt *ProgressTracker) Remove(id string) {
|
|
delete(pt.items, id)
|
|
}
|
|
|
|
// Get returns a progress item by ID.
|
|
func (pt *ProgressTracker) Get(id string) (*ProgressItem, bool) {
|
|
item, ok := pt.items[id]
|
|
return item, ok
|
|
}
|
|
|
|
// All returns all progress items.
|
|
func (pt *ProgressTracker) All() []*ProgressItem {
|
|
result := make([]*ProgressItem, 0, len(pt.items))
|
|
for _, item := range pt.items {
|
|
result = append(result, item)
|
|
}
|
|
return result
|
|
}
|
|
|
|
// Render returns the progress bar view for an item.
|
|
func (pt *ProgressTracker) Render(id string, width int) string {
|
|
item, ok := pt.items[id]
|
|
if !ok || item.Total == 0 {
|
|
return ""
|
|
}
|
|
|
|
percent := int((item.Completed / item.Total) * 100)
|
|
barWidth := width - 20 // Leave room for label and percentage
|
|
if barWidth < 5 {
|
|
barWidth = 5
|
|
}
|
|
|
|
filled := int((item.Completed / item.Total) * float64(barWidth))
|
|
bar := strings.Repeat("█", filled) + strings.Repeat("░", barWidth-filled)
|
|
|
|
label := pt.styles.Label.Render(item.Name)
|
|
percentStr := pt.styles.Percent.Render(fmt.Sprintf("%d%%", percent))
|
|
|
|
return fmt.Sprintf("%s [%s] %s", label, bar, percentStr)
|
|
}
|
|
|
|
// RenderSimple returns a simple progress bar without the label.
|
|
func (pt *ProgressTracker) RenderSimple(id string, width int) string {
|
|
item, ok := pt.items[id]
|
|
if !ok || item.Total == 0 {
|
|
return ""
|
|
}
|
|
|
|
percent := int((item.Completed / item.Total) * 100)
|
|
barWidth := width - 8 // Leave room for percentage
|
|
if barWidth < 5 {
|
|
barWidth = 5
|
|
}
|
|
|
|
filled := int((item.Completed / item.Total) * float64(barWidth))
|
|
bar := strings.Repeat("█", filled) + strings.Repeat("░", barWidth-filled)
|
|
|
|
percentStr := pt.styles.Percent.Render(fmt.Sprintf("%d%%", percent))
|
|
|
|
return fmt.Sprintf("[%s] %s", bar, percentStr)
|
|
}
|
|
|
|
// HasItems returns true if there are any progress items.
|
|
func (pt *ProgressTracker) HasItems() bool {
|
|
return len(pt.items) > 0
|
|
}
|