package tui import ( tea "github.com/charmbracelet/bubbletea" "github.com/lerko/nib/internal/db" ) type viewState int const ( stateList viewState = iota stateDetail stateInput ) type model struct { store *db.Store state viewState width int height int list listModel detail detailModel input inputModel status string err error } func newModel(store *db.Store) model { return model{ store: store, state: stateList, list: newListModel(), detail: newDetailModel(), input: newInputModel(), } } func (m model) Init() tea.Cmd { return loadEntities(m.store, db.DefaultListParams()) } 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.list.setSize(m.width, m.contentHeight()) m.detail.setSize(m.width, m.contentHeight()) return m, nil case entitiesLoadedMsg: m.list.setEntities(msg.entities) m.status = "" m.err = nil return m, nil case entityCreatedMsg: m.state = stateList m.input.reset() m.status = "created" return m, loadEntities(m.store, db.DefaultListParams()) case entityDeletedMsg: m.status = "deleted" return m, loadEntities(m.store, db.DefaultListParams()) case errMsg: m.err = msg.err return m, nil case tea.KeyMsg: if m.state == stateInput { return m.updateInput(msg) } return m.updateKeys(msg) } return m, nil } func (m model) updateKeys(msg tea.KeyMsg) (tea.Model, tea.Cmd) { switch { case msg.String() == "q" || msg.String() == "ctrl+c": return m, tea.Quit case msg.String() == "a" && m.state == stateList: m.state = stateInput m.input.focus() return m, m.input.ti.Focus() case msg.String() == "esc": if m.state == stateDetail { m.state = stateList } return m, nil case msg.String() == "enter" && m.state == stateList: if e := m.list.selected(); e != nil { m.detail.setEntity(e) m.state = stateDetail } return m, nil case msg.String() == "d" && m.state == stateList: if e := m.list.selected(); e != nil { return m, deleteEntity(m.store, e.ID) } return m, nil } switch m.state { case stateList: m.list = m.list.update(msg) case stateDetail: m.detail = m.detail.update(msg) } return m, nil } func (m model) updateInput(msg tea.KeyMsg) (tea.Model, tea.Cmd) { switch msg.String() { case "esc": m.state = stateList m.input.reset() return m, nil case "enter": if e := m.input.submit(); e != nil { return m, createEntity(m.store, e) } return m, nil } m.input = m.input.updateKey(msg) return m, nil } func (m model) View() string { var content string switch m.state { case stateList: content = m.list.view(m.width) case stateDetail: content = m.detail.view(m.width) case stateInput: content = m.list.view(m.width) } header := titleStyle.Render("nib") footer := m.footerView() return header + "\n" + content + "\n" + footer } func (m model) footerView() string { if m.state == stateInput { return m.input.view(m.width) } if m.err != nil { return errorStyle.Render("error: " + m.err.Error()) } if m.status != "" { return statusStyle.Render(m.status) } return helpStyle.Render("a:add enter:view d:delete q:quit ?:help") } func (m model) contentHeight() int { return m.height - 3 }