GoSetup-bubble/setup/form.go

285 lines
8.7 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package setup
import (
"bubble/setup/fields"
"bubble/setup/ui"
"bubble/types"
"fmt"
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/glamour"
"github.com/charmbracelet/lipgloss"
"github.com/muesli/reflow/wordwrap"
"github.com/sirupsen/logrus"
"strings"
)
type FormModel struct {
CurrentSection *types.Section
FieldModels []fields.FieldModel
focus int
renderedContent string
width int
height int
}
func NewFormModel(application *types.PackageApplication, sectionIdx, focus, width, height 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,
width: width,
height: height,
}
if currentSection.Type == types.SectionAbout {
md := strings.Join(currentSection.Paragraphs, "\n\n")
renderer, _ := glamour.NewTermRenderer(
glamour.WithAutoStyle(),
glamour.WithWordWrap(width-10))
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.WindowSizeMsg:
m.width = msg.Width
m.height = msg.Height
if m.CurrentSection.Type == types.SectionAbout {
md := strings.Join(m.CurrentSection.Paragraphs, "\n\n")
renderer, _ := glamour.NewTermRenderer(
glamour.WithAutoStyle(),
glamour.WithWordWrap(m.width-10),
)
rendered, _ := renderer.Render(md)
m.renderedContent = rendered
}
return m, nil
case tea.KeyMsg:
switch msg.String() {
case "ctrl+c", "q":
return m, tea.Quit
case "esc":
for _, field := range m.FieldModels {
if field.CaptureKeys() {
return m, m.updateFields(msg)
}
}
return m, func() tea.Msg {
return types.ShowModalMsg{
Type: types.ModalWarning,
Title: "Прервать настройку",
Message: "Вы уверены, что хотите прервать настройку?\nВсе введенные данные будут потеряны.",
ModalButtons: []types.ModalButton{
{Text: "Да, выйти", Value: true},
{Text: "Продолжить настройку", Value: false},
},
OnResult: func(result any) tea.Cmd {
if result == true {
return func() tea.Msg { return types.BackToListMsg{} }
}
return nil
},
}
}
//вернуться к списку приложений, чтобы выбрать другое приложение.
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 {
m.focus = 0
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, focus is %d", m.focus)
//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 {
contentWidth := ui.ContentWidth(m.width)
var res strings.Builder
for i, field := range m.FieldModels {
fieldStr := wordwrap.String(field.View(), contentWidth-10)
if i == m.focus {
res.WriteString(ui.FormFieldFocused.Render(fieldStr))
} else {
res.WriteString(ui.FormFieldUnfocused.Render(fieldStr))
}
res.WriteString("\n\n")
}
if m.CurrentSection.Type == types.SectionAbout {
res.WriteString(m.renderedContent)
}
nextBtn := "[ next section ]"
if m.focus == len(m.FieldModels) {
res.WriteString(ui.FormNextButton.Render(nextBtn))
} else {
res.WriteString(ui.FormFieldUnfocused.Render(nextBtn))
}
container := ui.FormContainer.Width(contentWidth).Render(res.String())
return lipgloss.Place(m.width, m.height,
lipgloss.Center, lipgloss.Center,
container,
lipgloss.WithWhitespaceChars(" "),
)
//for i, field := range m.FieldModels {
// fieldStr := wordwrap.String(field.View(), contentWidth-8)
// if i == m.focus {
// res.WriteString(ui.FormFocusedStyle.Render(fieldStr))
// } else {
// res.WriteString(ui.FormUnfocusedStyle.Render(fieldStr))
// }
// res.WriteString("\n\n")
//}
//
//if m.CurrentSection.Type == types.SectionAbout {
// res.WriteString("\n\n" + m.renderedContent)
//}
//
//button := "[ next section ]"
//if m.focus == len(m.FieldModels) {
// res.WriteString(ui.FormFocusedStyle.Render(button))
//} else {
// res.WriteString(ui.FormUnfocusedStyle.Render(button))
//}
//
//container := ui.FormStyle.Width(contentWidth).Render(res.String())
//
//return lipgloss.Place(m.width, m.height,
// lipgloss.Center, lipgloss.Top,
// container,
// lipgloss.WithWhitespaceChars(" "),
//)
}
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...)
}