1package repo
2
3import (
4 "fmt"
5 "path/filepath"
6
7 "github.com/charmbracelet/bubbles/key"
8 "github.com/charmbracelet/bubbles/spinner"
9 tea "github.com/charmbracelet/bubbletea"
10 "github.com/charmbracelet/soft-serve/server/backend"
11 "github.com/charmbracelet/soft-serve/server/proto"
12 "github.com/charmbracelet/soft-serve/server/ui/common"
13 "github.com/charmbracelet/soft-serve/server/ui/components/code"
14)
15
16// ReadmeMsg is a message sent when the readme is loaded.
17type ReadmeMsg struct {
18 Content string
19 Path string
20}
21
22// Readme is the readme component page.
23type Readme struct {
24 common common.Common
25 code *code.Code
26 ref RefMsg
27 repo proto.Repository
28 readmePath string
29 spinner spinner.Model
30 isLoading bool
31}
32
33// NewReadme creates a new readme model.
34func NewReadme(common common.Common) *Readme {
35 readme := code.New(common, "", "")
36 readme.NoContentStyle = readme.NoContentStyle.Copy().SetString("No readme found.")
37 readme.UseGlamour = true
38 s := spinner.New(spinner.WithSpinner(spinner.Dot),
39 spinner.WithStyle(common.Styles.Spinner))
40 return &Readme{
41 code: readme,
42 common: common,
43 spinner: s,
44 isLoading: true,
45 }
46}
47
48// TabName returns the name of the tab.
49func (r *Readme) TabName() string {
50 return "Readme"
51}
52
53// SetSize implements common.Component.
54func (r *Readme) SetSize(width, height int) {
55 r.common.SetSize(width, height)
56 r.code.SetSize(width, height)
57}
58
59// ShortHelp implements help.KeyMap.
60func (r *Readme) ShortHelp() []key.Binding {
61 b := []key.Binding{
62 r.common.KeyMap.UpDown,
63 }
64 return b
65}
66
67// FullHelp implements help.KeyMap.
68func (r *Readme) FullHelp() [][]key.Binding {
69 k := r.code.KeyMap
70 b := [][]key.Binding{
71 {
72 k.PageDown,
73 k.PageUp,
74 k.HalfPageDown,
75 k.HalfPageUp,
76 },
77 {
78 k.Down,
79 k.Up,
80 r.common.KeyMap.GotoTop,
81 r.common.KeyMap.GotoBottom,
82 },
83 }
84 return b
85}
86
87// Init implements tea.Model.
88func (r *Readme) Init() tea.Cmd {
89 r.isLoading = true
90 return tea.Batch(r.spinner.Tick, r.updateReadmeCmd)
91}
92
93// Update implements tea.Model.
94func (r *Readme) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
95 cmds := make([]tea.Cmd, 0)
96 switch msg := msg.(type) {
97 case RepoMsg:
98 r.repo = msg
99 case RefMsg:
100 r.ref = msg
101 cmds = append(cmds, r.Init())
102 case tea.WindowSizeMsg:
103 r.SetSize(msg.Width, msg.Height)
104 case EmptyRepoMsg:
105 cmds = append(cmds,
106 r.code.SetContent(defaultEmptyRepoMsg(r.common.Config(),
107 r.repo.Name()), ".md"),
108 )
109 case ReadmeMsg:
110 r.isLoading = false
111 r.readmePath = msg.Path
112 r.code.GotoTop()
113 cmds = append(cmds, r.code.SetContent(msg.Content, msg.Path))
114 case spinner.TickMsg:
115 if r.isLoading && r.spinner.ID() == msg.ID {
116 s, cmd := r.spinner.Update(msg)
117 r.spinner = s
118 if cmd != nil {
119 cmds = append(cmds, cmd)
120 }
121 }
122 }
123 c, cmd := r.code.Update(msg)
124 r.code = c.(*code.Code)
125 if cmd != nil {
126 cmds = append(cmds, cmd)
127 }
128 return r, tea.Batch(cmds...)
129}
130
131// View implements tea.Model.
132func (r *Readme) View() string {
133 if r.isLoading {
134 return renderLoading(r.common, r.spinner)
135 }
136 return r.code.View()
137}
138
139// SpinnerID implements common.TabComponent.
140func (r *Readme) SpinnerID() int {
141 return r.spinner.ID()
142}
143
144// StatusBarValue implements statusbar.StatusBar.
145func (r *Readme) StatusBarValue() string {
146 dir := filepath.Dir(r.readmePath)
147 if dir == "." || dir == "" {
148 return " "
149 }
150 return dir
151}
152
153// StatusBarInfo implements statusbar.StatusBar.
154func (r *Readme) StatusBarInfo() string {
155 return fmt.Sprintf("☰ %d%%", r.code.ScrollPosition())
156}
157
158func (r *Readme) updateReadmeCmd() tea.Msg {
159 m := ReadmeMsg{}
160 if r.repo == nil {
161 return common.ErrorMsg(common.ErrMissingRepo)
162 }
163 rm, rp, _ := backend.Readme(r.repo, r.ref)
164 m.Content = rm
165 m.Path = rp
166 return m
167}