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}