-
Notifications
You must be signed in to change notification settings - Fork 389
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- 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
1 parent
e2e9435
commit 81a88a2
Showing
7 changed files
with
543 additions
and
7 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
Oops, something went wrong.