Antonio Mika
·
2026-10-10
1package tui
2
3import (
4 "fmt"
5 "time"
6
7 "github.com/picosh/pico/pkg/db"
8 "go.rockorager.dev/vaxis"
9 "go.rockorager.dev/vaxis/vxfw"
10)
11
12type UsageInfo struct {
13 stats *db.UserServiceStats
14 Label string
15}
16
17func NewUsageInfo(label string, stats *db.UserServiceStats) *UsageInfo {
18 return &UsageInfo{Label: label, stats: stats}
19}
20
21func (m *UsageInfo) Draw(ctx vxfw.DrawContext) (vxfw.Surface, error) {
22 info := NewKv(m.getKv())
23 brd := NewBorder(info)
24 brd.Label = m.Label
25 brd.Style = vaxis.Style{Foreground: purp}
26 return brd.Draw(vxfw.DrawContext{
27 Characters: ctx.Characters,
28 Max: vxfw.Size{
29 Width: 30,
30 Height: 4 + 3,
31 },
32 })
33}
34
35func (m *UsageInfo) getKv() []Kv {
36 label := "posts"
37 if m.Label == "pages" {
38 label = "sites"
39 }
40 kv := []Kv{
41 {Key: label, Value: fmt.Sprintf("%d", m.stats.Num)},
42 {Key: "oldest", Value: formatUsageDate(m.stats.FirstCreatedAt)},
43 {Key: "newest", Value: formatUsageDate(m.stats.LastestCreatedAt)},
44 }
45 return kv
46}
47
48func formatUsageDate(t time.Time) string {
49 if t.IsZero() {
50 return "-"
51 }
52 return t.Format(time.DateOnly)
53}
54
55type UserInfo struct {
56 shared *SharedModel
57}
58
59func NewUserInfo(shrd *SharedModel) *UserInfo {
60 return &UserInfo{shrd}
61}
62
63func (m *UserInfo) Draw(ctx vxfw.DrawContext) (vxfw.Surface, error) {
64 features := NewKv(m.getKv())
65 brd := NewBorder(features)
66 brd.Label = "info"
67 brd.Style = vaxis.Style{Foreground: purp}
68 h := 1
69 if m.shared.PlusFeatureFlag != nil {
70 h += 1
71 }
72 if m.shared.InvitedBy != "" {
73 h += 1
74 }
75
76 return brd.Draw(vxfw.DrawContext{
77 Characters: ctx.Characters,
78 Max: vxfw.Size{
79 Width: 30,
80 Height: uint16(h) + 3,
81 },
82 })
83}
84
85func (m *UserInfo) getKv() []Kv {
86 createdAt := m.shared.User.CreatedAt.Format(time.DateOnly)
87 kv := []Kv{
88 {Key: "joined", Value: createdAt},
89 }
90
91 if m.shared.PlusFeatureFlag != nil {
92 expiresAt := m.shared.PlusFeatureFlag.ExpiresAt.Format(time.DateOnly)
93 kv = append(kv, Kv{Key: "pico+ expires", Value: expiresAt})
94 }
95
96 if m.shared.InvitedBy != "" {
97 kv = append(kv, Kv{Key: "invited by", Value: m.shared.InvitedBy})
98 }
99
100 return kv
101}
102
103type FeaturesList struct {
104 shared *SharedModel
105 features []*db.FeatureFlag
106 err error
107}
108
109func NewFeaturesList(shrd *SharedModel) *FeaturesList {
110 return &FeaturesList{shared: shrd}
111}
112
113func (m *FeaturesList) HandleEvent(ev vaxis.Event, phase vxfw.EventPhase) (vxfw.Command, error) {
114 switch ev.(type) {
115 case vxfw.Init:
116 m.err = m.fetchFeatures()
117 return vxfw.RedrawCmd{}, nil
118 }
119 return nil, nil
120}
121
122func (m *FeaturesList) Draw(ctx vxfw.DrawContext) (vxfw.Surface, error) {
123 features := NewKv(m.getFeaturesKv())
124 brd := NewBorder(features)
125 brd.Label = "features"
126 brd.Style = vaxis.Style{Foreground: purp}
127 return brd.Draw(vxfw.DrawContext{
128 Characters: ctx.Characters,
129 Max: vxfw.Size{
130 Width: 30,
131 Height: uint16(len(m.features)) + 4,
132 },
133 })
134}
135
136func (m *FeaturesList) fetchFeatures() error {
137 features, err := m.shared.Dbpool.FindFeaturesByUser(m.shared.User.ID)
138 m.features = features
139 return err
140}
141
142func (m *FeaturesList) getFeaturesKv() []Kv {
143 kv := []Kv{
144 {
145 Key: "name",
146 Value: "expires at",
147 Style: vaxis.Style{
148 UnderlineColor: purp,
149 UnderlineStyle: vaxis.UnderlineDashed,
150 Foreground: purp,
151 },
152 },
153 }
154
155 for _, feature := range m.features {
156 kv = append(kv, Kv{Key: feature.Name, Value: feature.ExpiresAt.Format(time.DateOnly)})
157 }
158
159 return kv
160}
161
162type ServicesList struct {
163 shared *SharedModel
164 data [][]string
165}
166
167func NewServicesList(shrd *SharedModel) *ServicesList {
168 return &ServicesList{shared: shrd}
169}
170
171func (m *ServicesList) HandleEvent(ev vaxis.Event, phase vxfw.EventPhase) (vxfw.Command, error) {
172 switch ev.(type) {
173 case vxfw.Init:
174 m.fetchServices()
175 return vxfw.RedrawCmd{}, nil
176 }
177 return nil, nil
178}
179
180func (m *ServicesList) fetchServices() {
181 dbpool := m.shared.Dbpool
182 userID := m.shared.User.ID
183 status := func(space, missing string) string {
184 if db.HasPlusOrSpace(dbpool, userID, space) {
185 return "active"
186 }
187 return missing
188 }
189 m.data = [][]string{
190 {"name", "status"},
191 {"pipe", "active"},
192 {"pastes", status("pastes", "invite/pico+")},
193 {"prose", status("prose", "invite/pico+")},
194 {"pages", status("pgs", "invite/pico+")},
195 {"tuns", status("tuns", "pico+")},
196 {"irc bouncer", status("bouncer", "pico+")},
197 {"feeds", status("feeds", "pico+")},
198 }
199}
200
201func (m *ServicesList) Draw(ctx vxfw.DrawContext) (vxfw.Surface, error) {
202 services := NewKv(m.getServiceKv())
203 brd := NewBorder(services)
204 brd.Label = "services"
205 brd.Style = vaxis.Style{Foreground: purp}
206 servicesHeight := 8
207 return brd.Draw(vxfw.DrawContext{
208 Characters: ctx.Characters,
209 Max: vxfw.Size{
210 Width: 32,
211 Height: uint16(servicesHeight) + 3,
212 },
213 })
214}
215
216func (m *ServicesList) getServiceKv() []Kv {
217 kv := []Kv{}
218 for idx, d := range m.data {
219 value := d[1]
220 var style vaxis.Style
221 if idx == 0 {
222 style = vaxis.Style{
223 UnderlineColor: purp,
224 UnderlineStyle: vaxis.UnderlineDashed,
225 Foreground: purp,
226 }
227 } else if value == "active" {
228 style = vaxis.Style{Foreground: green}
229 } else if value == "free tier" {
230 style = vaxis.Style{Foreground: oj}
231 } else {
232 style = vaxis.Style{Foreground: red}
233 }
234
235 kv = append(kv, Kv{
236 Key: d[0],
237 Value: value,
238 Style: style,
239 })
240
241 }
242
243 return kv
244}