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}