mirror of
https://github.com/lukaszraczylo/kportal.git
synced 2026-06-05 23:03:40 +00:00
f41c316b2b
* Add configuration wizard.
212 lines
4.7 KiB
Go
212 lines
4.7 KiB
Go
package ui
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
|
|
"github.com/charmbracelet/lipgloss"
|
|
)
|
|
|
|
// Color palette for wizards
|
|
var (
|
|
primaryColor = lipgloss.Color("205") // Pink/Magenta
|
|
successColor = lipgloss.Color("42") // Green
|
|
errorColor = lipgloss.Color("196") // Red
|
|
warningColor = lipgloss.Color("220") // Yellow
|
|
mutedColor = lipgloss.Color("241") // Gray
|
|
accentColor = lipgloss.Color("63") // Purple
|
|
highlightColor = lipgloss.Color("117") // Light blue
|
|
)
|
|
|
|
// Text styles
|
|
var (
|
|
wizardHeaderStyle = lipgloss.NewStyle().
|
|
Bold(true).
|
|
Foreground(primaryColor).
|
|
MarginBottom(0)
|
|
|
|
wizardStepStyle = lipgloss.NewStyle().
|
|
Foreground(mutedColor).
|
|
Italic(true)
|
|
|
|
breadcrumbStyle = lipgloss.NewStyle().
|
|
Foreground(highlightColor).
|
|
Bold(true)
|
|
|
|
selectedStyle = lipgloss.NewStyle().
|
|
Foreground(primaryColor).
|
|
Bold(true)
|
|
|
|
successStyle = lipgloss.NewStyle().
|
|
Foreground(successColor).
|
|
Bold(true)
|
|
|
|
errorStyle = lipgloss.NewStyle().
|
|
Foreground(errorColor).
|
|
Bold(true)
|
|
|
|
warningStyle = lipgloss.NewStyle().
|
|
Foreground(warningColor).
|
|
Bold(true)
|
|
|
|
mutedStyle = lipgloss.NewStyle().
|
|
Foreground(mutedColor)
|
|
|
|
helpStyle = lipgloss.NewStyle().
|
|
Foreground(mutedColor).
|
|
Italic(true)
|
|
|
|
spinnerStyle = lipgloss.NewStyle().
|
|
Foreground(accentColor).
|
|
Bold(true)
|
|
)
|
|
|
|
// Input styles
|
|
var (
|
|
inputStyle = lipgloss.NewStyle().
|
|
Foreground(lipgloss.Color("252"))
|
|
|
|
validInputStyle = lipgloss.NewStyle().
|
|
Foreground(successColor)
|
|
)
|
|
|
|
// Checkbox styles
|
|
var (
|
|
checkedBoxStyle = lipgloss.NewStyle().
|
|
Foreground(successColor).
|
|
Bold(true)
|
|
|
|
uncheckedBoxStyle = lipgloss.NewStyle().
|
|
Foreground(mutedColor)
|
|
)
|
|
|
|
// Container styles
|
|
var (
|
|
wizardBoxStyle = lipgloss.NewStyle().
|
|
Border(lipgloss.RoundedBorder()).
|
|
BorderForeground(accentColor).
|
|
Padding(1, 2).
|
|
Width(60)
|
|
)
|
|
|
|
// Helper functions for rendering
|
|
|
|
// renderProgress returns a step indicator like "Step 2/7"
|
|
func renderProgress(current, total int) string {
|
|
return wizardStepStyle.Render(fmt.Sprintf("Step %d/%d", current, total))
|
|
}
|
|
|
|
// renderHeader returns a formatted header with title and progress
|
|
func renderHeader(title, progress string) string {
|
|
header := wizardHeaderStyle.Render(title)
|
|
if progress != "" {
|
|
header += " " + progress
|
|
}
|
|
return header + "\n\n"
|
|
}
|
|
|
|
// renderBreadcrumb returns a formatted breadcrumb path
|
|
func renderBreadcrumb(parts ...string) string {
|
|
return breadcrumbStyle.Render(strings.Join(parts, " / "))
|
|
}
|
|
|
|
// renderList renders a list of items with cursor selection and viewport scrolling
|
|
func renderList(items []string, cursor int, prefix string, scrollOffset int) string {
|
|
var b strings.Builder
|
|
|
|
const viewportHeight = 20
|
|
totalItems := len(items)
|
|
|
|
// Show scroll up indicator if there are items above the viewport
|
|
if scrollOffset > 0 {
|
|
b.WriteString(mutedStyle.Render(" ↑ More above ↑") + "\n")
|
|
}
|
|
|
|
// Calculate visible range
|
|
start := scrollOffset
|
|
end := scrollOffset + viewportHeight
|
|
if end > totalItems {
|
|
end = totalItems
|
|
}
|
|
|
|
// Render visible items
|
|
for i := start; i < end; i++ {
|
|
cursorPrefix := prefix
|
|
if i == cursor {
|
|
cursorPrefix = "▸ "
|
|
b.WriteString(selectedStyle.Render(cursorPrefix + items[i]))
|
|
} else {
|
|
b.WriteString(cursorPrefix + items[i])
|
|
}
|
|
b.WriteString("\n")
|
|
}
|
|
|
|
// Show scroll down indicator if there are items below the viewport
|
|
if end < totalItems {
|
|
b.WriteString(mutedStyle.Render(" ↓ More below ↓") + "\n")
|
|
}
|
|
|
|
return b.String()
|
|
}
|
|
|
|
// renderTextInput renders a text input field with a cursor
|
|
func renderTextInput(label, value string, valid bool) string {
|
|
var b strings.Builder
|
|
|
|
b.WriteString(label)
|
|
|
|
inputText := value + "█"
|
|
if valid {
|
|
b.WriteString(validInputStyle.Render(inputText))
|
|
} else {
|
|
b.WriteString(inputStyle.Render(inputText))
|
|
}
|
|
|
|
return b.String()
|
|
}
|
|
|
|
// overlayContent overlays modal content centered on the base view
|
|
func overlayContent(base, modal string, termWidth, termHeight int) string {
|
|
baseLines := strings.Split(base, "\n")
|
|
modalLines := strings.Split(modal, "\n")
|
|
|
|
// Ensure base has enough lines
|
|
for len(baseLines) < termHeight {
|
|
baseLines = append(baseLines, "")
|
|
}
|
|
|
|
modalHeight := len(modalLines)
|
|
modalWidth := 0
|
|
for _, line := range modalLines {
|
|
w := lipgloss.Width(line)
|
|
if w > modalWidth {
|
|
modalWidth = w
|
|
}
|
|
}
|
|
|
|
// Calculate center position
|
|
startRow := (termHeight - modalHeight) / 2
|
|
if startRow < 0 {
|
|
startRow = 0
|
|
}
|
|
|
|
// Create result with modal overlaid
|
|
result := make([]string, len(baseLines))
|
|
copy(result, baseLines)
|
|
|
|
for i, modalLine := range modalLines {
|
|
row := startRow + i
|
|
if row >= 0 && row < len(result) {
|
|
// Center the modal line
|
|
padding := (termWidth - lipgloss.Width(modalLine)) / 2
|
|
if padding < 0 {
|
|
padding = 0
|
|
}
|
|
|
|
result[row] = strings.Repeat(" ", padding) + modalLine
|
|
}
|
|
}
|
|
|
|
return strings.Join(result, "\n")
|
|
}
|