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