Search Apps Documentation Source Content File Folder Download Copy Actions Download State String Boolean Number Struct Map Slice Pointer Function Closure Reference Nil Package Type Interface Unknown

pager.gno

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