shithub: hell

ref: 120d49084337787aca20d342a920e106e6ea63ad
dir: /pages.go/

View raw version
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 = fmt.Sprintf("$standard_reblog")
		} else {
			plaintext = fmt.Sprintf("$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)...)
}

// 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)
	}
	sb.WriteString(page.pageTitle())
	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
}