package main import ( "time" "charm.land/bubbles/v2/help" "charm.land/bubbles/v2/key" "charm.land/bubbles/v2/textinput" tea "charm.land/bubbletea/v2" ) type model struct { err error width int height int amount int keys keyMap services []Service filtered []Service query textinput.Model cursor int offset int help help.Model system System detail Detail } func initModel() model { query := textinput.New() query.Placeholder = "Search..." return model{ keys: keys, query: query, help: help.New(), } } func (m model) Init() tea.Cmd { return tea.Batch(LoadSystem, m.TickDetails()) } type DetailTickMsg struct{} func (m *model) TickDetails() tea.Cmd { return tea.Tick(1*time.Second, func(time.Time) tea.Msg { return DetailTickMsg{} }) } func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { var cmd tea.Cmd switch msg := msg.(type) { case tea.WindowSizeMsg: m.query.SetWidth(msg.Width) m.help.SetWidth(msg.Width) m.width = msg.Width m.height = msg.Height m.calcAmount() return m, nil case ServicesLoadedMsg: m.services = msg.services m.err = msg.err m.filterServices() return m, m.LoadDetail case SystemLoadedMsg: m.system = msg.System m.err = msg.Err return m, LoadServices case DetailLoadedMsg: m.detail = msg.Detail m.err = msg.Err for si, s := range m.services { if s.Name == msg.Detail.Name && s.Active != msg.Detail.Active { m.services[si].Active = msg.Detail.Active for fi, f := range m.filtered { if f.Name == msg.Detail.Name { m.filtered[fi].Active = msg.Detail.Active } } } } return m, nil case DetailTickMsg: return m, tea.Batch(m.LoadDetail, m.TickDetails()) case tea.KeyPressMsg: if m.query.Focused() { switch { case key.Matches(msg, m.keys.Back), key.Matches(msg, m.keys.Enter): m.query.Blur() case key.Matches(msg, m.keys.Help): m.help.ShowAll = !m.help.ShowAll } m.query, cmd = m.query.Update(msg) m.filterServices() return m, tea.Batch(cmd, m.LoadDetail) } switch { case key.Matches(msg, m.keys.Up): if m.cursor > 0 { m.cursor-- if m.cursor < m.offset { m.offset-- } } return m, m.LoadDetail case key.Matches(msg, m.keys.Down): if m.cursor < len(m.filtered)-1 { m.cursor++ if m.cursor >= m.offset+m.amount { m.offset++ } } return m, m.LoadDetail case key.Matches(msg, m.keys.Search): m.query.Focus() case key.Matches(msg, m.keys.Back): m.query.Blur() case key.Matches(msg, m.keys.Enter): s := m.filtered[m.cursor] if s.Active != "active" { m.StartService() } else { m.StopService() } case key.Matches(msg, m.keys.Reload): return m, LoadServices case key.Matches(msg, m.keys.Help): m.help.ShowAll = !m.help.ShowAll m.calcAmount() case key.Matches(msg, m.keys.Quit): return m, tea.Quit } } return m, cmd } func (m *model) calcAmount() { m.amount = (m.height - 2 - If(m.help.ShowAll, 2, 1)) / 3 }