repos / pico

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

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