Eric Bower
·
05 Oct 24
ui.go
1package tui
2
3import (
4 "fmt"
5
6 tea "github.com/charmbracelet/bubbletea"
7 "github.com/charmbracelet/lipgloss"
8 "github.com/charmbracelet/wish"
9 "github.com/muesli/reflow/wordwrap"
10 "github.com/muesli/reflow/wrap"
11 "github.com/picosh/pico/tui/common"
12 "github.com/picosh/pico/tui/createaccount"
13 "github.com/picosh/pico/tui/createkey"
14 "github.com/picosh/pico/tui/createtoken"
15 "github.com/picosh/pico/tui/logs"
16 "github.com/picosh/pico/tui/menu"
17 "github.com/picosh/pico/tui/notifications"
18 "github.com/picosh/pico/tui/pages"
19 "github.com/picosh/pico/tui/plus"
20 "github.com/picosh/pico/tui/pubkeys"
21 "github.com/picosh/pico/tui/settings"
22 "github.com/picosh/pico/tui/tokens"
23)
24
25type state int
26
27const (
28 initState state = iota
29 readyState
30)
31
32// Just a generic tea.Model to demo terminal information of ssh.
33type UI struct {
34 shared common.SharedModel
35
36 state state
37 activePage pages.Page
38 pages []tea.Model
39}
40
41func NewUI(shared common.SharedModel) *UI {
42 m := &UI{
43 shared: shared,
44 state: initState,
45 pages: make([]tea.Model, 10),
46 }
47 return m
48}
49
50func (m *UI) updateActivePage(msg tea.Msg) tea.Cmd {
51 nm, cmd := m.pages[m.activePage].Update(msg)
52 m.pages[m.activePage] = nm
53 return cmd
54}
55
56func (m *UI) Init() tea.Cmd {
57 // header height is required to calculate viewport for
58 // some pages
59 m.shared.HeaderHeight = lipgloss.Height(m.header()) + 1
60 user, err := findUser(m.shared)
61 if err != nil {
62 wish.Errorln(m.shared.Session, err)
63 return tea.Quit
64 }
65 m.shared.User = user
66
67 ff, _ := findPlusFeatureFlag(m.shared)
68 m.shared.PlusFeatureFlag = ff
69
70 m.pages[pages.MenuPage] = menu.NewModel(m.shared)
71 m.pages[pages.CreateAccountPage] = createaccount.NewModel(m.shared)
72 m.pages[pages.CreatePubkeyPage] = createkey.NewModel(m.shared)
73 m.pages[pages.CreateTokenPage] = createtoken.NewModel(m.shared)
74 m.pages[pages.CreateAccountPage] = createaccount.NewModel(m.shared)
75 m.pages[pages.PubkeysPage] = pubkeys.NewModel(m.shared)
76 m.pages[pages.TokensPage] = tokens.NewModel(m.shared)
77 m.pages[pages.NotificationsPage] = notifications.NewModel(m.shared)
78 m.pages[pages.PlusPage] = plus.NewModel(m.shared)
79 m.pages[pages.SettingsPage] = settings.NewModel(m.shared)
80 m.pages[pages.LogsPage] = logs.NewModel(m.shared)
81 if m.shared.User == nil {
82 m.activePage = pages.CreateAccountPage
83 } else {
84 m.activePage = pages.MenuPage
85 }
86 m.state = readyState
87 return nil
88}
89
90func (m *UI) updateModels(msg tea.Msg) tea.Cmd {
91 cmds := []tea.Cmd{}
92 for i, page := range m.pages {
93 if page == nil {
94 continue
95 }
96 nm, cmd := page.Update(msg)
97 m.pages[i] = nm
98 cmds = append(cmds, cmd)
99 }
100 return tea.Batch(cmds...)
101}
102
103func (m *UI) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
104 var cmds []tea.Cmd
105
106 switch msg := msg.(type) {
107 case tea.WindowSizeMsg:
108 m.shared.Width = msg.Width
109 m.shared.Height = msg.Height
110 return m, m.updateModels(msg)
111
112 case tea.KeyMsg:
113 switch msg.Type {
114 case tea.KeyCtrlC:
115 m.shared.Dbpool.Close()
116 return m, tea.Quit
117 }
118
119 case pages.NavigateMsg:
120 // send message to the active page so it can teardown
121 // and reset itself
122 cmds = append(cmds, m.updateActivePage(msg))
123 m.activePage = msg.Page
124
125 // user created account
126 case createaccount.CreateAccountMsg:
127 // reset model and pages
128 return m, m.Init()
129
130 case menu.MenuChoiceMsg:
131 switch msg.MenuChoice {
132 case menu.KeysChoice:
133 m.activePage = pages.PubkeysPage
134 case menu.TokensChoice:
135 m.activePage = pages.TokensPage
136 case menu.NotificationsChoice:
137 m.activePage = pages.NotificationsPage
138 case menu.PlusChoice:
139 m.activePage = pages.PlusPage
140 case menu.SettingsChoice:
141 m.activePage = pages.SettingsPage
142 case menu.LogsChoice:
143 m.activePage = pages.LogsPage
144 case menu.ChatChoice:
145 return m, LoadChat(m.shared)
146 case menu.ExitChoice:
147 m.shared.Dbpool.Close()
148 return m, tea.Quit
149 }
150
151 cmds = append(cmds, m.pages[m.activePage].Init())
152 }
153
154 cmd := m.updateActivePage(msg)
155 cmds = append(cmds, cmd)
156
157 return m, tea.Batch(cmds...)
158}
159
160func (m *UI) header() string {
161 logoTxt := "pico.sh"
162 ff := m.shared.PlusFeatureFlag
163 if ff != nil && ff.IsValid() {
164 logoTxt = "pico+"
165 }
166
167 logo := m.shared.
168 Styles.
169 Logo.
170 SetString(logoTxt)
171 title := m.shared.
172 Styles.
173 Note.
174 SetString(pages.ToTitle(m.activePage))
175 div := m.shared.
176 Styles.
177 HelpDivider.
178 Foreground(common.Green)
179 s := fmt.Sprintf("%s%s%s\n\n", logo, div, title)
180 return s
181}
182
183func (m *UI) View() string {
184 s := m.header()
185
186 if m.pages[m.activePage] != nil {
187 s += m.pages[m.activePage].View()
188 }
189
190 width := m.shared.Width - m.shared.Styles.App.GetHorizontalFrameSize()
191 maxWidth := width
192 str := wrap.String(
193 wordwrap.String(s, maxWidth),
194 maxWidth,
195 )
196 return m.shared.Styles.App.Render(str)
197}