ui.go

  1package ui
  2
  3import (
  4	"strings"
  5
  6	"github.com/charmbracelet/bubbles/key"
  7	tea "github.com/charmbracelet/bubbletea"
  8	"github.com/charmbracelet/lipgloss"
  9	"github.com/charmbracelet/soft-serve/ui/common"
 10	"github.com/charmbracelet/soft-serve/ui/components/footer"
 11	"github.com/charmbracelet/soft-serve/ui/components/header"
 12	"github.com/charmbracelet/soft-serve/ui/pages/selection"
 13	"github.com/charmbracelet/soft-serve/ui/session"
 14)
 15
 16type sessionState int
 17
 18const (
 19	startState sessionState = iota
 20	errorState
 21	loadedState
 22)
 23
 24type UI struct {
 25	s          session.Session
 26	common     common.Common
 27	pages      []common.Page
 28	activePage int
 29	state      sessionState
 30	header     *header.Header
 31	footer     *footer.Footer
 32	error      error
 33}
 34
 35func New(s session.Session, c common.Common, initialRepo string) *UI {
 36	h := header.New(c, s.Config().Name)
 37	ui := &UI{
 38		s:          s,
 39		common:     c,
 40		pages:      make([]common.Page, 2), // selection & repo
 41		activePage: 0,
 42		state:      startState,
 43		header:     h,
 44	}
 45	ui.footer = footer.New(c, ui)
 46	ui.SetSize(c.Width, c.Height)
 47	return ui
 48}
 49
 50func (ui *UI) getMargins() (wm, hm int) {
 51	wm = ui.common.Styles.App.GetHorizontalFrameSize()
 52	hm = ui.common.Styles.App.GetVerticalFrameSize() +
 53		ui.common.Styles.Header.GetHeight() +
 54		ui.common.Styles.Footer.GetHeight()
 55	return
 56}
 57
 58func (ui *UI) ShortHelp() []key.Binding {
 59	b := make([]key.Binding, 0)
 60	b = append(b, ui.pages[ui.activePage].ShortHelp()...)
 61	b = append(b, ui.common.Keymap.Quit)
 62	return b
 63}
 64
 65func (ui *UI) FullHelp() [][]key.Binding {
 66	b := make([][]key.Binding, 0)
 67	b = append(b, ui.pages[ui.activePage].FullHelp()...)
 68	b = append(b, []key.Binding{ui.common.Keymap.Quit})
 69	return b
 70}
 71
 72func (ui *UI) SetSize(width, height int) {
 73	ui.common.SetSize(width, height)
 74	wm, hm := ui.getMargins()
 75	ui.header.SetSize(width-wm, height-hm)
 76	ui.footer.SetSize(width-wm, height-hm)
 77	for _, p := range ui.pages {
 78		if p != nil {
 79			p.SetSize(width-wm, height-hm)
 80		}
 81	}
 82}
 83
 84func (ui *UI) Init() tea.Cmd {
 85	ui.pages[0] = selection.New(ui.s, ui.common)
 86	ui.pages[1] = selection.New(ui.s, ui.common)
 87	ui.SetSize(ui.common.Width, ui.common.Height)
 88	ui.state = loadedState
 89	return ui.pages[ui.activePage].Init()
 90}
 91
 92// TODO update help when page change.
 93func (ui *UI) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
 94	cmds := make([]tea.Cmd, 0)
 95	switch msg := msg.(type) {
 96	case tea.WindowSizeMsg:
 97		h, cmd := ui.header.Update(msg)
 98		ui.header = h.(*header.Header)
 99		if cmd != nil {
100			cmds = append(cmds, cmd)
101		}
102		f, cmd := ui.footer.Update(msg)
103		ui.footer = f.(*footer.Footer)
104		if cmd != nil {
105			cmds = append(cmds, cmd)
106		}
107		for i, p := range ui.pages {
108			m, cmd := p.Update(msg)
109			ui.pages[i] = m.(common.Page)
110			if cmd != nil {
111				cmds = append(cmds, cmd)
112			}
113		}
114		ui.SetSize(msg.Width, msg.Height)
115	case tea.KeyMsg:
116		switch {
117		case key.Matches(msg, ui.common.Keymap.Quit):
118			return ui, tea.Quit
119		}
120	case common.ErrorMsg:
121		ui.error = msg
122		ui.state = errorState
123		return ui, nil
124	}
125	m, cmd := ui.pages[ui.activePage].Update(msg)
126	ui.pages[ui.activePage] = m.(common.Page)
127	if cmd != nil {
128		cmds = append(cmds, cmd)
129	}
130	return ui, tea.Batch(cmds...)
131}
132
133func (ui *UI) View() string {
134	s := strings.Builder{}
135	switch ui.state {
136	case startState:
137		return "\n Loading..."
138	case errorState:
139		err := ui.common.Styles.ErrorTitle.Render("Bummer")
140		err += ui.common.Styles.ErrorBody.Render(ui.error.Error())
141		return err
142	case loadedState:
143		s.WriteString(lipgloss.JoinVertical(
144			lipgloss.Bottom,
145			ui.header.View(),
146			ui.pages[ui.activePage].View(),
147			ui.footer.View(),
148		))
149	default:
150		return "\n Unknown state :/ this is a bug!"
151	}
152	return ui.common.Styles.App.Render(s.String())
153}