package tui import ( "fmt" "strings" "github.com/charmbracelet/bubbles/textinput" "github.com/charmbracelet/bubbletea" "github.com/mirivlad/sshkeeper/internal/model" ) // --- Template form model --- type templateFormModel struct { edit bool oldName string inputs []textinput.Model labels []string focusIdx int err error saved bool width int height int } func newTemplateFormModel(t *model.CommandTemplate, w, h int) *templateFormModel { labels := []string{"Name", "Command", "Description"} inputs := make([]textinput.Model, len(labels)) for i := range inputs { inputs[i] = textinput.New() inputs[i].CharLimit = 512 } inputs[0].Placeholder = "uptime" inputs[1].Placeholder = "uptime" inputs[2].Placeholder = "optional" inputs[0].Focus() tf := &templateFormModel{inputs: inputs, labels: labels, width: w, height: h} if t != nil { tf.edit = true tf.oldName = t.Name inputs[0].SetValue(t.Name) inputs[1].SetValue(t.Command) inputs[2].SetValue(t.Description) } tf.updateFocus() return tf } func (tf *templateFormModel) Init() tea.Cmd { return nil } func (tf *templateFormModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { switch msg := msg.(type) { case tea.KeyMsg: switch msg.Type { case tea.KeyTab, tea.KeyDown: tf.focusIdx++ if tf.focusIdx > len(tf.inputs) { tf.focusIdx = 0 } tf.updateFocus() return tf, nil case tea.KeyShiftTab, tea.KeyUp: tf.focusIdx-- if tf.focusIdx < 0 { tf.focusIdx = len(tf.inputs) } tf.updateFocus() return tf, nil case tea.KeyEnter: if tf.focusIdx == len(tf.inputs) { return tf, tf.save() } tf.focusIdx++ tf.updateFocus() return tf, nil } } if tf.focusIdx < len(tf.inputs) { var cmd tea.Cmd tf.inputs[tf.focusIdx], cmd = tf.inputs[tf.focusIdx].Update(msg) return tf, cmd } return tf, nil } func (tf *templateFormModel) updateFocus() { for i := range tf.inputs { tf.inputs[i].Blur() tf.inputs[i].Prompt = blurredStyle.Render(tf.labels[i] + ": ") } if tf.focusIdx < len(tf.inputs) { tf.inputs[tf.focusIdx].Focus() tf.inputs[tf.focusIdx].Prompt = focusedStyle.Render(tf.labels[tf.focusIdx] + "> ") } } func (tf *templateFormModel) save() tea.Cmd { return func() tea.Msg { if SaveCommandTemplate == nil { return saveDoneMsg{err: fmt.Errorf("template storage is unavailable")} } t := &model.CommandTemplate{ Name: strings.TrimSpace(tf.inputs[0].Value()), Command: strings.TrimSpace(tf.inputs[1].Value()), Description: strings.TrimSpace(tf.inputs[2].Value()), } if t.Name == "" { return saveDoneMsg{err: fmt.Errorf("name is required")} } if t.Command == "" { return saveDoneMsg{err: fmt.Errorf("command is required")} } if err := SaveCommandTemplate(tf.oldName, t); err != nil { return saveDoneMsg{err: err} } return saveDoneMsg{} } } func (tf *templateFormModel) View() string { var b strings.Builder title := "Add Template" if tf.edit { title = "Edit Template" } b.WriteString(titleStyle.Render(title)) b.WriteString("\n\n") for i := range tf.inputs { b.WriteString(tf.inputs[i].View()) b.WriteString("\n") } button := "[ Save ]" if tf.focusIdx == len(tf.inputs) { button = selectedStyle.Render(button) } b.WriteString("\n" + button + "\n\n") if tf.err != nil { b.WriteString(errorStyle.Render(tf.err.Error())) b.WriteString("\n") } b.WriteString(renderHelp([]helpItem{ {Key: "Tab/↓", Action: "next"}, {Key: "↑", Action: "prev"}, {Key: "Enter", Action: "select"}, {Key: "Esc", Action: "back"}, }, tf.width)) return b.String() }