package setup import ( "bubble/bootstrap" "bubble/types" "fmt" tea "github.com/charmbracelet/bubbletea" "github.com/sirupsen/logrus" ) func Start() { p := tea.NewProgram(NewMain(), tea.WithAltScreen()) if _, err := p.Run(); err != nil { panic(err) } } type Main struct { current tea.Model overlay tea.Model Applications []types.PackageApplication CurrentApplication *types.PackageApplication CurrentSection *types.Section CurrentSectionIdx int Width int Height int } func NewMain() *Main { apps := bootstrap.LoadApplications() m := &Main{ Applications: apps, CurrentSectionIdx: 0, } m.current = NewListApplicationsModel(m.Applications, m.Width, m.Height) return m } func (m *Main) Init() tea.Cmd { return m.current.Init() } func (m *Main) Update(msg tea.Msg) (tea.Model, tea.Cmd) { var cmds []tea.Cmd if m.overlay != nil { var cmd tea.Cmd m.overlay, cmd = m.overlay.Update(msg) if cmd != nil { cmds = append(cmds, cmd) } } else { var cmd tea.Cmd m.current, cmd = m.current.Update(msg) if cmd != nil { cmds = append(cmds, cmd) } } switch msg := msg.(type) { case tea.WindowSizeMsg: m.Width = msg.Width m.Height = msg.Height if m.current != nil { m.current, _ = m.current.Update(msg) } if m.overlay != nil { m.overlay, _ = m.overlay.Update(msg) } case tea.KeyMsg: switch msg.String() { case "ctrl+c": return m, func() tea.Msg { return types.ShowModalMsg{ Type: types.ModalWarning, Title: "Закрытие приложения", Message: "Вы нажали ctrl+c, вы желаете закрыть приложение?", ModalButtons: []types.ModalButton{ {Text: "Закрыть приложение", Value: true}, {Text: "Не закрывать", Value: false}, }, OnResult: func(result any) tea.Cmd { if result == true { return tea.Quit } return nil }, } } } case types.NextSectionMsg: m.CurrentSectionIdx++ if m.CurrentSectionIdx == len(m.CurrentApplication.Sections) { return m, tea.Quit } for sectionIdx := m.CurrentSectionIdx; sectionIdx < len(m.CurrentApplication.Sections); sectionIdx++ { //todo переделать. Учесть динамические индексы. section := m.CurrentApplication.Sections[sectionIdx] if CheckCondition(section.Condition, m.CurrentApplication) { m.CurrentSectionIdx = sectionIdx m.CurrentSection = §ion break } } if m.CurrentSection == nil { return m, func() tea.Msg { return types.ShowModalMsg{ Type: types.ModalError, Title: "Ошибка конфигурации", Message: "Это приложение не имеет доступных секций для настройки\n" + "Возможно оно повреждено или не поддерживается", ModalButtons: []types.ModalButton{{Text: "Выйти", Value: true}}, OnResult: func(result any) tea.Cmd { return tea.Quit }, } } } //todo переделать. Учесть динамические индексы. if m.CurrentSectionIdx == len(m.CurrentApplication.Sections)-1 && m.CurrentSection.Type == types.SectionFinal { m.current = NewFinalModel(m.CurrentApplication.Variables, m.Width, m.Height) } else { m.current = NewFormModel(m.CurrentApplication, m.CurrentSectionIdx, 0, m.Width, m.Height) } case types.SugarMsg: m.Sugar(msg) case types.SelectedAppMsg: //получаем сообщение о выборе приложения m.CurrentApplication = &msg.App //ищем доступную секцию for sectionIdx := 0; sectionIdx < len(m.CurrentApplication.Sections); sectionIdx++ { section := m.CurrentApplication.Sections[sectionIdx] if CheckCondition(section.Condition, m.CurrentApplication) { m.CurrentSectionIdx = sectionIdx m.CurrentSection = §ion break } } //если секция не была найдена, то выходим if m.CurrentSection == nil { logrus.Infof("select app, eq nil, out") return m, tea.Quit } //создаем новую форму m.current = NewFormModel(m.CurrentApplication, m.CurrentSectionIdx, 0, m.Width, m.Height) case types.BackToListMsg: m.CurrentApplication = nil m.CurrentSection = nil m.CurrentSectionIdx = 0 m.current = NewListApplicationsModel(m.Applications, m.Width, m.Height) case types.ShowModalMsg: m.overlay = NewModalModel(msg, m.Width, m.Height) return m, nil case types.CloseModalMsg: m.overlay = nil return m, tea.Batch(cmds...) } return m, tea.Batch(cmds...) } func (m *Main) View() string { view := m.current.View() if m.overlay != nil { view = m.overlay.View() } return view } func (m *Main) Sugar(sugar types.SugarMsg) { if m.IsMultiField(sugar.VariableName) { m.DeactivateField(sugar.VariableName, sugar.FieldName) } if IsParent(sugar.VariableName, m.CurrentApplication.Sections) { if sugar.Compute != "" { Recompute(m.CurrentApplication) } m.current = NewFormModel(m.CurrentApplication, m.CurrentSectionIdx, sugar.Focus, m.Width, m.Height) } } func IsParent(variableName string, sections []types.Section) bool { for _, section := range sections { for _, parent := range section.Parent { if parent == variableName { return true } } for _, field := range section.Fields { for _, parent := range field.Parent { if parent == variableName { return true } } } } return false } func (m *Main) IsMultiField(variableName string) bool { var count int for _, section := range m.CurrentApplication.Sections { for _, field := range section.Fields { if field.Name == variableName { count++ } } } return count > 1 } func (m *Main) DeactivateField(variableName, fieldName string) { for sectionIdx, section := range m.CurrentApplication.Sections { for fieldIdx, field := range section.Fields { if field.Variable.Name == variableName { if field.Name != fieldName { m.CurrentApplication.Sections[sectionIdx].Fields[fieldIdx].Active = false } else { m.CurrentApplication.Sections[sectionIdx].Fields[fieldIdx].Active = true } } } } } func (m *Main) RecomputeFields() { for i := range m.CurrentApplication.Sections { section := &m.CurrentApplication.Sections[i] for j := range section.Fields { field := §ion.Fields[j] if field.FieldType == "compute" && field.Compute != "" { if field.Variable != nil { continue } value, err := ComputeValue(field.Compute, m.CurrentApplication) if err != nil { value = fmt.Sprintf("Ошибка вычисления: %v", err) } field.Variable.Value = value } } } }