repos / pico

pico services - prose.sh, pastes.sh, imgs.sh, feeds.sh, pgs.sh
git clone https://github.com/picosh/pico.git

pico / tui
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}