ref: 3ea29357e9eb81b737771148c22b6aff39031b7e
dir: /pages.go/
package main
import (
"context"
"fmt"
"strings"
"sync"
mastodon "codeberg.org/penny64/hellclient-go-mastodon"
"golang.org/x/term"
)
// Loads rendered status items ready for pagination
type StatusLoader interface {
Load(int) *[]PageItem
}
// Get interface for loading mastodon statuses
// The status loader uses this as the provider for statuses
type StatusGetter interface {
Get(int) ([]*mastodon.Status, error)
}
type ProfileLoader struct {
client *mastodon.Client
}
// Get(int) implementation for mastodon client calls that use the same function signature
type BasicStatusGetter struct {
getter func(ctx context.Context, pg *mastodon.Pagination) ([]*mastodon.Status, error)
page *mastodon.Pagination
}
// Get implementation for loading a status thread
type ThreadStatusGetter struct {
target *mastodon.Status
client *mastodon.Client
loaded bool
}
// Thread status getter
// The API doesn't take a limit for this so we ignore the limit
func (getter *ThreadStatusGetter) Get(limit int) ([]*mastodon.Status, error) {
if getter.loaded {
return nil, nil
}
context, err := getter.client.GetStatusContext(context.Background(), getter.target.ID)
if err == nil {
getter.loaded = true
}
var statuses []*mastodon.Status
statuses = append(statuses, context.Ancestors...)
statuses = append(statuses, getter.target)
statuses = append(statuses, context.Descendants...)
return statuses, err
}
// Get the status calls with the getter.getter function
func (getter *BasicStatusGetter) Get(limit int) ([]*mastodon.Status, error) {
if getter.page == nil {
getter.page = &mastodon.Pagination{}
}
if getter.page.MaxID == "" && getter.page.MinID != "" {
return nil, nil
}
getter.page.MinID = ""
getter.page.Limit = int64(limit)
statuses, err := getter.getter(context.Background(), getter.page)
return statuses, err
}
// Get implementation for pinned statuses
type PinnedStatusGetter struct {
loaded bool
ID mastodon.ID
client *mastodon.Client
}
// Gets pinned statuses for account up to limit
// Pinned posts are not paginated this is another oneshot
func (getter *PinnedStatusGetter) Get(limit int) ([]*mastodon.Status, error) {
if getter.loaded {
return nil, nil
}
statuses, err := getter.client.GetAccountPinnedStatuses(context.Background(), getter.ID)
if err == nil {
getter.loaded = true
}
return statuses, err
}
// Get implementation for profile statuses
type AccountStatusGetter struct {
page *mastodon.Pagination
ID mastodon.ID
client *mastodon.Client
}
// Gets statuses for account up to limit
func (getter *AccountStatusGetter) Get(limit int) ([]*mastodon.Status, error) {
if getter.page == nil {
getter.page = &mastodon.Pagination{}
}
if getter.page.MaxID == "" && getter.page.MinID != "" {
return nil, nil
}
getter.page.MinID = ""
getter.page.Limit = int64(limit)
statuses, err := getter.client.GetAccountStatuses(context.Background(), getter.ID, getter.page)
return statuses, err
}
// Page implements paginated dynamic rendering of PageItems
type Page struct {
loader StatusLoader
//Array index for the item buffer
index int
//Index of the last item on the page
indexend int
//our previous index for going back
previndexes []int
//Semantic page number for the UI
page int
//don't flip the order around
disablereverse bool
itembuffer *[]PageItem
findindex sync.Once
}
type StatusPages struct {
hc *Hellclient
prefix string
getter StatusGetter
}
type PageItem struct {
itemtext string
lines int
}
func makePageItem(renderedtext string) (page PageItem) {
page.lines = strings.Count(renderedtext, "\n")
page.itemtext = renderedtext
return
}
func (statusData *StatusPages) Load(limit int) *[]PageItem {
var itemArray []PageItem
var statuses []*mastodon.Status
var err error
statusfunc := func(job *GenericJob) { statuses, err = statusData.getter.Get(limit) }
statusjob := statusData.hc.dispatchFunc(statusfunc)
statusjob.Wait()
if err != nil {
fmt.Printf("Couldn't load status page: %s\n", err)
}
formatter := &StatusFormatter{prefs: statusData.hc.preferences, postContext: statusData.hc.ctxref}
templater := newStatusTemplateRenderer(formatter)
for i := range statuses {
formatter.status = statuses[i]
if formatter.status.Reblog != nil {
}
var plaintext string
if formatter.status.Reblog != nil {
plaintext = "$standard_reblog"
} else {
plaintext = "$standard_status"
}
line, _ := templater.render(plaintext)
item := makePageItem(line)
itemArray = append(itemArray, item)
justIncrementPostref(statusData.hc.ctxref, statuses[i])
}
return &itemArray
}
// Notification PageItem loader
type NotificationPages struct {
hc *Hellclient
page *mastodon.Pagination
}
// Returns limit of rendered notification PageItems
func (noticeData *NotificationPages) Load(limit int) *[]PageItem {
if noticeData.page == nil {
noticeData.page = &mastodon.Pagination{}
}
noticeData.page.Limit = int64(limit)
var notices []*mastodon.Notification
var err error
getnotifs := func(job *GenericJob) {
notices, err = noticeData.hc.client.GetNotifications(context.Background(), noticeData.page)
}
notifjob := noticeData.hc.dispatchFunc(getnotifs)
notifjob.Wait()
if err != nil {
fmt.Printf("Error getting notification page: %s\n", err)
}
noticeArray := noticeData.hc.RenderNotificationsArray(notices)
var itemArray []PageItem
for i := range noticeArray {
item := makePageItem(noticeArray[i])
itemArray = append(itemArray, item)
}
noticeData.page.MinID = ""
return &itemArray
}
// Get how many of the page items fit within the given window height
// And whether we had enough items to bump off of the screen
func findIndex(height int, items []PageItem) (index int, bumped bool) {
linecount := 5
for i := range items {
linecount = linecount + items[i].lines
if i != 0 && linecount > height {
return index, true
}
//-1 lines indicates we should display this item on its own page
if i != 0 && items[i].lines == -1 {
return index, false
}
if i == 0 && items[i].lines == -1 {
index++
return index, true
}
index++
}
return index, false
}
// adjusts page indexes to show the previous page
func (page *Page) findIndexStart() {
_, windowheight, err := term.GetSize(int(0))
if err != nil {
//Not a tty? Dunno, here's 25 lines for a standard terminal
windowheight = 25
}
var items []PageItem
items = (*page.itembuffer)[:page.index]
items = reverseArray(items)
newindex, _ := findIndex(windowheight, items)
page.indexend = page.index
page.index = page.index - newindex
}
// increases indexes to the next page, buffering more PostItems as needed
func (page *Page) findIndexEnd() {
_, windowheight, err := term.GetSize(int(0))
if err != nil {
//Not a tty? Dunno, here's 25 lines for a standard terminal
windowheight = 25
}
page.indexend = page.index
var items []PageItem
var newend int
var bumped bool
if page.itembuffer == nil {
page.Buffer()
}
if len(*page.itembuffer) >= page.index {
for {
items = (*page.itembuffer)[page.index:]
newend, bumped = findIndex(windowheight, items)
newend = page.indexend + newend
if bumped {
page.indexend = newend
return
}
//If we didn't bump and there's no posts left, check for more
if !bumped && len((*page.itembuffer)[newend:]) == 0 {
page.Buffer()
if len((*page.itembuffer)[newend:]) == 0 {
page.indexend = newend
return
}
}
}
}
}
// Load a new batch of statusitems into the buffer
func (page *Page) Buffer() {
if page.itembuffer == nil {
page.itembuffer = &[]PageItem{}
}
*page.itembuffer = append(*page.itembuffer, *page.loader.Load(20)...)
}
func (page *Page) Pages(number int) string {
var sb strings.Builder
if number == 0 {
number = 1
}
pageAction := page.Next
if number < 0 {
number = -number
pageAction = page.Prev
}
for p := 0 ; p < number ; p++ {
sb.WriteString(page.String())
if (p + 1) != number {
pageAction()
}
}
sb.WriteString(page.pageTitle())
return sb.String()
}
// Returns the current page rendered as text
func (page *Page) String() string {
var sb strings.Builder
page.findindex.Do(page.findIndexEnd)
var items []PageItem
items = (*page.itembuffer)[page.index:page.indexend]
if !page.disablereverse {
items = reverseArray(items)
}
for i := range items {
sb.WriteString(items[i].itemtext)
}
return sb.String()
}
// Advance the page to the next indexes
func (page *Page) Next() {
page.page++
page.index = page.indexend
page.indexend = 0
page.findIndexEnd()
}
// Go to the previous page of items
func (page *Page) Prev() {
page.findIndexStart()
if page.page > 0 {
page.page--
}
}
// Simple navigation hint line
func (page *Page) pageTitle() string {
return fmt.Sprintf("Page %v. /next for next page /prev for previous\n", page.page+1)
}
func (page *Page) Current() int {
return page.page
}