- commit
- 428b94c
- parent
- 2b9a0ab
- author
- Eric Bower
- date
- 2024-05-13 17:42:27 +0000 UTC
chore: cleanup
3 files changed,
+167,
-6
+13,
-0
1@@ -10,6 +10,7 @@ import (
2 "github.com/picosh/pico/db"
3 "github.com/picosh/pico/shared"
4 "github.com/picosh/pico/tui/common"
5+ "github.com/picosh/pico/tui/notifications"
6 "github.com/picosh/pico/tui/plus"
7 "github.com/picosh/send/send/utils"
8 )
9@@ -56,6 +57,12 @@ func (c *Cmd) plus() {
10 c.output(view)
11 }
12
13+func (c *Cmd) notifications() error {
14+ md := notifications.NotificationsView(c.Dbpool, c.User.ID)
15+ c.output(md)
16+ return nil
17+}
18+
19 type CliHandler struct {
20 DBPool db.DB
21 Logger *slog.Logger
22@@ -120,6 +127,12 @@ func WishMiddleware(handler *CliHandler) wish.Middleware {
23 } else if cmd == "pico+" {
24 opts.plus()
25 return
26+ } else if cmd == "notifications" {
27+ err := opts.notifications()
28+ if err != nil {
29+ wish.Fatalln(sesh, err)
30+ }
31+ return
32 } else {
33 next(sesh)
34 return
+28,
-6
1@@ -20,6 +20,7 @@ import (
2 "github.com/picosh/pico/tui/common"
3 "github.com/picosh/pico/tui/info"
4 "github.com/picosh/pico/tui/keys"
5+ "github.com/picosh/pico/tui/notifications"
6 "github.com/picosh/pico/tui/plus"
7 "github.com/picosh/pico/tui/tokens"
8 )
9@@ -33,6 +34,7 @@ const (
10 statusBrowsingKeys
11 statusBrowsingTokens
12 statusBrowsingPlus
13+ statusBrowsingNotifications
14 statusChat
15 statusQuitting
16 )
17@@ -55,6 +57,7 @@ const (
18 keysChoice menuChoice = iota
19 tokensChoice
20 plusChoice
21+ notificationsChoice
22 chatChoice
23 exitChoice
24 unsetChoice // set when no choice has been made
25@@ -62,11 +65,12 @@ const (
26
27 // menu text corresponding to menu choices. these are presented to the user.
28 var menuChoices = map[menuChoice]string{
29- keysChoice: "manage keys",
30- tokensChoice: "manage tokens",
31- plusChoice: "pico+",
32- chatChoice: "chat",
33- exitChoice: "exit",
34+ keysChoice: "Manage keys",
35+ tokensChoice: "Manage tokens",
36+ plusChoice: "Pico+",
37+ notificationsChoice: "Notifications",
38+ chatChoice: "Chat",
39+ exitChoice: "Exit",
40 }
41
42 func NewSpinner(styles common.Styles) spinner.Model {
43@@ -142,6 +146,7 @@ type model struct {
44 keys keys.Model
45 tokens tokens.Model
46 plus plus.Model
47+ notifications notifications.Model
48 createAccount account.CreateModel
49 terminalSize tea.WindowSizeMsg
50 session ssh.Session
51@@ -243,6 +248,7 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
52 m.tokens = tokens.NewModel(m.styles, m.dbpool, m.user)
53 m.createAccount = account.NewCreateModel(m.styles, m.dbpool, m.publicKey)
54 m.plus = plus.NewModel(m.styles, m.user, m.session)
55+ m.notifications = notifications.NewModel(m.styles, m.dbpool, m.user, m.session)
56 }
57
58 switch m.status {
59@@ -252,6 +258,7 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
60 m.tokens = tokens.NewModel(m.styles, m.dbpool, m.user)
61 m.createAccount = account.NewCreateModel(m.styles, m.dbpool, m.publicKey)
62 m.plus = plus.NewModel(m.styles, m.user, m.session)
63+ m.notifications = notifications.NewModel(m.styles, m.dbpool, m.user, m.session)
64 if m.user == nil {
65 m.status = statusNoAccount
66 } else {
67@@ -308,7 +315,16 @@ func updateChildren(msg tea.Msg, m model) (model, tea.Cmd) {
68 if m.plus.Done {
69 m.plus = plus.NewModel(m.styles, m.user, m.session)
70 m.status = statusReady
71- } else if m.tokens.Quit {
72+ } else if m.plus.Quit {
73+ m.status = statusQuitting
74+ return m, tea.Quit
75+ }
76+ case statusBrowsingNotifications:
77+ m.notifications, cmd = notifications.Update(msg, m.notifications)
78+ if m.notifications.Done {
79+ m.notifications = notifications.NewModel(m.styles, m.dbpool, m.user, m.session)
80+ m.status = statusReady
81+ } else if m.notifications.Quit {
82 m.status = statusQuitting
83 return m, tea.Quit
84 }
85@@ -336,6 +352,9 @@ func updateChildren(msg tea.Msg, m model) (model, tea.Cmd) {
86 case plusChoice:
87 m.status = statusBrowsingPlus
88 m.menuChoice = unsetChoice
89+ case notificationsChoice:
90+ m.status = statusBrowsingNotifications
91+ m.menuChoice = unsetChoice
92 case chatChoice:
93 m.status = statusChat
94 m.menuChoice = unsetChoice
95@@ -435,6 +454,9 @@ func (m model) View() string {
96 case statusBrowsingPlus:
97 s = plus.View(m.plus)
98 return s
99+ case statusBrowsingNotifications:
100+ s = notifications.View(m.notifications)
101+ return s
102 }
103 return m.styles.App.Render(wrap.String(wordwrap.String(s, w), w))
104 }
+126,
-0
1@@ -0,0 +1,126 @@
2+package notifications
3+
4+import (
5+ "fmt"
6+
7+ "github.com/charmbracelet/bubbles/viewport"
8+ tea "github.com/charmbracelet/bubbletea"
9+ "github.com/charmbracelet/glamour"
10+ "github.com/charmbracelet/ssh"
11+ "github.com/picosh/pico/db"
12+ "github.com/picosh/pico/tui/common"
13+)
14+
15+func NotificationsView(dbpool db.DB, userID string) string {
16+ pass, err := dbpool.UpsertToken(userID, "pico-rss")
17+ if err != nil {
18+ return err.Error()
19+ }
20+ url := fmt.Sprintf("https://auth.pico.sh/rss/%s", pass)
21+ md := fmt.Sprintf(`# Notifications
22+
23+We provide a special RSS feed for all pico users where we can send
24+user-specific notifications. This is where we will send pico+
25+expiration notices, among other alerts. To be clear, this is
26+optional but **highly** recommended.
27+
28+Add this URL to your RSS feed reader:
29+
30+%s
31+
32+## Using our [rss-to-email](https://pico.sh/feeds) service
33+
34+Create a feeds file (e.g. pico.txt):`, url)
35+
36+ md += "\n```\n"
37+ md += fmt.Sprintf(`=: email rss@myemail.com
38+=: digest_interval 1day
39+=> %s
40+`, url)
41+ md += "\n```\n"
42+ md += "Then copy the file to us:\n"
43+ md += "```\nrsync pico.txt feeds.pico.sh:/\n```"
44+
45+ r, _ := glamour.NewTermRenderer(
46+ // detect background color and pick either the default dark or light theme
47+ glamour.WithAutoStyle(),
48+ )
49+ out, err := r.Render(md)
50+ if err != nil {
51+ return err.Error()
52+ }
53+ return out
54+}
55+
56+// Model holds the state of the username UI.
57+type Model struct {
58+ Done bool // true when it's time to exit this view
59+ Quit bool // true when the user wants to quit the whole program
60+
61+ styles common.Styles
62+ user *db.User
63+ viewport viewport.Model
64+}
65+
66+func headerHeight(styles common.Styles) int {
67+ return 0
68+}
69+
70+func headerWidth(w int) int {
71+ return w - 2
72+}
73+
74+// NewModel returns a new username model in its initial state.
75+func NewModel(styles common.Styles, dbpool db.DB, user *db.User, session ssh.Session) Model {
76+ pty, _, _ := session.Pty()
77+ hh := headerHeight(styles)
78+ viewport := viewport.New(headerWidth(pty.Window.Width), pty.Window.Height-hh)
79+ viewport.YPosition = hh
80+ viewport.SetContent(NotificationsView(dbpool, user.ID))
81+
82+ return Model{
83+ Done: false,
84+ Quit: false,
85+ styles: styles,
86+ user: user,
87+ viewport: viewport,
88+ }
89+}
90+
91+// Update is the Bubble Tea update loop.
92+func Update(msg tea.Msg, m Model) (Model, tea.Cmd) {
93+ var cmd tea.Cmd
94+ var cmds []tea.Cmd
95+
96+ switch msg := msg.(type) {
97+ case tea.KeyMsg:
98+ switch msg.Type {
99+ case tea.KeyCtrlC:
100+ m.Quit = true
101+ case tea.KeyEscape:
102+ m.Done = true
103+
104+ default:
105+ switch msg.String() {
106+ case "q":
107+ m.Done = true
108+ }
109+ }
110+
111+ case tea.WindowSizeMsg:
112+ m.viewport.Width = headerWidth(msg.Width)
113+ hh := headerHeight(m.styles)
114+ m.viewport.Height = msg.Height - hh
115+ }
116+
117+ m.viewport, cmd = m.viewport.Update(msg)
118+ cmds = append(cmds, cmd)
119+
120+ return m, tea.Batch(cmds...)
121+}
122+
123+// View renders current view from the model.
124+func View(m Model) string {
125+ s := m.viewport.View()
126+ return s
127+}