pager.gno
5.12 Kb · 228 lines
1package pager
2
3import (
4 "math"
5 "net/url"
6 "strconv"
7
8 "gno.land/p/demo/avl/rotree"
9 "gno.land/p/demo/ufmt"
10)
11
12// Pager is a struct that holds the AVL tree and pagination parameters.
13type Pager struct {
14 Tree rotree.IReadOnlyTree
15 PageQueryParam string
16 SizeQueryParam string
17 DefaultPageSize int
18 Reversed bool
19}
20
21// Page represents a single page of results.
22type Page struct {
23 Items []Item
24 PageNumber int
25 PageSize int
26 TotalItems int
27 TotalPages int
28 HasPrev bool
29 HasNext bool
30 Pager *Pager // Reference to the parent Pager
31}
32
33// Item represents a key-value pair in the AVL tree.
34type Item struct {
35 Key string
36 Value any
37}
38
39// NewPager creates a new Pager with default values.
40func NewPager(tree rotree.IReadOnlyTree, defaultPageSize int, reversed bool) *Pager {
41 return &Pager{
42 Tree: tree,
43 PageQueryParam: "page",
44 SizeQueryParam: "size",
45 DefaultPageSize: defaultPageSize,
46 Reversed: reversed,
47 }
48}
49
50// GetPage retrieves a page of results from the AVL tree.
51func (p *Pager) GetPage(pageNumber int) *Page {
52 return p.GetPageWithSize(pageNumber, p.DefaultPageSize)
53}
54
55func (p *Pager) GetPageWithSize(pageNumber, pageSize int) *Page {
56 totalItems := p.Tree.Size()
57 totalPages := int(math.Ceil(float64(totalItems) / float64(pageSize)))
58
59 page := &Page{
60 TotalItems: totalItems,
61 TotalPages: totalPages,
62 PageSize: pageSize,
63 Pager: p,
64 }
65
66 // pages without content
67 if pageSize < 1 {
68 return page
69 }
70
71 // page number provided is not available
72 if pageNumber < 1 {
73 page.HasNext = totalPages > 0
74 return page
75 }
76
77 // page number provided is outside the range of total pages
78 if pageNumber > totalPages {
79 page.PageNumber = pageNumber
80 page.HasPrev = pageNumber > 0
81 return page
82 }
83
84 startIndex := (pageNumber - 1) * pageSize
85 endIndex := startIndex + pageSize
86 if endIndex > totalItems {
87 endIndex = totalItems
88 }
89
90 items := []Item{}
91
92 if p.Reversed {
93 p.Tree.ReverseIterateByOffset(startIndex, endIndex-startIndex, func(key string, value any) bool {
94 items = append(items, Item{Key: key, Value: value})
95 return false
96 })
97 } else {
98 p.Tree.IterateByOffset(startIndex, endIndex-startIndex, func(key string, value any) bool {
99 items = append(items, Item{Key: key, Value: value})
100 return false
101 })
102 }
103
104 page.Items = items
105 page.PageNumber = pageNumber
106 page.HasPrev = pageNumber > 1
107 page.HasNext = pageNumber < totalPages
108 return page
109}
110
111func (p *Pager) MustGetPageByPath(rawURL string) *Page {
112 page, err := p.GetPageByPath(rawURL)
113 if err != nil {
114 panic("invalid path")
115 }
116 return page
117}
118
119// GetPageByPath retrieves a page of results based on the query parameters in the URL path.
120func (p *Pager) GetPageByPath(rawURL string) (*Page, error) {
121 pageNumber, pageSize, err := p.ParseQuery(rawURL)
122 if err != nil {
123 return nil, err
124 }
125 return p.GetPageWithSize(pageNumber, pageSize), nil
126}
127
128// Picker generates the Markdown UI for the page Picker
129func (p *Page) Picker(path string) string {
130 pageNumber := p.PageNumber
131 pageNumber = max(pageNumber, 1)
132
133 if p.TotalPages <= 1 {
134 return ""
135 }
136
137 u, _ := url.Parse(path)
138 query := u.Query()
139
140 // Remove existing page query parameter
141 query.Del(p.Pager.PageQueryParam)
142
143 // Encode remaining query parameters
144 baseQuery := query.Encode()
145 if baseQuery != "" {
146 baseQuery = "&" + baseQuery
147 }
148 md := ""
149
150 if p.HasPrev {
151 md += ufmt.Sprintf("[%d](?%s=%d%s) | ", 1, p.Pager.PageQueryParam, 1, baseQuery)
152
153 if p.PageNumber > 4 {
154 md += "… | "
155 }
156
157 if p.PageNumber > 3 {
158 md += ufmt.Sprintf("[%d](?%s=%d%s) | ", p.PageNumber-2, p.Pager.PageQueryParam, p.PageNumber-2, baseQuery)
159 }
160
161 if p.PageNumber > 2 {
162 md += ufmt.Sprintf("[%d](?%s=%d%s) | ", p.PageNumber-1, p.Pager.PageQueryParam, p.PageNumber-1, baseQuery)
163 }
164 }
165
166 if p.PageNumber > 0 && p.PageNumber <= p.TotalPages {
167 md += ufmt.Sprintf("**%d**", p.PageNumber)
168 } else {
169 md += ufmt.Sprintf("_%d_", p.PageNumber)
170 }
171
172 if p.HasNext {
173 if p.PageNumber < p.TotalPages-1 {
174 md += ufmt.Sprintf(" | [%d](?%s=%d%s)", p.PageNumber+1, p.Pager.PageQueryParam, p.PageNumber+1, baseQuery)
175 }
176
177 if p.PageNumber < p.TotalPages-2 {
178 md += ufmt.Sprintf(" | [%d](?%s=%d%s)", p.PageNumber+2, p.Pager.PageQueryParam, p.PageNumber+2, baseQuery)
179 }
180
181 if p.PageNumber < p.TotalPages-3 {
182 md += " | …"
183 }
184
185 md += ufmt.Sprintf(" | [%d](?%s=%d%s)", p.TotalPages, p.Pager.PageQueryParam, p.TotalPages, baseQuery)
186 }
187
188 return md
189}
190
191// ParseQuery parses the URL to extract the page number and page size.
192func (p *Pager) ParseQuery(rawURL string) (int, int, error) {
193 u, err := url.Parse(rawURL)
194 if err != nil {
195 return 1, p.DefaultPageSize, err
196 }
197
198 query := u.Query()
199 pageNumber := 1
200 pageSize := p.DefaultPageSize
201
202 if p.PageQueryParam != "" {
203 if pageStr := query.Get(p.PageQueryParam); pageStr != "" {
204 pageNumber, err = strconv.Atoi(pageStr)
205 if err != nil || pageNumber < 1 {
206 pageNumber = 1
207 }
208 }
209 }
210
211 if p.SizeQueryParam != "" {
212 if sizeStr := query.Get(p.SizeQueryParam); sizeStr != "" {
213 pageSize, err = strconv.Atoi(sizeStr)
214 if err != nil || pageSize < 1 {
215 pageSize = p.DefaultPageSize
216 }
217 }
218 }
219
220 return pageNumber, pageSize, nil
221}
222
223func max(a, b int) int {
224 if a > b {
225 return a
226 }
227 return b
228}