Skip to content

Commit

Permalink
feat: add p/avl/pager (#2584)
Browse files Browse the repository at this point in the history
- add `p/demo/avl/pager`
- update `r/demo/users`

Hey reviewers, in addition to what you wanted to review, I'm
specifically curious if you have any better API/usage ideas.

Example:
https://github.com/gnolang/gno/pull/2584/files#diff-8d5cbbe072737a7f288f74adcaaace11cacc3d31264e6a001515fcae824394e2R33

Related with #447, #599, #868

---------

Signed-off-by: moul <[email protected]>
Co-authored-by: Antonio Navarro Perez <[email protected]>
Co-authored-by: Leon Hudak <[email protected]>
  • Loading branch information
3 people authored Nov 6, 2024
1 parent e2e9435 commit 81a88a2
Show file tree
Hide file tree
Showing 7 changed files with 543 additions and 7 deletions.
8 changes: 8 additions & 0 deletions examples/gno.land/p/demo/avl/pager/gno.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
module gno.land/p/demo/avl/pager

require (
gno.land/p/demo/avl v0.0.0-latest
gno.land/p/demo/uassert v0.0.0-latest
gno.land/p/demo/ufmt v0.0.0-latest
gno.land/p/demo/urequire v0.0.0-latest
)
213 changes: 213 additions & 0 deletions examples/gno.land/p/demo/avl/pager/pager.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,213 @@
package pager

import (
"math"
"net/url"
"strconv"

"gno.land/p/demo/avl"
"gno.land/p/demo/ufmt"
)

// Pager is a struct that holds the AVL tree and pagination parameters.
type Pager struct {
Tree *avl.Tree
PageQueryParam string
SizeQueryParam string
DefaultPageSize int
}

// Page represents a single page of results.
type Page struct {
Items []Item
PageNumber int
PageSize int
TotalItems int
TotalPages int
HasPrev bool
HasNext bool
Pager *Pager // Reference to the parent Pager
}

// Item represents a key-value pair in the AVL tree.
type Item struct {
Key string
Value interface{}
}

// NewPager creates a new Pager with default values.
func NewPager(tree *avl.Tree, defaultPageSize int) *Pager {
return &Pager{
Tree: tree,
PageQueryParam: "page",
SizeQueryParam: "size",
DefaultPageSize: defaultPageSize,
}
}

// GetPage retrieves a page of results from the AVL tree.
func (p *Pager) GetPage(pageNumber int) *Page {
return p.GetPageWithSize(pageNumber, p.DefaultPageSize)
}

func (p *Pager) GetPageWithSize(pageNumber, pageSize int) *Page {
totalItems := p.Tree.Size()
totalPages := int(math.Ceil(float64(totalItems) / float64(pageSize)))

page := &Page{
TotalItems: totalItems,
TotalPages: totalPages,
PageSize: pageSize,
Pager: p,
}

// pages without content
if pageSize < 1 {
return page
}

// page number provided is not available
if pageNumber < 1 {
page.HasNext = totalPages > 0
return page
}

// page number provided is outside the range of total pages
if pageNumber > totalPages {
page.PageNumber = pageNumber
page.HasPrev = pageNumber > 0
return page
}

startIndex := (pageNumber - 1) * pageSize
endIndex := startIndex + pageSize
if endIndex > totalItems {
endIndex = totalItems
}

items := []Item{}
p.Tree.ReverseIterateByOffset(startIndex, endIndex-startIndex, func(key string, value interface{}) bool {
items = append(items, Item{Key: key, Value: value})
return false
})

page.Items = items
page.PageNumber = pageNumber
page.HasPrev = pageNumber > 1
page.HasNext = pageNumber < totalPages
return page
}

func (p *Pager) MustGetPageByPath(rawURL string) *Page {
page, err := p.GetPageByPath(rawURL)
if err != nil {
panic("invalid path")
}
return page
}

// GetPageByPath retrieves a page of results based on the query parameters in the URL path.
func (p *Pager) GetPageByPath(rawURL string) (*Page, error) {
pageNumber, pageSize, err := p.ParseQuery(rawURL)
if err != nil {
return nil, err
}
return p.GetPageWithSize(pageNumber, pageSize), nil
}

// UI generates the Markdown UI for the page selector.
func (p *Page) Selector() string {
pageNumber := p.PageNumber
pageNumber = max(pageNumber, 1)

if p.TotalPages <= 1 {
return ""
}

md := ""

if p.HasPrev {
// Always show the first page link
md += ufmt.Sprintf("[%d](?%s=%d) | ", 1, p.Pager.PageQueryParam, 1)

// Before
if p.PageNumber > 4 {
md += "… | "
}

if p.PageNumber > 3 {
md += ufmt.Sprintf("[%d](?%s=%d) | ", p.PageNumber-2, p.Pager.PageQueryParam, p.PageNumber-2)
}

if p.PageNumber > 2 {
md += ufmt.Sprintf("[%d](?%s=%d) | ", p.PageNumber-1, p.Pager.PageQueryParam, p.PageNumber-1)
}
}

if p.PageNumber > 0 && p.PageNumber <= p.TotalPages {
// Current page
md += ufmt.Sprintf("**%d**", p.PageNumber)
} else {
md += ufmt.Sprintf("_%d_", p.PageNumber)
}

if p.HasNext {
md += " | "

if p.PageNumber < p.TotalPages-1 {
md += ufmt.Sprintf("[%d](?%s=%d) | ", p.PageNumber+1, p.Pager.PageQueryParam, p.PageNumber+1)
}

if p.PageNumber < p.TotalPages-2 {
md += ufmt.Sprintf("[%d](?%s=%d) | ", p.PageNumber+2, p.Pager.PageQueryParam, p.PageNumber+2)
}

if p.PageNumber < p.TotalPages-3 {
md += "… | "
}

// Always show the last page link
md += ufmt.Sprintf("[%d](?%s=%d)", p.TotalPages, p.Pager.PageQueryParam, p.TotalPages)
}

return md
}

// ParseQuery parses the URL to extract the page number and page size.
func (p *Pager) ParseQuery(rawURL string) (int, int, error) {
u, err := url.Parse(rawURL)
if err != nil {
return 1, p.DefaultPageSize, err
}

query := u.Query()
pageNumber := 1
pageSize := p.DefaultPageSize

if p.PageQueryParam != "" {
if pageStr := query.Get(p.PageQueryParam); pageStr != "" {
pageNumber, err = strconv.Atoi(pageStr)
if err != nil || pageNumber < 1 {
pageNumber = 1
}
}
}

if p.SizeQueryParam != "" {
if sizeStr := query.Get(p.SizeQueryParam); sizeStr != "" {
pageSize, err = strconv.Atoi(sizeStr)
if err != nil || pageSize < 1 {
pageSize = p.DefaultPageSize
}
}
}

return pageNumber, pageSize, nil
}

func max(a, b int) int {
if a > b {
return a
}
return b
}
Loading

0 comments on commit 81a88a2

Please sign in to comment.