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