package setup import ( "bubble/setup/fields" "bubble/setup/ui" "bubble/types" "fmt" tea "github.com/charmbracelet/bubbletea" "github.com/charmbracelet/glamour" "github.com/sirupsen/logrus" "strings" ) type FormModel struct { CurrentSection *types.Section FieldModels []fields.FieldModel focus int renderedContent string } func NewFormModel(application *types.PackageApplication, sectionIdx, focus int) *FormModel { currentSection := &application.Sections[sectionIdx] var fieldModels []fields.FieldModel for _, sectionField := range currentSection.Fields { if !CheckCondition(sectionField.Condition, application) { continue } switch sectionField.FieldType { case "select": var options []string for j := range sectionField.Options { options = append(options, fmt.Sprintf("%v", sectionField.Options[j])) } if sectionField.SpecialValue != "" { specialOptions := SpecialValueHandle(sectionField.SpecialValue) if strSpecial, ok := specialOptions.([]string); ok { options = strSpecial } } selected := fmt.Sprintf("%v", sectionField.Variable.Value) fieldModels = append(fieldModels, fields.NewSelectModel(sectionField.Name, selected, options)) case "check": checked := false if value, ok := sectionField.Variable.Value.(bool); ok { checked = value } else { checked = sectionField.Variable.Value == "true" } fieldModels = append(fieldModels, fields.NewCheckboxModel(sectionField.Name, checked)) case "compute": value, err := ComputeValue(sectionField.Compute, application) if err != nil { value = fmt.Sprintf("Ошибка вычисления: %v", err) } fieldModels = append(fieldModels, fields.NewComputeModel(sectionField.Name, fmt.Sprintf("%v", value))) //compute расчет case "file": fieldModels = append(fieldModels, fields.NewFileSelectModel(sectionField.Name, fmt.Sprintf("%v", sectionField.Variable.Value), sectionField.FileFieldExtensions)) case "directory": fieldModels = append(fieldModels, fields.NewDirSelectModel(sectionField.Name, fmt.Sprintf("%v", sectionField.Variable.Value))) default: fieldModels = append(fieldModels, fields.NewInputModel(sectionField.Name, fmt.Sprintf("%v", sectionField.Variable.Value))) } } if len(fieldModels) > focus { fieldModels[focus].Focus() } m := &FormModel{ FieldModels: fieldModels, focus: focus, CurrentSection: currentSection, } if currentSection.Type == "abouta" { //"about" { md := strings.Join(currentSection.Paragraphs, "\n\n") renderer, _ := glamour.NewTermRenderer( glamour.WithAutoStyle(), glamour.WithWordWrap(80)) rendered, _ := renderer.Render(md) m.renderedContent = rendered m.focus = 0 } return m } func (m *FormModel) Init() tea.Cmd { return nil } func (m *FormModel) 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 "esc": //вернуться к списку приложений, чтобы выбрать другое приложение. case "enter": logrus.Infof("sections %s, focus %d, fields len %d", m.CurrentSection.Name, m.focus, len(m.FieldModels)) if len(m.FieldModels) == m.focus { return m, func() tea.Msg { return types.NextSectionMsg{} } } case "tab", "down", "shift+tab", "up": key := msg.String() if m.focus != len(m.FieldModels) { logrus.Infof("1") //todo исправить фокус field := m.FieldModels[m.focus] logrus.Infof("field %s, capture keys %t", field.GetLabel(), field.CaptureKeys()) if !field.CaptureKeys() { if key == "tab" || key == "down" { m.focus++ } else if key == "shift+tab" || key == "up" { m.focus-- } } } else { logrus.Infof("2") if key == "tab" || key == "down" { m.focus++ } else if key == "shift+tab" || key == "up" { m.focus-- } } if m.focus == len(m.FieldModels)+1 { m.focus = 0 } else if m.focus < 0 { m.focus = len(m.FieldModels) } } case types.SelectedOptionMsg: logrus.Infof("chose %+v", msg) } cmd := m.updateFields(msg) return m, cmd } func (m *FormModel) View() string { res := "" if len(m.FieldModels) > 0 { for i := range m.FieldModels { inputView := m.FieldModels[i].View() if m.focus == i { res += ui.FormFocusedStyle.Render(inputView) } else { res += ui.FormUnfocusedStyle.Render(inputView) } res += "\n" } } button := "\n[ next section ]" if m.CurrentSection.Type == "about" { res += m.renderedContent } if m.focus == len(m.FieldModels) { res += ui.FormFocusedStyle.Render(button) } else { res += ui.FormUnfocusedStyle.Render(button) } return ui.FormStyle.Render(res) } func (m *FormModel) updateFields(msg tea.Msg) tea.Cmd { var cmds []tea.Cmd for i, fieldModel := range m.FieldModels { var fieldCmd tea.Cmd var updated fields.FieldModel if i == m.focus { //если фокус установлен на текущем поле, то текущее поле активируем и передаем ему сообщения для обновления fieldModel.Focus() updated, fieldCmd = fieldModel.Update(msg) } else { //если фокус не установлен на текущем поле, то убираем с неё фокус, передаем сообщения, но только те, что не содержат действий клавиатуры fieldModel.Blur() if _, ok := msg.(tea.KeyMsg); !ok { updated, fieldCmd = fieldModel.Update(msg) } } if updated != nil { m.FieldModels[i] = updated for _, sectionField := range m.CurrentSection.Fields { if sectionField.Name == fieldModel.GetLabel() { oldValue := sectionField.Variable.Value newValue := fieldModel.GetValue() if oldValue != newValue { cmds = append(cmds, func() tea.Msg { return types.SugarMsg{ VariableName: sectionField.VariableName, FieldName: sectionField.Name, Compute: sectionField.Compute, Focus: m.focus, } }) sectionField.Variable.Value = newValue //recompute вызывать. } } } } if fieldCmd != nil { cmds = append(cmds, fieldCmd) } } return tea.Batch(cmds...) }