You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
cointop/cointop/table.go

384 lines
9.9 KiB
Go

package cointop
import (
"fmt"
"math"
"net/url"
"strconv"
"strings"
"time"
"github.com/miguelmota/cointop/pkg/humanize"
"github.com/miguelmota/cointop/pkg/pad"
"github.com/miguelmota/cointop/pkg/table"
"github.com/miguelmota/cointop/pkg/ui"
)
// TableView is structure for table view
type TableView = ui.View
// NewTableView returns a new table view
func NewTableView() *TableView {
var view *TableView = ui.NewView("table")
return view
}
// TableColumnOrder returns the default order of the table columns
func TableColumnOrder() []string {
return []string{
"rank",
"name",
"symbol",
"price",
"holdings",
"balance",
"marketcap",
"24hvolume",
"1hchange",
"7dchange",
"totalsupply",
"availablesupply",
"percentholdings",
"lastupdated",
}
}
const dots = "..."
// RefreshTable refreshes the table
func (ct *Cointop) RefreshTable() error {
ct.debuglog("refreshTable()")
maxX := ct.width()
ct.table = table.New().SetWidth(maxX)
ct.table.HideColumHeaders = true
if ct.State.portfolioVisible {
ct.table.AddCol("")
ct.table.AddCol("")
ct.table.AddCol("")
ct.table.AddCol("")
ct.table.AddCol("")
ct.table.AddCol("")
ct.table.AddCol("")
ct.table.AddCol("")
ct.table.AddCol("")
total := ct.GetPortfolioTotal()
for _, coin := range ct.State.coins {
unix, _ := strconv.ParseInt(coin.LastUpdated, 10, 64)
lastUpdated := time.Unix(unix, 0).Format("15:04:05 Jan 02")
colorbalance := ct.colorscheme.TableColumnPrice
color24h := ct.colorscheme.TableColumnChange
if coin.PercentChange24H > 0 {
color24h = ct.colorscheme.TableColumnChangeUp
}
if coin.PercentChange24H < 0 {
color24h = ct.colorscheme.TableColumnChangeDown
}
name := coin.Name
star := ct.colorscheme.TableRow(" ")
if coin.Favorite {
star = ct.colorscheme.TableRowFavorite("*")
}
rank := fmt.Sprintf("%s%v", star, ct.colorscheme.TableRow(fmt.Sprintf("%6v ", coin.Rank)))
if len(name) > 20 {
name = fmt.Sprintf("%s%s", name[0:18], dots)
}
namecolor := ct.colorscheme.TableRow
if coin.Favorite {
namecolor = ct.colorscheme.TableRowFavorite
}
percentHoldings := (coin.Balance / total) * 1e2
if math.IsNaN(percentHoldings) {
percentHoldings = 0
}
ct.table.AddRow(
rank,
namecolor(pad.Right(fmt.Sprintf("%.22s", name), 21, " ")),
ct.colorscheme.TableRow(pad.Right(fmt.Sprintf("%.6s", coin.Symbol), 5, " ")),
ct.colorscheme.TableRow(fmt.Sprintf("%13s", humanize.Commaf(coin.Price))),
ct.colorscheme.TableRow(fmt.Sprintf("%15s", strconv.FormatFloat(coin.Holdings, 'f', -1, 64))),
colorbalance(fmt.Sprintf("%15s", humanize.Commaf(coin.Balance))),
color24h(fmt.Sprintf("%8.2f%%", coin.PercentChange24H)),
ct.colorscheme.TableRow(fmt.Sprintf("%12.2f%%", percentHoldings)),
ct.colorscheme.TableRow(pad.Right(fmt.Sprintf("%17s", lastUpdated), 80, " ")),
)
}
} else {
ct.table.AddCol("")
ct.table.AddCol("")
ct.table.AddCol("")
ct.table.AddCol("")
ct.table.AddCol("")
ct.table.AddCol("")
ct.table.AddCol("")
ct.table.AddCol("")
ct.table.AddCol("")
ct.table.AddCol("")
ct.table.AddCol("")
ct.table.AddCol("")
for _, coin := range ct.State.coins {
if coin == nil {
continue
}
unix, _ := strconv.ParseInt(coin.LastUpdated, 10, 64)
lastUpdated := time.Unix(unix, 0).Format("15:04:05 Jan 02")
namecolor := ct.colorscheme.TableRow
color1h := ct.colorscheme.TableColumnChange
color24h := ct.colorscheme.TableColumnChange
color7d := ct.colorscheme.TableColumnChange
if coin.Favorite {
namecolor = ct.colorscheme.TableRowFavorite
}
if coin.PercentChange1H > 0 {
color1h = ct.colorscheme.TableColumnChangeUp
}
if coin.PercentChange1H < 0 {
color1h = ct.colorscheme.TableColumnChangeDown
}
if coin.PercentChange24H > 0 {
color24h = ct.colorscheme.TableColumnChangeUp
}
if coin.PercentChange24H < 0 {
color24h = ct.colorscheme.TableColumnChangeDown
}
if coin.PercentChange7D > 0 {
color7d = ct.colorscheme.TableColumnChangeUp
}
if coin.PercentChange7D < 0 {
color7d = ct.colorscheme.TableColumnChangeDown
}
name := coin.Name
star := ct.colorscheme.TableRow(" ")
if coin.Favorite {
star = ct.colorscheme.TableRowFavorite("*")
}
rank := fmt.Sprintf("%s%v", star, ct.colorscheme.TableRow(fmt.Sprintf("%6v ", coin.Rank)))
if len(name) > 20 {
name = fmt.Sprintf("%s%s", name[0:18], dots)
}
symbolpadding := 5
// NOTE: this is to adjust padding by 1 because when all name rows are
// yellow it messes the spacing (need to debug)
if ct.State.filterByFavorites {
symbolpadding = 6
}
ct.table.AddRow(
rank,
namecolor(pad.Right(fmt.Sprintf("%.22s", name), 21, " ")),
ct.colorscheme.TableRow(pad.Right(fmt.Sprintf("%.6s", coin.Symbol), symbolpadding, " ")),
ct.colorscheme.TableColumnPrice(fmt.Sprintf("%13s", humanize.Commaf(coin.Price))),
ct.colorscheme.TableRow(fmt.Sprintf("%17s", humanize.Commaf(coin.MarketCap))),
ct.colorscheme.TableRow(fmt.Sprintf("%15s", humanize.Commaf(coin.Volume24H))),
color1h(fmt.Sprintf("%8.2f%%", coin.PercentChange1H)),
color24h(fmt.Sprintf("%8.2f%%", coin.PercentChange24H)),
color7d(fmt.Sprintf("%8.2f%%", coin.PercentChange7D)),
ct.colorscheme.TableRow(fmt.Sprintf("%21s", humanize.Commaf(coin.TotalSupply))),
ct.colorscheme.TableRow(fmt.Sprintf("%18s", humanize.Commaf(coin.AvailableSupply))),
ct.colorscheme.TableRow(fmt.Sprintf("%18s", lastUpdated)),
// TODO: add %percent of cap
)
}
}
// highlight last row if current row is out of bounds (can happen when switching views)
currentrow := ct.HighlightedRowIndex()
if len(ct.State.coins) > currentrow {
ct.HighlightRow(currentrow)
}
ct.UpdateUI(func() error {
ct.Views.Table.Clear()
ct.table.Format().Fprint(ct.Views.Table.Backing())
go ct.RowChanged()
go ct.UpdateTableHeader()
go ct.UpdateMarketbar()
go ct.UpdateChart()
return nil
})
return nil
}
// UpdateTable updates the table
func (ct *Cointop) UpdateTable() error {
ct.debuglog("UpdateTable()")
ct.State.allCoinsSlugMap.Range(func(key, value interface{}) bool {
k := key.(string)
if v, ok := value.(*Coin); ok {
v.Favorite = ct.State.favorites[v.Name]
ct.State.allCoinsSlugMap.Store(k, v)
}
return true
})
if ct.State.filterByFavorites {
ct.State.coins = ct.GetFavoritesSlice()
} else if ct.State.portfolioVisible {
ct.State.coins = ct.GetPortfolioSlice()
} else {
// TODO: maintain state of previous sorting
if ct.State.sortBy == "holdings" {
ct.State.sortBy = "rank"
ct.State.sortDesc = false
}
ct.State.coins = ct.GetTableCoinsSlice()
}
ct.Sort(ct.State.sortBy, ct.State.sortDesc, ct.State.coins, true)
go ct.RefreshTable()
return nil
}
// GetTableCoinsSlice returns a slice of the table rows
func (ct *Cointop) GetTableCoinsSlice() []*Coin {
ct.debuglog("GetTableCoinsSlice()")
sliced := []*Coin{}
start := ct.State.page * ct.State.perPage
end := start + ct.State.perPage
allCoins := ct.AllCoins()
size := len(allCoins)
if start < 0 {
start = 0
}
if end >= size-1 {
start = int(math.Floor(float64(start/100)) * 100)
end = size - 1
}
if start < 0 {
start = 0
}
if end >= size {
end = size - 1
}
if end < 0 {
end = 0
}
if start >= end {
return nil
}
if end > 0 {
sliced = allCoins[start:end]
// NOTE: restore rank
for _, coin := range sliced {
icoin, _ := ct.State.allCoinsSlugMap.Load(coin.Name)
if icoin != nil {
c, _ := icoin.(*Coin)
coin.Rank = c.Rank
}
}
}
return sliced
}
// HighlightedRowIndex returns the index of the highlighted row
func (ct *Cointop) HighlightedRowIndex() int {
ct.debuglog("HighlightedRowIndex()")
oy := ct.Views.Table.OriginY()
cy := ct.Views.Table.CursorY()
idx := oy + cy
if idx < 0 {
idx = 0
}
if idx >= len(ct.State.coins) {
idx = len(ct.State.coins) - 1
}
return idx
}
// HighlightedRowCoin returns the coin at the index of the highlighted row
func (ct *Cointop) HighlightedRowCoin() *Coin {
ct.debuglog("HighlightedRowCoin()")
idx := ct.HighlightedRowIndex()
if len(ct.State.coins) == 0 {
return nil
}
return ct.State.coins[idx]
}
// HighlightedPageRowIndex returns the index of page row of the highlighted row
func (ct *Cointop) HighlightedPageRowIndex() int {
ct.debuglog("HighlightedPageRowIndex()")
cy := ct.Views.Table.CursorY()
idx := cy
if idx < 0 {
idx = 0
}
return idx
}
// RowLink returns the row url link
func (ct *Cointop) RowLink() string {
ct.debuglog("RowLink()")
coin := ct.HighlightedRowCoin()
if coin == nil {
return ""
}
return ct.api.CoinLink(coin.Name)
}
// RowLinkShort returns a shortened version of the row url link
func (ct *Cointop) RowLinkShort() string {
ct.debuglog("RowLinkShort()")
link := ct.RowLink()
if link != "" {
u, err := url.Parse(link)
if err != nil {
return ""
}
host := u.Hostname()
host = strings.Replace(host, "www.", "", -1)
path := u.EscapedPath()
parts := strings.Split(path, "/")
if len(parts) > 0 {
path = parts[len(parts)-1]
}
return fmt.Sprintf("http://%s/%s/%s", host, dots, path)
}
return ""
}
// ToggleTableFullscreen toggles the table fullscreen mode
func (ct *Cointop) ToggleTableFullscreen() error {
ct.debuglog("ToggleTableFullscreen()")
ct.State.onlyTable = !ct.State.onlyTable
if ct.State.onlyTable {
} else {
// NOTE: cached values are initial config settings.
// If the only-table config was set then toggle
// all other initial hidden views.
onlyTable, _ := ct.cache.Get("onlyTable")
if onlyTable.(bool) {
ct.State.hideMarketbar = false
ct.State.hideChart = false
ct.State.hideStatusbar = false
} else {
// NOTE: cached values store initial hidden views preferences.
hideMarketbar, _ := ct.cache.Get("hideMarketbar")
ct.State.hideMarketbar = hideMarketbar.(bool)
hideChart, _ := ct.cache.Get("hideChart")
ct.State.hideChart = hideChart.(bool)
hideStatusbar, _ := ct.cache.Get("hideStatusbar")
ct.State.hideStatusbar = hideStatusbar.(bool)
}
}
return nil
}