target.go

  1package screens
  2
  3import (
  4	"charm.land/bubbles/v2/key"
  5	tea "charm.land/bubbletea/v2"
  6
  7	"charm.land/huh/v2"
  8
  9	"git.secluded.site/keld/internal/theme"
 10	"git.secluded.site/keld/internal/ui"
 11)
 12
 13// Target is a Screen adapter that wraps a huh Input for entering
 14// the restore target directory. It validates that the input is
 15// non-empty and returns the entered path via [Value].
 16type Target struct {
 17	styles    *theme.Styles
 18	form      *huh.Form
 19	entered   string
 20	selection string
 21}
 22
 23// NewTarget creates a target directory screen. The styles pointer
 24// should come from the session so theme updates propagate.
 25func NewTarget(styles *theme.Styles) *Target {
 26	return &Target{
 27		styles: styles,
 28	}
 29}
 30
 31// buildForm constructs the huh form. Called from Init when the form
 32// needs (re)building.
 33func (t *Target) buildForm() {
 34	// Preserve any previous selection so back-navigation shows the
 35	// user's earlier input. Reset the breadcrumb label since the
 36	// screen is being re-activated.
 37	t.selection = ""
 38
 39	input := huh.NewInput().
 40		Title("Restore destination").
 41		Placeholder("e.g. /tmp/restore").
 42		Value(&t.entered).
 43		Validate(huh.ValidateNotEmpty())
 44
 45	t.form = huh.NewForm(
 46		huh.NewGroup(input),
 47	).WithTheme(t.styles.Huh).WithShowHelp(false)
 48}
 49
 50// Init initialises the embedded form. On first call or after
 51// completion, the form is (re)built so it picks up the current
 52// theme and can accept input again.
 53func (t *Target) Init() tea.Cmd {
 54	if t.form == nil || t.form.State != huh.StateNormal {
 55		t.buildForm()
 56	}
 57	return t.form.Init()
 58}
 59
 60// Update handles messages. Esc is intercepted for back navigation.
 61// All other messages are forwarded to the huh form.
 62func (t *Target) Update(msg tea.Msg) (ui.Screen, tea.Cmd) {
 63	if t.form == nil {
 64		return t, nil
 65	}
 66
 67	switch msg.(type) {
 68	case tea.BackgroundColorMsg:
 69		t.buildForm()
 70		return t, t.form.Init()
 71	}
 72
 73	if kp, ok := msg.(tea.KeyPressMsg); ok {
 74		if kp.Code == tea.KeyEscape {
 75			return t, ui.BackCmd
 76		}
 77	}
 78
 79	model, cmd := t.form.Update(msg)
 80	if f, ok := model.(*huh.Form); ok {
 81		t.form = f
 82	}
 83
 84	if t.form.State == huh.StateCompleted {
 85		t.selection = t.entered
 86		return t, ui.DoneCmd
 87	}
 88
 89	return t, cmd
 90}
 91
 92// View renders the form.
 93func (t *Target) View() string {
 94	if t.form == nil {
 95		return ""
 96	}
 97	return t.form.View()
 98}
 99
100// Title returns the screen's display title.
101func (t *Target) Title() string { return "Target directory" }
102
103// KeyBindings returns bindings for the help bar.
104func (t *Target) KeyBindings() []key.Binding {
105	return []key.Binding{
106		key.NewBinding(key.WithKeys("enter"), key.WithHelp("enter", "confirm")),
107	}
108}
109
110// Selection returns the entered path for breadcrumb display, or ""
111// if nothing has been entered yet.
112func (t *Target) Selection() string { return t.selection }
113
114// Value returns the entered target directory path.
115func (t *Target) Value() string { return t.entered }