-
Notifications
You must be signed in to change notification settings - Fork 373
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: add p/avl/pager #2584
feat: add p/avl/pager #2584
Changes from 24 commits
ac0d79e
a17edb2
1a44f35
291b92f
6038562
23db6aa
fa7286b
c0d435d
1cff9f1
04d62c7
b2294ac
2a1093e
903fb48
f1adda0
7d44835
c8e4e1f
a0be035
5c3524d
97794d7
1da8805
943df0c
1c10732
79ffd7d
adf95c0
3754a70
b9b7209
e39e037
fbc75c8
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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 | ||
) |
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -0,0 +1,220 @@ | ||||||
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 | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. offtopic: a bit misleading. Normally, if you want to continue, you return true, but it is what it is. Example from standard library: https://pkg.go.dev/sync#Map.Range |
||||||
}) | ||||||
|
||||||
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 { | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
I think this is a more intuitive name There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh yes, Picker sounds better. @alexiscolin, do you have an opinion on the name of this part, "1 2 3 4 5"? |
||||||
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 != "" { | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: given the current situation, this parameter will not be empty, so the check can be omitted. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It can be made empty. The question is whether we let the |
||||||
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 min(a, b int) int { | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. seems not used in any place There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||||||
if a < b { | ||||||
return a | ||||||
} | ||||||
return b | ||||||
} | ||||||
|
||||||
func max(a, b int) int { | ||||||
if a > b { | ||||||
return a | ||||||
} | ||||||
return b | ||||||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
pageNumber > 0
will always be true in thisif
, becausetotalPages
cannot be less than 0There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
right, but actually i'm considering if in this case it should maybe be
totalPages > 0
.