97 lines
2.6 KiB
Go
97 lines
2.6 KiB
Go
// Package tui реализует интерактивный выбор варианта: список превью слева,
|
|
// полный текущий превью справа, стрелки для навигации, Enter — подтвердить.
|
|
package tui
|
|
|
|
import (
|
|
"fmt"
|
|
|
|
"asciigen"
|
|
|
|
tea "github.com/charmbracelet/bubbletea"
|
|
"github.com/charmbracelet/lipgloss"
|
|
)
|
|
|
|
var (
|
|
selectedStyle = lipgloss.NewStyle().Bold(true).Foreground(lipgloss.Color("42"))
|
|
normalStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("245"))
|
|
previewBox = lipgloss.NewStyle().Border(lipgloss.RoundedBorder()).Padding(1, 2)
|
|
)
|
|
|
|
// Model — состояние TUI. Renderer абстрагирует, картинка перед нами или текст.
|
|
type Model struct {
|
|
renderer asciigen.Renderer
|
|
variants []asciigen.Variant
|
|
cursor int
|
|
chosen string // ID выбранного варианта после подтверждения, "" пока не выбрано
|
|
fullText string // отрендеренная полная версия, заполняется после Enter
|
|
err error
|
|
}
|
|
|
|
func New(r asciigen.Renderer) (Model, error) {
|
|
variants, err := r.Previews()
|
|
if err != nil {
|
|
return Model{}, err
|
|
}
|
|
return Model{renderer: r, variants: variants}, nil
|
|
}
|
|
|
|
func (m Model) Init() tea.Cmd {
|
|
return nil
|
|
}
|
|
|
|
func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|
switch msg := msg.(type) {
|
|
case tea.KeyMsg:
|
|
switch msg.String() {
|
|
case "ctrl+c", "q":
|
|
return m, tea.Quit
|
|
case "up", "k":
|
|
if m.cursor > 0 {
|
|
m.cursor--
|
|
}
|
|
case "down", "j":
|
|
if m.cursor < len(m.variants)-1 {
|
|
m.cursor++
|
|
}
|
|
case "enter":
|
|
id := m.variants[m.cursor].ID
|
|
full, err := m.renderer.Full(id)
|
|
if err != nil {
|
|
m.err = err
|
|
return m, nil
|
|
}
|
|
m.chosen = id
|
|
m.fullText = full
|
|
return m, tea.Quit
|
|
}
|
|
}
|
|
return m, nil
|
|
}
|
|
|
|
func (m Model) View() string {
|
|
if m.err != nil {
|
|
return fmt.Sprintf("Ошибка: %v\n", m.err)
|
|
}
|
|
|
|
var list string
|
|
for i, v := range m.variants {
|
|
line := v.Label
|
|
if i == m.cursor {
|
|
line = selectedStyle.Render("> " + line)
|
|
} else {
|
|
line = normalStyle.Render(" " + line)
|
|
}
|
|
list += line + "\n"
|
|
}
|
|
|
|
preview := previewBox.Render(m.variants[m.cursor].Preview)
|
|
|
|
return lipgloss.JoinHorizontal(lipgloss.Top, list, " ", preview) +
|
|
"\n\n↑/↓ выбор • Enter подтвердить • q выход\n"
|
|
}
|
|
|
|
// Chosen возвращает (ID, полный рендер, ok) — вызывать после того как
|
|
// tea.NewProgram(...).Run() завершился.
|
|
func (m Model) Chosen() (string, string, bool) {
|
|
return m.chosen, m.fullText, m.chosen != ""
|
|
}
|