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}