repos / pico

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

pico / tui / settings
Antonio Mika · 08 Oct 24

settings.go

  1package settings
  2
  3import (
  4	"fmt"
  5	"time"
  6
  7	tea "github.com/charmbracelet/bubbletea"
  8	"github.com/charmbracelet/lipgloss"
  9	"github.com/charmbracelet/lipgloss/table"
 10	"github.com/picosh/pico/db"
 11	"github.com/picosh/pico/tui/common"
 12	"github.com/picosh/pico/tui/pages"
 13	"github.com/picosh/utils"
 14)
 15
 16var maxWidth = 50
 17
 18type state int
 19
 20const (
 21	stateLoading state = iota
 22	stateReady
 23)
 24
 25type focus int
 26
 27const (
 28	focusNone = iota
 29	focusAnalytics
 30)
 31
 32type featuresLoadedMsg []*db.FeatureFlag
 33
 34type Model struct {
 35	shared   common.SharedModel
 36	features []*db.FeatureFlag
 37	state    state
 38	focus    focus
 39}
 40
 41func NewModel(shrd common.SharedModel) Model {
 42	return Model{
 43		shared: shrd,
 44		state:  stateLoading,
 45		focus:  focusNone,
 46	}
 47}
 48
 49func (m Model) Init() tea.Cmd {
 50	return m.fetchFeatures()
 51}
 52
 53func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
 54	switch msg := msg.(type) {
 55	case tea.KeyMsg:
 56		switch msg.String() {
 57		case "q", "esc":
 58			return m, pages.Navigate(pages.MenuPage)
 59		case "tab":
 60			if m.focus == focusNone {
 61				m.focus = focusAnalytics
 62			} else {
 63				m.focus = focusNone
 64			}
 65		case "enter":
 66			if m.focus == focusAnalytics {
 67				return m, m.toggleAnalytics()
 68			}
 69		}
 70
 71	case featuresLoadedMsg:
 72		m.state = stateReady
 73		m.focus = focusNone
 74		m.features = msg
 75	}
 76	return m, nil
 77}
 78
 79func (m Model) View() string {
 80	if m.state == stateLoading {
 81		return "Loading ..."
 82	}
 83	return m.featuresView() + "\n" + m.analyticsView()
 84}
 85
 86func (m Model) findAnalyticsFeature() *db.FeatureFlag {
 87	for _, feature := range m.features {
 88		if feature.Name == "analytics" {
 89			return feature
 90		}
 91	}
 92	return nil
 93}
 94
 95func (m Model) analyticsView() string {
 96	banner := `Get usage statistics on your blog, blog posts, and pages sites. For example, see unique visitors, most popular URLs, and top referers.
 97
 98We do not collect usage statistic unless analytics is enabled. Further, when analytics are disabled we do not purge usage statistics.
 99
100We will only store usage statistics for 1 year from when the event was created.`
101
102	str := ""
103	hasPlus := m.shared.PlusFeatureFlag != nil
104	if hasPlus {
105		ff := m.findAnalyticsFeature()
106		hasFocus := m.focus == focusAnalytics
107		if ff == nil {
108			str += banner + "\n\nEnable analytics " + common.OKButtonView(m.shared.Styles, hasFocus, false)
109		} else {
110			str += "Disable analytics " + common.OKButtonView(m.shared.Styles, hasFocus, false)
111		}
112	} else {
113		str += banner + "\n\n" + m.shared.Styles.Error.SetString("Analytics is only available to pico+ users.").String()
114	}
115
116	return m.shared.Styles.RoundedBorder.Width(maxWidth).SetString(str).String()
117}
118
119func (m Model) featuresView() string {
120	headers := []string{
121		"Name",
122		"Quota (GB)",
123		"Expires At",
124	}
125
126	data := [][]string{}
127	for _, feature := range m.features {
128		storeMax := utils.BytesToGB(int(feature.FindStorageMax(0)))
129		row := []string{
130			feature.Name,
131			fmt.Sprintf("%.2f", storeMax),
132			feature.ExpiresAt.Format(common.DateFormat),
133		}
134		data = append(data, row)
135	}
136	t := table.New().
137		Border(lipgloss.RoundedBorder()).
138		BorderStyle(m.shared.Styles.Renderer.NewStyle().BorderForeground(common.Indigo)).
139		Width(maxWidth).
140		Headers(headers...).
141		Rows(data...)
142	return "Features\n" + t.String()
143}
144
145func (m Model) fetchFeatures() tea.Cmd {
146	return func() tea.Msg {
147		features, err := m.shared.Dbpool.FindFeaturesForUser(m.shared.User.ID)
148		if err != nil {
149			return common.ErrMsg{Err: err}
150		}
151		return featuresLoadedMsg(features)
152	}
153}
154
155func (m Model) toggleAnalytics() tea.Cmd {
156	return func() tea.Msg {
157		if m.findAnalyticsFeature() == nil {
158			now := time.Now()
159			expiresAt := now.AddDate(100, 0, 0)
160			_, err := m.shared.Dbpool.InsertFeature(m.shared.User.ID, "analytics", expiresAt)
161			if err != nil {
162				return common.ErrMsg{Err: err}
163			}
164		} else {
165			err := m.shared.Dbpool.RemoveFeature(m.shared.User.ID, "analytics")
166			if err != nil {
167				return common.ErrMsg{Err: err}
168			}
169		}
170
171		cmd := m.fetchFeatures()
172		return cmd()
173	}
174}