package main import ( "fmt" "strings" tea "github.com/charmbracelet/bubbletea" ) type mainTab int const ( tabDashboard mainTab = iota tabEditor ) var mainTabNames = []string{"Dashboard", "Editor"} type saveResultMsg struct { output string err error } type Model struct { tab mainTab dashboard DashboardModel editor EditorModel profile *Profile profilePath string dirty bool statusMsg string errMsg string width int height int quitting bool } func NewModel() (Model, tea.Cmd) { profilePath, _ := DefaultProfilePath() p, err := LoadProfile(profilePath) if err != nil { p = &Profile{} } checks := RunChecks() m := Model{ tab: tabDashboard, dashboard: NewDashboardModel(checks), editor: NewEditorModel(p), profile: p, profilePath: profilePath, } return m, nil } func (m Model) Init() tea.Cmd { return nil } func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { switch msg := msg.(type) { case tea.WindowSizeMsg: m.width = msg.Width m.height = msg.Height m.dashboard.width = msg.Width m.editor.width = msg.Width return m, nil case saveResultMsg: if msg.err != nil { m.errMsg = fmt.Sprintf("Save failed: %v", msg.err) } else { m.dirty = false m.statusMsg = "Saved & validated: " + msg.output m.errMsg = "" } return m, nil case tea.KeyMsg: switch msg.String() { case "ctrl+c", "q": if m.dirty { m.statusMsg = "Unsaved changes! Press Q to force quit, or s to save." return m, nil } m.quitting = true return m, tea.Quit case "Q": m.quitting = true return m, tea.Quit case "tab": if m.tab == tabDashboard { m.tab = tabEditor } else { m.tab = tabDashboard } m.statusMsg = "" m.errMsg = "" return m, nil case "s": if m.tab == tabEditor { m.editor.SyncToProfile(m.profile) cmd := saveAndValidateCmd(m.profilePath, m.profile) m.statusMsg = "Saving..." return m, cmd } } } switch m.tab { case tabDashboard: var cmd tea.Cmd m.dashboard, cmd = m.dashboard.Update(msg) return m, cmd case tabEditor: var cmd tea.Cmd m.editor, cmd = m.editor.Update(msg) if !m.dirty { m.dirty = true } return m, cmd } return m, nil } func (m Model) View() string { if m.quitting { return "" } var b strings.Builder title := "nono-tui" if m.dirty { title += " *" } b.WriteString(titleStyle.Render(" " + title + " ")) tabs := []string{} for i, name := range mainTabNames { if mainTab(i) == m.tab { tabs = append(tabs, activeTabStyle.Render(name)) } else { tabs = append(tabs, tabStyle.Render(name)) } } b.WriteString(" " + strings.Join(tabs, " ")) b.WriteString("\n\n") switch m.tab { case tabDashboard: b.WriteString(m.dashboard.View()) case tabEditor: b.WriteString(m.editor.View()) } b.WriteString("\n\n") if m.errMsg != "" { b.WriteString(errorStyle.Render(" " + m.errMsg)) } else if m.statusMsg != "" { b.WriteString(successStyle.Render(" " + m.statusMsg)) } else { b.WriteString(helpStyle.Render(" q:quit Tab:switch tabs")) } return b.String() }