pull/42/head
Miguel Mota 5 years ago
parent bedbda8e00
commit 4ef4f7aa54
No known key found for this signature in database
GPG Key ID: 67EC1161588A00F9

@ -644,6 +644,10 @@ Frequently asked questions:
cointop --coinmarketcap-api-key=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
```
- Q: I can I add my own API to cointop?
- A: Fork cointop and add the API that implements the [interface](https://github.com/miguelmota/cointop/blob/master/cointop/common/api/interface.go) to [`cointop/cointop/common/api/impl/`](https://github.com/miguelmota/cointop/tree/master/cointop/common/api/impl). You can use the [CoinGecko implementatoin](https://github.com/miguelmota/cointop/blob/master/cointop/common/api/impl/coingecko/coingecko.go) as reference.
- Q: I installed cointop without errors but the command is not found.
- A: Make sure your `GOPATH` and `PATH` is set correctly.
@ -886,7 +890,6 @@ Frequently asked questions:
-A: Cointop uses ~15MB of memory so you can run it on a Raspberry Pi Zero if you wanted to (one reason why cointop was built using Go instead of Node.js or Python).
- Q: How does cointop differ from [rate.sx](https://rate.sx/)?
- A: *rate.sx* is great for one-off queries or fetching data for bash scripts because it doesn't require installing anything. Cointop differs in that it is interactive and also supports more currencies.

@ -55,5 +55,5 @@ func actionsMap() map[string]bool {
}
func (ct *Cointop) actionExists(action string) bool {
return ct.actionsmap[action]
return ct.actionsMap[action]
}

@ -11,38 +11,38 @@ import (
"github.com/miguelmota/cointop/cointop/common/timeutil"
)
var chartlock sync.Mutex
var chartpointslock sync.Mutex
var chartLock sync.Mutex
var chartPointsLock sync.Mutex
func (ct *Cointop) updateChart() error {
if ct.chartview == nil {
if ct.Views.Chart.Backing == nil {
return nil
}
chartlock.Lock()
defer chartlock.Unlock()
chartLock.Lock()
defer chartLock.Unlock()
if ct.portfoliovisible {
if ct.State.portfolioVisible {
if err := ct.portfolioChart(); err != nil {
return err
}
} else {
symbol := ct.selectedCoinSymbol()
name := ct.selectedCoinName()
ct.chartPoints(symbol, name)
ct.calcChartPoints(symbol, name)
}
if len(ct.chartpoints) != 0 {
ct.chartview.Clear()
if len(ct.State.chartPoints) != 0 {
ct.Views.Chart.Backing.Clear()
}
var body string
if len(ct.chartpoints) == 0 {
if len(ct.State.chartPoints) == 0 {
body = "\n\n\n\n\nnot enough data for chart"
} else {
for i := range ct.chartpoints {
for i := range ct.State.chartPoints {
var s string
for j := range ct.chartpoints[i] {
p := ct.chartpoints[i][j]
for j := range ct.State.chartPoints[i] {
p := ct.State.chartPoints[i][j]
s = fmt.Sprintf("%s%c", s, p.Ch)
}
body = fmt.Sprintf("%s%s\n", body, s)
@ -50,20 +50,20 @@ func (ct *Cointop) updateChart() error {
}
}
ct.update(func() {
if ct.chartview == nil {
if ct.Views.Chart.Backing == nil {
return
}
fmt.Fprint(ct.chartview, ct.colorscheme.Chart(body))
fmt.Fprint(ct.Views.Chart.Backing, ct.colorscheme.Chart(body))
})
return nil
}
func (ct *Cointop) chartPoints(symbol string, name string) error {
maxX := ct.maxtablewidth - 3
chartpointslock.Lock()
defer chartpointslock.Unlock()
func (ct *Cointop) calcChartPoints(symbol string, name string) error {
maxX := ct.maxTableWidth - 3
chartPointsLock.Lock()
defer chartPointsLock.Unlock()
// TODO: not do this (SoC)
go ct.updateMarketbar()
@ -74,8 +74,8 @@ func (ct *Cointop) chartPoints(symbol string, name string) error {
// NOTE: empty list means don't show x-axis labels
chart.DataLabels = []string{""}
rangeseconds := ct.chartrangesmap[ct.selectedchartrange]
if ct.selectedchartrange == "YTD" {
rangeseconds := ct.chartRangesMap[ct.State.selectedChartRange]
if ct.State.selectedChartRange == "YTD" {
ytd := time.Now().Unix() - int64(timeutil.BeginningOfYear().Unix())
rangeseconds = time.Duration(ytd) * time.Second
}
@ -91,7 +91,7 @@ func (ct *Cointop) chartPoints(symbol string, name string) error {
if keyname == "" {
keyname = "globaldata"
}
cachekey := ct.cacheKey(fmt.Sprintf("%s_%s", keyname, strings.Replace(ct.selectedchartrange, " ", "", -1)))
cachekey := ct.cacheKey(fmt.Sprintf("%s_%s", keyname, strings.Replace(ct.State.selectedChartRange, " ", "", -1)))
cached, found := ct.cache.Get(cachekey)
if found {
@ -155,15 +155,15 @@ func (ct *Cointop) chartPoints(symbol string, name string) error {
points = append(points, rowpoints)
}
ct.chartpoints = points
ct.State.chartPoints = points
return nil
}
func (ct *Cointop) portfolioChart() error {
maxX := ct.maxtablewidth - 3
chartpointslock.Lock()
defer chartpointslock.Unlock()
maxX := ct.maxTableWidth - 3
chartPointsLock.Lock()
defer chartPointsLock.Unlock()
// TODO: not do this (SoC)
go ct.updateMarketbar()
@ -174,8 +174,8 @@ func (ct *Cointop) portfolioChart() error {
// NOTE: empty list means don't show x-axis labels
chart.DataLabels = []string{""}
rangeseconds := ct.chartrangesmap[ct.selectedchartrange]
if ct.selectedchartrange == "YTD" {
rangeseconds := ct.chartRangesMap[ct.State.selectedChartRange]
if ct.State.selectedChartRange == "YTD" {
ytd := time.Now().Unix() - int64(timeutil.BeginningOfYear().Unix())
rangeseconds = time.Duration(ytd) * time.Second
}
@ -201,7 +201,7 @@ func (ct *Cointop) portfolioChart() error {
}
var graphData []float64
cachekey := strings.ToLower(fmt.Sprintf("%s_%s", p.Symbol, strings.Replace(ct.selectedchartrange, " ", "", -1)))
cachekey := strings.ToLower(fmt.Sprintf("%s_%s", p.Symbol, strings.Replace(ct.State.selectedChartRange, " ", "", -1)))
cached, found := ct.cache.Get(cachekey)
if found {
// cache hit
@ -263,16 +263,16 @@ func (ct *Cointop) portfolioChart() error {
points = append(points, rowpoints)
}
ct.chartpoints = points
ct.State.chartPoints = points
return nil
}
func (ct *Cointop) nextChartRange() error {
sel := 0
max := len(ct.chartranges)
for i, k := range ct.chartranges {
if k == ct.selectedchartrange {
max := len(ct.chartRanges)
for i, k := range ct.chartRanges {
if k == ct.State.selectedChartRange {
sel = i + 1
break
}
@ -281,7 +281,7 @@ func (ct *Cointop) nextChartRange() error {
sel = 0
}
ct.selectedchartrange = ct.chartranges[sel]
ct.State.selectedChartRange = ct.chartRanges[sel]
go ct.updateChart()
return nil
@ -289,57 +289,39 @@ func (ct *Cointop) nextChartRange() error {
func (ct *Cointop) prevChartRange() error {
sel := 0
for i, k := range ct.chartranges {
if k == ct.selectedchartrange {
for i, k := range ct.chartRanges {
if k == ct.State.selectedChartRange {
sel = i - 1
break
}
}
if sel < 0 {
sel = len(ct.chartranges) - 1
sel = len(ct.chartRanges) - 1
}
ct.selectedchartrange = ct.chartranges[sel]
ct.State.selectedChartRange = ct.chartRanges[sel]
go ct.updateChart()
return nil
}
func (ct *Cointop) firstChartRange() error {
ct.selectedchartrange = ct.chartranges[0]
ct.State.selectedChartRange = ct.chartRanges[0]
go ct.updateChart()
return nil
}
func (ct *Cointop) lastChartRange() error {
ct.selectedchartrange = ct.chartranges[len(ct.chartranges)-1]
ct.State.selectedChartRange = ct.chartRanges[len(ct.chartRanges)-1]
go ct.updateChart()
return nil
}
func (ct *Cointop) selectedCoinName() string {
coin := ct.selectedcoin
if coin != nil {
return coin.Name
}
return ""
}
func (ct *Cointop) selectedCoinSymbol() string {
coin := ct.selectedcoin
if coin != nil {
return coin.Symbol
}
return ""
}
func (ct *Cointop) toggleCoinChart() error {
highlightedcoin := ct.highlightedRowCoin()
if ct.selectedcoin == highlightedcoin {
ct.selectedcoin = nil
if ct.State.selectedCoin == highlightedcoin {
ct.State.selectedCoin = nil
} else {
ct.selectedcoin = highlightedcoin
ct.State.selectedCoin = highlightedcoin
}
go ct.updateChart()

@ -0,0 +1,61 @@
package cointop
// Coin is the row structure
type Coin struct {
ID string
Name string
Slug string
Symbol string
Rank int
Price float64
Volume24H float64
MarketCap float64
AvailableSupply float64
TotalSupply float64
PercentChange1H float64
PercentChange24H float64
PercentChange7D float64
LastUpdated string
// for favorites
Favorite bool
// for portfolio
Holdings float64
Balance float64
}
func (ct *Cointop) allCoins() []*Coin {
if ct.State.filterByFavorites {
var list []*Coin
for i := range ct.State.allCoins {
coin := ct.State.allCoins[i]
if coin.Favorite {
list = append(list, coin)
}
}
return list
}
if ct.State.portfolioVisible {
var list []*Coin
for i := range ct.State.allCoins {
coin := ct.State.allCoins[i]
if ct.PortfolioEntryExists(coin) {
list = append(list, coin)
}
}
return list
}
return ct.State.allCoins
}
func (ct *Cointop) coinBySymbol(symbol string) *Coin {
for i := range ct.State.allCoins {
coin := ct.State.allCoins[i]
if coin.Symbol == symbol {
return coin
}
}
return nil
}

@ -24,80 +24,79 @@ import (
// ErrInvalidAPIChoice is error for invalid API choice
var ErrInvalidAPIChoice = errors.New("Invalid API choice")
// Cointop cointop
type Cointop struct {
g *gocui.Gui
apiChoice string
colorschemename string
colorscheme *Colorscheme
marketbarviewname string
marketbarview *gocui.View
chartview *gocui.View
chartviewname string
chartpoints [][]termui.Cell
chartranges []string
chartrangesmap map[string]time.Duration
selectedchartrange string
headersview *gocui.View
headerviewname string
tableview *gocui.View
tableviewname string
tablecolumnorder []string
table *table.Table
maxtablewidth int
portfoliovisible bool
visible bool
statusbarview *gocui.View
statusbarviewname string
sortdesc bool
sortby string
api api.Interface
allcoins []*Coin
coins []*Coin
allcoinsslugmap map[string]*Coin
page int
perpage int
refreshmux sync.Mutex
refreshRate time.Duration
refreshTicker *time.Ticker
forcerefresh chan bool
selectedcoin *Coin
actionsmap map[string]bool
shortcutkeys map[string]string
config config // toml config
configFilepath string
searchfield *gocui.View
searchfieldviewname string
searchfieldvisible bool
// Views are all views in cointop
type Views struct {
Chart *View
Header *View
Table *View
Marketbar *View
SearchField *View
Statusbar *View
Help *View
ConvertMenu *View
Input *View
PortfolioUpdateMenu *View
}
// State is the state preferences of cointop
type State struct {
allCoins []*Coin
allCoinsSlugMap map[string]*Coin
coins []*Coin
chartPoints [][]termui.Cell
currencyConversion string
convertMenuVisible bool
defaultView string
// DEPRECATED: favorites by 'symbol' is deprecated because of collisions.
favoritesbysymbol map[string]bool
favorites map[string]bool
filterByFavorites bool
savemux sync.Mutex
cache *cache.Cache
debug bool
helpview *gocui.View
helpviewname string
helpvisible bool
currencyconversion string
convertmenuview *gocui.View
convertmenuviewname string
convertmenuvisible bool
portfolio *portfolio
portfolioupdatemenuview *gocui.View
portfolioupdatemenuviewname string
portfolioupdatemenuvisible bool
inputview *gocui.View
inputviewname string
defaultView string
apiKeys *apiKeys
limiter <-chan time.Time
hideMarketbar bool
hideChart bool
hideStatusbar bool
onlyTable bool
favorites map[string]bool
filterByFavorites bool
helpVisible bool
hideMarketbar bool
hideChart bool
hideStatusbar bool
page int
perPage int
portfolio *Portfolio
portfolioVisible bool
portfolioUpdateMenuVisible bool
refreshRate time.Duration
searchFieldVisible bool
selectedCoin *Coin
selectedChartRange string
shortcutKeys map[string]string
sortDesc bool
sortBy string
onlyTable bool
}
// Cointop cointop
type Cointop struct {
g *gocui.Gui
actionsMap map[string]bool
apiKeys *APIKeys
cache *cache.Cache
config config // toml config
configFilepath string
api api.Interface
apiChoice string
chartRanges []string
chartRangesMap map[string]time.Duration
colorschemeName string
colorscheme *Colorscheme
debug bool
forceRefresh chan bool
limiter <-chan time.Time
maxTableWidth int
refreshMux sync.Mutex
refreshTicker *time.Ticker
saveMux sync.Mutex
State *State
table *table.Table
tableColumnOrder []string
Views *Views
}
// CoinMarketCap is API choice
@ -107,14 +106,14 @@ var CoinMarketCap = "coinmarketcap"
var CoinGecko = "coingecko"
// PortfolioEntry is portfolio entry
type portfolioEntry struct {
type PortfolioEntry struct {
Coin string
Holdings float64
}
// Portfolio is portfolio structure
type portfolio struct {
Entries map[string]*portfolioEntry
type Portfolio struct {
Entries map[string]*PortfolioEntry
}
// Config config options
@ -131,8 +130,8 @@ type Config struct {
RefreshRate *uint
}
// apiKeys is api keys structure
type apiKeys struct {
// APIKeys is api keys structure
type APIKeys struct {
cmc string
}
@ -154,25 +153,14 @@ func NewCointop(config *Config) *Cointop {
}
ct := &Cointop{
apiChoice: CoinGecko,
allcoinsslugmap: make(map[string]*Coin),
allcoins: []*Coin{},
sortby: "rank",
page: 0,
perpage: 100,
forcerefresh: make(chan bool),
maxtablewidth: 175,
actionsmap: actionsMap(),
shortcutkeys: defaultShortcuts(),
// DEPRECATED: favorites by 'symbol' is deprecated because of collisions. Kept for backward compatibility.
favoritesbysymbol: make(map[string]bool),
favorites: make(map[string]bool),
cache: cache.New(1*time.Minute, 2*time.Minute),
debug: debug,
configFilepath: configFilepath,
marketbarviewname: "market",
chartviewname: "chart",
chartranges: []string{
apiChoice: CoinGecko,
apiKeys: new(APIKeys),
forceRefresh: make(chan bool),
maxTableWidth: 175,
actionsMap: actionsMap(),
cache: cache.New(1*time.Minute, 2*time.Minute),
configFilepath: configFilepath,
chartRanges: []string{
"1H",
"6H",
"24H",
@ -185,7 +173,8 @@ func NewCointop(config *Config) *Cointop {
"YTD",
"All Time",
},
chartrangesmap: map[string]time.Duration{
debug: debug,
chartRangesMap: map[string]time.Duration{
"All Time": time.Duration(24 * 7 * 4 * 12 * 5 * time.Hour),
"YTD": time.Duration(1 * time.Second), // this will be calculated
"1Y": time.Duration(24 * 7 * 4 * 12 * time.Hour),
@ -198,10 +187,29 @@ func NewCointop(config *Config) *Cointop {
"6H": time.Duration(6 * time.Hour),
"1H": time.Duration(1 * time.Hour),
},
selectedchartrange: "7D",
headerviewname: "header",
tableviewname: "table",
tablecolumnorder: []string{
limiter: time.Tick(2 * time.Second),
State: &State{
allCoinsSlugMap: make(map[string]*Coin),
allCoins: []*Coin{},
currencyConversion: "USD",
// DEPRECATED: favorites by 'symbol' is deprecated because of collisions. Kept for backward compatibility.
favoritesbysymbol: make(map[string]bool),
favorites: make(map[string]bool),
hideMarketbar: config.HideMarketbar,
hideChart: config.HideChart,
hideStatusbar: config.HideStatusbar,
onlyTable: config.OnlyTable,
refreshRate: 60 * time.Second,
selectedChartRange: "7D",
shortcutKeys: defaultShortcuts(),
sortBy: "rank",
page: 0,
perPage: 100,
portfolio: &Portfolio{
Entries: make(map[string]*PortfolioEntry, 0),
},
},
tableColumnOrder: []string{
"rank",
"name",
"symbol",
@ -217,23 +225,38 @@ func NewCointop(config *Config) *Cointop {
"percentholdings",
"lastupdated",
},
statusbarviewname: "statusbar",
searchfieldviewname: "searchfield",
helpviewname: "help",
convertmenuviewname: "convertmenu",
currencyconversion: "USD",
portfolio: &portfolio{
Entries: make(map[string]*portfolioEntry, 0),
Views: &Views{
Chart: &View{
Name: "chart",
},
Header: &View{
Name: "header",
},
Table: &View{
Name: "table",
},
Marketbar: &View{
Name: "marketbar",
},
SearchField: &View{
Name: "searchfield",
},
Statusbar: &View{
Name: "statusbar",
},
Help: &View{
Name: "help",
},
ConvertMenu: &View{
Name: "convert",
},
Input: &View{
Name: "input",
},
PortfolioUpdateMenu: &View{
Name: "portfolioupdatemenu",
},
},
portfolioupdatemenuviewname: "portfolioupdatemenu",
inputviewname: "input",
apiKeys: new(apiKeys),
limiter: time.Tick(2 * time.Second),
hideMarketbar: config.HideMarketbar,
hideChart: config.HideChart,
hideStatusbar: config.HideStatusbar,
onlyTable: config.OnlyTable,
refreshRate: 60 * time.Second,
}
err := ct.setupConfig()
@ -241,20 +264,20 @@ func NewCointop(config *Config) *Cointop {
log.Fatal(err)
}
ct.cache.Set("onlyTable", ct.onlyTable, cache.NoExpiration)
ct.cache.Set("hideMarketbar", ct.hideMarketbar, cache.NoExpiration)
ct.cache.Set("hideChart", ct.hideChart, cache.NoExpiration)
ct.cache.Set("hideStatusbar", ct.hideStatusbar, cache.NoExpiration)
ct.cache.Set("onlyTable", ct.State.onlyTable, cache.NoExpiration)
ct.cache.Set("hideMarketbar", ct.State.hideMarketbar, cache.NoExpiration)
ct.cache.Set("hideChart", ct.State.hideChart, cache.NoExpiration)
ct.cache.Set("hideStatusbar", ct.State.hideStatusbar, cache.NoExpiration)
if config.RefreshRate != nil {
ct.refreshRate = time.Duration(*config.RefreshRate) * time.Second
ct.State.refreshRate = time.Duration(*config.RefreshRate) * time.Second
}
if ct.refreshRate == 0 {
if ct.State.refreshRate == 0 {
ct.refreshTicker = time.NewTicker(time.Duration(1))
ct.refreshTicker.Stop()
} else {
ct.refreshTicker = time.NewTicker(ct.refreshRate)
ct.refreshTicker = time.NewTicker(ct.State.refreshRate)
}
// prompt for CoinMarketCap api key if not found
@ -266,7 +289,7 @@ func NewCointop(config *Config) *Cointop {
}
if config.Colorscheme != "" {
ct.colorschemename = config.Colorscheme
ct.colorschemeName = config.Colorscheme
}
colors, err := ct.getColorschemeColors()
@ -297,7 +320,7 @@ func NewCointop(config *Config) *Cointop {
}
if ct.apiChoice == CoinGecko {
ct.selectedchartrange = "1Y"
ct.State.selectedChartRange = "1Y"
}
if ct.apiChoice == CoinMarketCap {
@ -308,35 +331,35 @@ func NewCointop(config *Config) *Cointop {
log.Fatal(ErrInvalidAPIChoice)
}
coinscachekey := ct.cacheKey("allcoinsslugmap")
filecache.Get(coinscachekey, &ct.allcoinsslugmap)
coinscachekey := ct.cacheKey("allCoinsSlugMap")
filecache.Get(coinscachekey, &ct.State.allCoinsSlugMap)
for k := range ct.allcoinsslugmap {
ct.allcoins = append(ct.allcoins, ct.allcoinsslugmap[k])
for k := range ct.State.allCoinsSlugMap {
ct.State.allCoins = append(ct.State.allCoins, ct.State.allCoinsSlugMap[k])
}
if len(ct.allcoins) > 1 {
max := len(ct.allcoins)
if len(ct.State.allCoins) > 1 {
max := len(ct.State.allCoins)
if max > 100 {
max = 100
}
ct.sort(ct.sortby, ct.sortdesc, ct.allcoins, false)
ct.coins = ct.allcoins[0:max]
ct.sort(ct.State.sortBy, ct.State.sortDesc, ct.State.allCoins, false)
ct.State.coins = ct.State.allCoins[0:max]
}
// DEPRECATED: favorites by 'symbol' is deprecated because of collisions. Kept for backward compatibility.
// Here we're doing a lookup based on symbol and setting the favorite to the coin name instead of coin symbol.
for i := range ct.allcoinsslugmap {
coin := ct.allcoinsslugmap[i]
for k := range ct.favoritesbysymbol {
for i := range ct.State.allCoinsSlugMap {
coin := ct.State.allCoinsSlugMap[i]
for k := range ct.State.favoritesbysymbol {
if coin.Symbol == k {
ct.favorites[coin.Name] = true
delete(ct.favoritesbysymbol, k)
ct.State.favorites[coin.Name] = true
delete(ct.State.favoritesbysymbol, k)
}
}
}
var globaldata []float64
chartcachekey := ct.cacheKey(fmt.Sprintf("%s_%s", "globaldata", strings.Replace(ct.selectedchartrange, " ", "", -1)))
chartcachekey := ct.cacheKey(fmt.Sprintf("%s_%s", "globaldata", strings.Replace(ct.State.selectedChartRange, " ", "", -1)))
filecache.Get(chartcachekey, &globaldata)
ct.cache.Set(chartcachekey, globaldata, 10*time.Second)

@ -8,6 +8,8 @@ import (
xtermcolor "github.com/tomnomnom/xtermcolor"
)
// TODO: fix hex color support
// colorschemeColors ..
type colorschemeColors map[string]interface{}

@ -125,8 +125,8 @@ func (ct *Cointop) makeConfigFile() error {
}
func (ct *Cointop) saveConfig() error {
ct.savemux.Lock()
defer ct.savemux.Unlock()
ct.saveMux.Lock()
defer ct.saveMux.Unlock()
path := ct.configPath()
if _, err := os.Stat(path); err == nil {
b, err := ct.configToToml()
@ -154,13 +154,13 @@ func (ct *Cointop) parseConfig() error {
func (ct *Cointop) configToToml() ([]byte, error) {
shortcutsIfcs := map[string]interface{}{}
for k, v := range ct.shortcutkeys {
for k, v := range ct.State.shortcutKeys {
var i interface{} = v
shortcutsIfcs[k] = i
}
var favorites []interface{}
for k, ok := range ct.favorites {
for k, ok := range ct.State.favorites {
if ok {
var i interface{} = k
favorites = append(favorites, i)
@ -174,8 +174,8 @@ func (ct *Cointop) configToToml() ([]byte, error) {
}
portfolioIfc := map[string]interface{}{}
for name := range ct.portfolio.Entries {
entry, ok := ct.portfolio.Entries[name]
for name := range ct.State.portfolio.Entries {
entry, ok := ct.State.portfolio.Entries[name]
if !ok || entry.Coin == "" {
continue
}
@ -183,10 +183,10 @@ func (ct *Cointop) configToToml() ([]byte, error) {
portfolioIfc[entry.Coin] = i
}
var currencyIfc interface{} = ct.currencyconversion
var defaultViewIfc interface{} = ct.defaultView
var colorschemeIfc interface{} = ct.colorschemename
var refreshRateIfc interface{} = uint(ct.refreshRate.Seconds())
var currencyIfc interface{} = ct.State.currencyConversion
var defaultViewIfc interface{} = ct.State.defaultView
var colorschemeIfc interface{} = ct.colorschemeName
var refreshRateIfc interface{} = uint(ct.State.refreshRate.Seconds())
cmcIfc := map[string]interface{}{
"pro_api_key": ct.apiKeys.cmc,
@ -221,10 +221,10 @@ func (ct *Cointop) loadShortcutsFromConfig() error {
if !ct.actionExists(v) {
continue
}
if ct.shortcutkeys[k] == "" {
if ct.State.shortcutKeys[k] == "" {
continue
}
ct.shortcutkeys[k] = v
ct.State.shortcutKeys[k] = v
}
}
return nil
@ -232,7 +232,7 @@ func (ct *Cointop) loadShortcutsFromConfig() error {
func (ct *Cointop) loadCurrencyFromConfig() error {
if currency, ok := ct.config.Currency.(string); ok {
ct.currencyconversion = strings.ToUpper(currency)
ct.State.currencyConversion = strings.ToUpper(currency)
}
return nil
}
@ -242,17 +242,17 @@ func (ct *Cointop) loadDefaultViewFromConfig() error {
defaultView = strings.ToLower(defaultView)
switch defaultView {
case "portfolio":
ct.portfoliovisible = true
ct.State.portfolioVisible = true
case "favorites":
ct.filterByFavorites = true
ct.State.filterByFavorites = true
case "default":
fallthrough
default:
ct.portfoliovisible = false
ct.filterByFavorites = false
ct.State.portfolioVisible = false
ct.State.filterByFavorites = false
defaultView = "default"
}
ct.defaultView = defaultView
ct.State.defaultView = defaultView
}
return nil
}
@ -269,7 +269,7 @@ func (ct *Cointop) loadAPIKeysFromConfig() error {
func (ct *Cointop) loadColorschemeFromConfig() error {
if colorscheme, ok := ct.config.Colorscheme.(string); ok {
ct.colorschemename = colorscheme
ct.colorschemeName = colorscheme
}
return nil
@ -277,7 +277,7 @@ func (ct *Cointop) loadColorschemeFromConfig() error {
func (ct *Cointop) loadRefreshRateFromConfig() error {
if refreshRate, ok := ct.config.RefreshRate.(int64); ok {
ct.refreshRate = time.Duration(uint(refreshRate)) * time.Second
ct.State.refreshRate = time.Duration(uint(refreshRate)) * time.Second
}
return nil
@ -285,16 +285,16 @@ func (ct *Cointop) loadRefreshRateFromConfig() error {
func (ct *Cointop) getColorschemeColors() (map[string]interface{}, error) {
var colors map[string]interface{}
if ct.colorschemename == "" {
ct.colorschemename = defaultColorscheme
if ct.colorschemeName == "" {
ct.colorschemeName = defaultColorscheme
if _, err := toml.Decode(DefaultColors, &colors); err != nil {
return nil, err
}
} else {
path := normalizePath(fmt.Sprintf("~/.cointop/colors/%s.toml", ct.colorschemename))
path := normalizePath(fmt.Sprintf("~/.cointop/colors/%s.toml", ct.colorschemeName))
if _, err := os.Stat(path); os.IsNotExist(err) {
// NOTE: case for when cointop is set as the theme but the colorscheme file doesn't exist
if ct.colorschemename == "cointop" {
if ct.colorschemeName == "cointop" {
if _, err := toml.Decode(DefaultColors, &colors); err != nil {
return nil, err
}
@ -328,13 +328,13 @@ func (ct *Cointop) loadFavoritesFromConfig() error {
if k == "symbols" {
for _, ifc := range arr {
if v, ok := ifc.(string); ok {
ct.favoritesbysymbol[strings.ToUpper(v)] = true
ct.State.favoritesbysymbol[strings.ToUpper(v)] = true
}
}
} else if k == "names" {
for _, ifc := range arr {
if v, ok := ifc.(string); ok {
ct.favorites[v] = true
ct.State.favorites[v] = true
}
}
}

@ -119,19 +119,11 @@ func (ct *Cointop) sortedSupportedCurrencyConversions() []string {
return keys
}
func (ct *Cointop) toggleConvertMenu() error {
ct.convertmenuvisible = !ct.convertmenuvisible
if ct.convertmenuvisible {
return ct.showConvertMenu()
}
return ct.hideConvertMenu()
}
func (ct *Cointop) updateConvertMenu() {
header := ct.colorscheme.MenuHeader(fmt.Sprintf(" Currency Conversion %s\n\n", pad.Left("[q] close menu ", ct.maxtablewidth-20, " ")))
header := ct.colorscheme.MenuHeader(fmt.Sprintf(" Currency Conversion %s\n\n", pad.Left("[q] close menu ", ct.maxTableWidth-20, " ")))
helpline := " Press the corresponding key to select currency for conversion\n\n"
cnt := 0
h := ct.viewHeight(ct.convertmenuviewname)
h := ct.viewHeight(ct.Views.ConvertMenu.Name)
percol := h - 5
cols := make([][]string, percol)
for i := range cols {
@ -146,7 +138,7 @@ func (ct *Cointop) updateConvertMenu() {
cnt = 0
}
shortcut := string(alphanumericcharacters[i])
if key == ct.currencyconversion {
if key == ct.State.currencyConversion {
shortcut = ct.colorscheme.MenuLabelActive(color.Bold("*"))
key = ct.colorscheme.Menu(color.Bold(key))
currency = ct.colorscheme.MenuLabelActive(color.Bold(currency))
@ -171,53 +163,61 @@ func (ct *Cointop) updateConvertMenu() {
content := fmt.Sprintf("%s%s%s", header, helpline, body)
ct.update(func() {
if ct.convertmenuview == nil {
if ct.Views.ConvertMenu.Backing == nil {
return
}
ct.convertmenuview.Clear()
ct.convertmenuview.Frame = true
fmt.Fprintln(ct.convertmenuview, content)
ct.Views.ConvertMenu.Backing.Clear()
ct.Views.ConvertMenu.Backing.Frame = true
fmt.Fprintln(ct.Views.ConvertMenu.Backing, content)
})
}
func (ct *Cointop) setCurrencyConverstion(convert string) func() error {
return func() error {
ct.State.currencyConversion = convert
ct.hideConvertMenu()
go ct.refreshAll()
return nil
}
}
func (ct *Cointop) currencySymbol() string {
symbol, ok := currencySymbol[ct.State.currencyConversion]
if ok {
return symbol
}
return "$"
}
func (ct *Cointop) showConvertMenu() error {
ct.convertmenuvisible = true
ct.State.convertMenuVisible = true
ct.updateConvertMenu()
ct.setActiveView(ct.convertmenuviewname)
ct.setActiveView(ct.Views.ConvertMenu.Name)
return nil
}
func (ct *Cointop) hideConvertMenu() error {
ct.convertmenuvisible = false
ct.setViewOnBottom(ct.convertmenuviewname)
ct.setActiveView(ct.tableviewname)
ct.State.convertMenuVisible = false
ct.setViewOnBottom(ct.Views.ConvertMenu.Name)
ct.setActiveView(ct.Views.Table.Name)
ct.update(func() {
if ct.convertmenuview == nil {
if ct.Views.ConvertMenu.Backing == nil {
return
}
ct.convertmenuview.Clear()
ct.convertmenuview.Frame = false
fmt.Fprintln(ct.convertmenuview, "")
ct.Views.ConvertMenu.Backing.Clear()
ct.Views.ConvertMenu.Backing.Frame = false
fmt.Fprintln(ct.Views.ConvertMenu.Backing, "")
})
return nil
}
func (ct *Cointop) setCurrencyConverstion(convert string) func() error {
return func() error {
ct.currencyconversion = convert
ct.hideConvertMenu()
go ct.refreshAll()
return nil
}
}
func (ct *Cointop) currencySymbol() string {
symbol, ok := currencySymbol[ct.currencyconversion]
if ok {
return symbol
func (ct *Cointop) toggleConvertMenu() error {
ct.State.convertMenuVisible = !ct.State.convertMenuVisible
if ct.State.convertMenuVisible {
return ct.showConvertMenu()
}
return "$"
return ct.hideConvertMenu()
}

@ -1,17 +1,17 @@
package cointop
func (ct *Cointop) toggleFavorite() error {
ct.portfoliovisible = false
ct.State.portfolioVisible = false
coin := ct.highlightedRowCoin()
if coin == nil {
return nil
}
_, ok := ct.favorites[coin.Name]
_, ok := ct.State.favorites[coin.Name]
if ok {
delete(ct.favorites, coin.Name)
delete(ct.State.favorites, coin.Name)
coin.Favorite = false
} else {
ct.favorites[coin.Name] = true
ct.State.favorites[coin.Name] = true
coin.Favorite = true
}
go ct.updateTable()
@ -19,8 +19,8 @@ func (ct *Cointop) toggleFavorite() error {
}
func (ct *Cointop) toggleShowFavorites() error {
ct.portfoliovisible = false
ct.filterByFavorites = !ct.filterByFavorites
ct.State.portfolioVisible = false
ct.State.filterByFavorites = !ct.State.filterByFavorites
go ct.updateTable()
return nil
}

@ -7,31 +7,23 @@ import (
"github.com/miguelmota/cointop/cointop/common/pad"
)
func (ct *Cointop) toggleHelp() error {
ct.helpvisible = !ct.helpvisible
if ct.helpvisible {
return ct.showHelp()
}
return ct.hideHelp()
}
func (ct *Cointop) updateHelp() {
keys := make([]string, 0, len(ct.shortcutkeys))
for k := range ct.shortcutkeys {
keys := make([]string, 0, len(ct.State.shortcutKeys))
for k := range ct.State.shortcutKeys {
keys = append(keys, k)
}
sort.Strings(keys)
header := ct.colorscheme.MenuHeader(fmt.Sprintf(" Help %s\n\n", pad.Left("[q] close ", ct.maxtablewidth-10, " ")))
header := ct.colorscheme.MenuHeader(fmt.Sprintf(" Help %s\n\n", pad.Left("[q] close ", ct.maxTableWidth-10, " ")))
cnt := 0
h := ct.viewHeight(ct.helpviewname)
h := ct.viewHeight(ct.Views.Help.Name)
percol := h - 6
cols := make([][]string, percol)
for i := range cols {
cols[i] = make([]string, 20)
}
for _, k := range keys {
v := ct.shortcutkeys[k]
v := ct.State.shortcutKeys[k]
if cnt%percol == 0 {
cnt = 0
}
@ -52,39 +44,47 @@ func (ct *Cointop) updateHelp() {
body = fmt.Sprintf("%s\n", body)
infoline := " List of keyboard shortcuts\n\n"
versionline := pad.Left(fmt.Sprintf("v%s", ct.version()), ct.maxtablewidth-5, " ")
versionline := pad.Left(fmt.Sprintf("v%s", ct.version()), ct.maxTableWidth-5, " ")
content := header + infoline + body + versionline
ct.update(func() {
if ct.helpview == nil {
if ct.Views.Help.Backing == nil {
return
}
ct.helpview.Clear()
ct.helpview.Frame = true
fmt.Fprintln(ct.helpview, content)
ct.Views.Help.Backing.Clear()
ct.Views.Help.Backing.Frame = true
fmt.Fprintln(ct.Views.Help.Backing, content)
})
}
func (ct *Cointop) showHelp() error {
ct.helpvisible = true
ct.State.helpVisible = true
ct.updateHelp()
ct.setActiveView(ct.helpviewname)
ct.setActiveView(ct.Views.Help.Name)
return nil
}
func (ct *Cointop) hideHelp() error {
ct.helpvisible = false
ct.setViewOnBottom(ct.helpviewname)
ct.setActiveView(ct.tableviewname)
ct.State.helpVisible = false
ct.setViewOnBottom(ct.Views.Help.Name)
ct.setActiveView(ct.Views.Table.Name)
ct.update(func() {
if ct.helpview == nil {
if ct.Views.Help.Backing == nil {
return
}
ct.helpview.Clear()
ct.helpview.Frame = false
fmt.Fprintln(ct.helpview, "")
ct.Views.Help.Backing.Clear()
ct.Views.Help.Backing.Frame = false
fmt.Fprintln(ct.Views.Help.Backing, "")
})
return nil
}
func (ct *Cointop) toggleHelp() error {
ct.State.helpVisible = !ct.State.helpVisible
if ct.State.helpVisible {
return ct.showHelp()
}
return ct.hideHelp()
}

@ -207,7 +207,7 @@ func (ct *Cointop) parseKeys(s string) (interface{}, gocui.Modifier) {
}
func (ct *Cointop) keybindings(g *gocui.Gui) error {
for k, v := range ct.shortcutkeys {
for k, v := range ct.State.shortcutKeys {
if k == "" {
continue
}
@ -345,29 +345,29 @@ func (ct *Cointop) keybindings(g *gocui.Gui) error {
ct.setKeybindingMod(gocui.KeyCtrlZ, gocui.ModNone, ct.keyfn(ct.quit), "")
// searchfield keys
ct.setKeybindingMod(gocui.KeyEnter, gocui.ModNone, ct.keyfn(ct.doSearch), ct.searchfieldviewname)
ct.setKeybindingMod(gocui.KeyEsc, gocui.ModNone, ct.keyfn(ct.cancelSearch), ct.searchfieldviewname)
ct.setKeybindingMod(gocui.KeyEnter, gocui.ModNone, ct.keyfn(ct.doSearch), ct.Views.SearchField.Name)
ct.setKeybindingMod(gocui.KeyEsc, gocui.ModNone, ct.keyfn(ct.cancelSearch), ct.Views.SearchField.Name)
// keys to quit help when open
ct.setKeybindingMod(gocui.KeyEsc, gocui.ModNone, ct.keyfn(ct.hideHelp), ct.helpviewname)
ct.setKeybindingMod('q', gocui.ModNone, ct.keyfn(ct.hideHelp), ct.helpviewname)
ct.setKeybindingMod(gocui.KeyEsc, gocui.ModNone, ct.keyfn(ct.hideHelp), ct.Views.Help.Name)
ct.setKeybindingMod('q', gocui.ModNone, ct.keyfn(ct.hideHelp), ct.Views.Help.Name)
// keys to quit portfolio update menu when open
ct.setKeybindingMod(gocui.KeyEsc, gocui.ModNone, ct.keyfn(ct.hidePortfolioUpdateMenu), ct.inputviewname)
ct.setKeybindingMod('q', gocui.ModNone, ct.keyfn(ct.hidePortfolioUpdateMenu), ct.inputviewname)
ct.setKeybindingMod(gocui.KeyEsc, gocui.ModNone, ct.keyfn(ct.hidePortfolioUpdateMenu), ct.Views.Input.Name)
ct.setKeybindingMod('q', gocui.ModNone, ct.keyfn(ct.hidePortfolioUpdateMenu), ct.Views.Input.Name)
// keys to update portfolio holdings
ct.setKeybindingMod(gocui.KeyEnter, gocui.ModNone, ct.keyfn(ct.setPortfolioHoldings), ct.inputviewname)
ct.setKeybindingMod(gocui.KeyEnter, gocui.ModNone, ct.keyfn(ct.setPortfolioHoldings), ct.Views.Input.Name)
// keys to quit convert menu when open
ct.setKeybindingMod(gocui.KeyEsc, gocui.ModNone, ct.keyfn(ct.hideConvertMenu), ct.convertmenuviewname)
ct.setKeybindingMod('q', gocui.ModNone, ct.keyfn(ct.hideConvertMenu), ct.convertmenuviewname)
ct.setKeybindingMod(gocui.KeyEsc, gocui.ModNone, ct.keyfn(ct.hideConvertMenu), ct.Views.ConvertMenu.Name)
ct.setKeybindingMod('q', gocui.ModNone, ct.keyfn(ct.hideConvertMenu), ct.Views.ConvertMenu.Name)
// character key press to select option
// TODO: use scrolling table
keys := ct.sortedSupportedCurrencyConversions()
for i, k := range keys {
ct.setKeybindingMod(rune(alphanumericcharacters[i]), gocui.ModNone, ct.keyfn(ct.setCurrencyConverstion(k)), ct.convertmenuviewname)
ct.setKeybindingMod(rune(alphanumericcharacters[i]), gocui.ModNone, ct.keyfn(ct.setCurrencyConverstion(k)), ct.Views.ConvertMenu.Name)
}
return nil
@ -392,7 +392,7 @@ func (ct *Cointop) keyfn(fn func() error) func(g *gocui.Gui, v *gocui.View) erro
func (ct *Cointop) handleHkey(key interface{}) func(g *gocui.Gui, v *gocui.View) error {
return func(g *gocui.Gui, v *gocui.View) error {
if k, ok := key.(rune); ok && k == 'h' && ct.portfoliovisible {
if k, ok := key.(rune); ok && k == 'h' && ct.State.portfolioVisible {
ct.sortToggle("holdings", true)
} else {
ct.prevPage()

@ -7,6 +7,8 @@ import (
"github.com/jroimartin/gocui"
)
// TODO: break up into small functions
// layout sets initial layout
func (ct *Cointop) layout(g *gocui.Gui) error {
maxX, maxY := ct.size()
@ -17,63 +19,63 @@ func (ct *Cointop) layout(g *gocui.Gui) error {
chartHeight := 10
statusbarHeight := 1
if ct.onlyTable {
ct.hideMarketbar = true
ct.hideChart = true
ct.hideStatusbar = true
if ct.State.onlyTable {
ct.State.hideMarketbar = true
ct.State.hideChart = true
ct.State.hideStatusbar = true
}
if ct.hideMarketbar {
if ct.State.hideMarketbar {
marketbarHeight = 0
}
if ct.hideChart {
if ct.State.hideChart {
chartHeight = 0
}
if ct.hideStatusbar {
if ct.State.hideStatusbar {
statusbarHeight = 0
}
if !ct.hideMarketbar {
if v, err := g.SetView(ct.marketbarviewname, 0, topOffset, maxX, 2); err != nil {
if !ct.State.hideMarketbar {
if v, err := g.SetView(ct.Views.Marketbar.Name, 0, topOffset, maxX, 2); err != nil {
if err != gocui.ErrUnknownView {
return err
}
ct.marketbarview = v
ct.marketbarview.Frame = false
ct.colorscheme.SetViewColor(ct.marketbarview, "marketbar")
ct.Views.Marketbar.Backing = v
ct.Views.Marketbar.Backing.Frame = false
ct.colorscheme.SetViewColor(ct.Views.Marketbar.Backing, "marketbar")
go func() {
ct.updateMarketbar()
_, found := ct.cache.Get(ct.marketbarviewname)
_, found := ct.cache.Get(ct.Views.Marketbar.Name)
if found {
ct.cache.Delete(ct.marketbarviewname)
ct.cache.Delete(ct.Views.Marketbar.Name)
ct.updateMarketbar()
}
}()
}
} else {
if ct.marketbarview != nil {
if err := g.DeleteView(ct.marketbarviewname); err != nil {
if ct.Views.Marketbar.Backing != nil {
if err := g.DeleteView(ct.Views.Marketbar.Name); err != nil {
return err
}
ct.marketbarview = nil
ct.Views.Marketbar.Backing = nil
}
}
topOffset = topOffset + marketbarHeight
if !ct.hideChart {
if v, err := g.SetView(ct.chartviewname, 0, topOffset, maxX, topOffset+chartHeight+marketbarHeight); err != nil {
if !ct.State.hideChart {
if v, err := g.SetView(ct.Views.Chart.Name, 0, topOffset, maxX, topOffset+chartHeight+marketbarHeight); err != nil {
if err != gocui.ErrUnknownView {
return err
}
ct.chartview = v
ct.chartview.Frame = false
ct.colorscheme.SetViewColor(ct.chartview, "chart")
ct.Views.Chart.Backing = v
ct.Views.Chart.Backing.Frame = false
ct.colorscheme.SetViewColor(ct.Views.Chart.Backing, "chart")
go func() {
ct.updateChart()
cachekey := strings.ToLower(fmt.Sprintf("%s_%s", "globaldata", strings.Replace(ct.selectedchartrange, " ", "", -1)))
cachekey := strings.ToLower(fmt.Sprintf("%s_%s", "globaldata", strings.Replace(ct.State.selectedChartRange, " ", "", -1)))
_, found := ct.cache.Get(cachekey)
if found {
ct.cache.Delete(cachekey)
@ -82,37 +84,37 @@ func (ct *Cointop) layout(g *gocui.Gui) error {
}()
}
} else {
if ct.chartview != nil {
if err := g.DeleteView(ct.chartviewname); err != nil {
if ct.Views.Chart.Backing != nil {
if err := g.DeleteView(ct.Views.Chart.Name); err != nil {
return err
}
ct.chartview = nil
ct.Views.Chart.Backing = nil
}
}
topOffset = topOffset + chartHeight
if v, err := g.SetView(ct.headerviewname, 0, topOffset, ct.maxtablewidth, topOffset+2); err != nil {
if v, err := g.SetView(ct.Views.Header.Name, 0, topOffset, ct.maxTableWidth, topOffset+2); err != nil {
if err != gocui.ErrUnknownView {
return err
}
ct.headersview = v
ct.headersview.Frame = false
ct.colorscheme.SetViewColor(ct.headersview, "table_header")
ct.Views.Header.Backing = v
ct.Views.Header.Backing.Frame = false
ct.colorscheme.SetViewColor(ct.Views.Header.Backing, "table_header")
go ct.updateHeaders()
}
topOffset = topOffset + headerHeight
if v, err := g.SetView(ct.tableviewname, 0, topOffset, ct.maxtablewidth, maxY-statusbarHeight); err != nil {
if v, err := g.SetView(ct.Views.Table.Name, 0, topOffset, ct.maxTableWidth, maxY-statusbarHeight); err != nil {
if err != gocui.ErrUnknownView {
return err
}
ct.tableview = v
ct.tableview.Frame = false
ct.tableview.Highlight = true
ct.colorscheme.SetViewActiveColor(ct.tableview, "table_row_active")
_, found := ct.cache.Get("allcoinsslugmap")
ct.Views.Table.Backing = v
ct.Views.Table.Backing.Frame = false
ct.Views.Table.Backing.Highlight = true
ct.colorscheme.SetViewActiveColor(ct.Views.Table.Backing, "table_row_active")
_, found := ct.cache.Get("allCoinsSlugMap")
if found {
ct.cache.Delete("allcoinsslugmap")
ct.cache.Delete("allCoinsSlugMap")
}
go func() {
ct.updateCoins()
@ -120,123 +122,84 @@ func (ct *Cointop) layout(g *gocui.Gui) error {
}()
}
if !ct.hideStatusbar {
if v, err := g.SetView(ct.statusbarviewname, 0, maxY-statusbarHeight-1, ct.maxtablewidth, maxY); err != nil {
if !ct.State.hideStatusbar {
if v, err := g.SetView(ct.Views.Statusbar.Name, 0, maxY-statusbarHeight-1, ct.maxTableWidth, maxY); err != nil {
if err != gocui.ErrUnknownView {
return err
}
ct.statusbarview = v
ct.statusbarview.Frame = false
ct.colorscheme.SetViewColor(ct.statusbarview, "statusbar")
ct.Views.Statusbar.Backing = v
ct.Views.Statusbar.Backing.Frame = false
ct.colorscheme.SetViewColor(ct.Views.Statusbar.Backing, "statusbar")
go ct.updateStatusbar("")
}
} else {
if ct.statusbarview != nil {
if err := g.DeleteView(ct.statusbarviewname); err != nil {
if ct.Views.Statusbar.Backing != nil {
if err := g.DeleteView(ct.Views.Statusbar.Name); err != nil {
return err
}
ct.statusbarview = nil
ct.Views.Statusbar.Backing = nil
}
}
if v, err := g.SetView(ct.searchfieldviewname, 0, maxY-2, ct.maxtablewidth, maxY); err != nil {
if v, err := g.SetView(ct.Views.SearchField.Name, 0, maxY-2, ct.maxTableWidth, maxY); err != nil {
if err != gocui.ErrUnknownView {
return err
}
ct.searchfield = v
ct.searchfield.Editable = true
ct.searchfield.Wrap = true
ct.searchfield.Frame = false
ct.colorscheme.SetViewColor(ct.searchfield, "searchbar")
ct.Views.SearchField.Backing = v
ct.Views.SearchField.Backing.Editable = true
ct.Views.SearchField.Backing.Wrap = true
ct.Views.SearchField.Backing.Frame = false
ct.colorscheme.SetViewColor(ct.Views.SearchField.Backing, "searchbar")
}
if v, err := g.SetView(ct.helpviewname, 1, 1, ct.maxtablewidth-1, maxY-1); err != nil {
if v, err := g.SetView(ct.Views.Help.Name, 1, 1, ct.maxTableWidth-1, maxY-1); err != nil {
if err != gocui.ErrUnknownView {
return err
}
ct.helpview = v
ct.helpview.Frame = false
ct.colorscheme.SetViewColor(ct.helpview, "menu")
ct.Views.Help.Backing = v
ct.Views.Help.Backing.Frame = false
ct.colorscheme.SetViewColor(ct.Views.Help.Backing, "menu")
}
if v, err := g.SetView(ct.portfolioupdatemenuviewname, 1, 1, ct.maxtablewidth-1, maxY-1); err != nil {
if v, err := g.SetView(ct.Views.PortfolioUpdateMenu.Name, 1, 1, ct.maxTableWidth-1, maxY-1); err != nil {
if err != gocui.ErrUnknownView {
return err
}
ct.portfolioupdatemenuview = v
ct.portfolioupdatemenuview.Frame = false
ct.colorscheme.SetViewColor(ct.portfolioupdatemenuview, "menu")
ct.Views.PortfolioUpdateMenu.Backing = v
ct.Views.PortfolioUpdateMenu.Backing.Frame = false
ct.colorscheme.SetViewColor(ct.Views.PortfolioUpdateMenu.Backing, "menu")
}
if v, err := g.SetView(ct.inputviewname, 3, 6, 30, 8); err != nil {
if v, err := g.SetView(ct.Views.Input.Name, 3, 6, 30, 8); err != nil {
if err != gocui.ErrUnknownView {
return err
}
ct.inputview = v
ct.inputview.Frame = true
ct.inputview.Editable = true
ct.inputview.Wrap = true
ct.colorscheme.SetViewColor(ct.inputview, "menu")
ct.Views.Input.Backing = v
ct.Views.Input.Backing.Frame = true
ct.Views.Input.Backing.Editable = true
ct.Views.Input.Backing.Wrap = true
ct.colorscheme.SetViewColor(ct.Views.Input.Backing, "menu")
}
if v, err := g.SetView(ct.convertmenuviewname, 1, 1, ct.maxtablewidth-1, maxY-1); err != nil {
if v, err := g.SetView(ct.Views.ConvertMenu.Name, 1, 1, ct.maxTableWidth-1, maxY-1); err != nil {
if err != gocui.ErrUnknownView {
return err
}
ct.convertmenuview = v
ct.convertmenuview.Frame = false
ct.colorscheme.SetViewColor(ct.convertmenuview, "menu")
ct.Views.ConvertMenu.Backing = v
ct.Views.ConvertMenu.Backing.Frame = false
ct.colorscheme.SetViewColor(ct.Views.ConvertMenu.Backing, "menu")
// run only once on init.
// this bit of code should be at the bottom
ct.g = g
g.SetViewOnBottom(ct.searchfieldviewname) // hide
g.SetViewOnBottom(ct.helpviewname) // hide
g.SetViewOnBottom(ct.convertmenuviewname) // hide
g.SetViewOnBottom(ct.portfolioupdatemenuviewname) // hide
g.SetViewOnBottom(ct.inputviewname) // hide
ct.setActiveView(ct.tableviewname)
g.SetViewOnBottom(ct.Views.SearchField.Name) // hide
g.SetViewOnBottom(ct.Views.Help.Name) // hide
g.SetViewOnBottom(ct.Views.ConvertMenu.Name) // hide
g.SetViewOnBottom(ct.Views.PortfolioUpdateMenu.Name) // hide
g.SetViewOnBottom(ct.Views.Input.Name) // hide
ct.setActiveView(ct.Views.Table.Name)
ct.intervalFetchData()
}
return nil
}
func (ct *Cointop) setActiveView(v string) error {
ct.g.SetViewOnTop(v)
ct.g.SetCurrentView(v)
if v == ct.searchfieldviewname {
ct.searchfield.Clear()
ct.searchfield.SetCursor(1, 0)
fmt.Fprintf(ct.searchfield, "%s", "/")
} else if v == ct.tableviewname {
ct.g.SetViewOnTop(ct.statusbarviewname)
}
if v == ct.portfolioupdatemenuviewname {
ct.g.SetViewOnTop(ct.inputviewname)
ct.g.SetCurrentView(ct.inputviewname)
}
return nil
}
func (ct *Cointop) activeViewName() string {
return ct.g.CurrentView().Name()
}
func (ct *Cointop) setViewOnBottom(v string) error {
_, err := ct.g.SetViewOnBottom(v)
return err
}
func (ct *Cointop) intervalFetchData() {
go func() {
for {
select {
case <-ct.forcerefresh:
ct.refreshAll()
case <-ct.refreshTicker.C:
ct.refreshAll()
}
}
}()
}

@ -14,23 +14,23 @@ var updatecoinsmux sync.Mutex
func (ct *Cointop) updateCoins() error {
coinslock.Lock()
defer coinslock.Unlock()
cachekey := ct.cacheKey("allcoinsslugmap")
cachekey := ct.cacheKey("allCoinsSlugMap")
var err error
var allcoinsslugmap map[string]types.Coin
var allCoinsSlugMap map[string]types.Coin
cached, found := ct.cache.Get(cachekey)
_ = cached
if found {
// cache hit
allcoinsslugmap, _ = cached.(map[string]types.Coin)
allCoinsSlugMap, _ = cached.(map[string]types.Coin)
ct.debuglog("soft cache hit")
}
// cache miss
if allcoinsslugmap == nil {
if allCoinsSlugMap == nil {
ct.debuglog("cache miss")
ch := make(chan []types.Coin)
err = ct.api.GetAllCoinData(ct.currencyconversion, ch)
err = ct.api.GetAllCoinData(ct.State.currencyConversion, ch)
if err != nil {
return err
}
@ -39,7 +39,7 @@ func (ct *Cointop) updateCoins() error {
go ct.processCoins(coins)
}
} else {
ct.processCoinsMap(allcoinsslugmap)
ct.processCoinsMap(allCoinsSlugMap)
}
return nil
@ -58,14 +58,14 @@ func (ct *Cointop) processCoins(coins []types.Coin) {
updatecoinsmux.Lock()
defer updatecoinsmux.Unlock()
cachekey := ct.cacheKey("allcoinsslugmap")
ct.cache.Set(cachekey, ct.allcoinsslugmap, 10*time.Second)
filecache.Set(cachekey, ct.allcoinsslugmap, 24*time.Hour)
cachekey := ct.cacheKey("allCoinsSlugMap")
ct.cache.Set(cachekey, ct.State.allCoinsSlugMap, 10*time.Second)
filecache.Set(cachekey, ct.State.allCoinsSlugMap, 24*time.Hour)
for _, v := range coins {
k := v.Name
last := ct.allcoinsslugmap[k]
ct.allcoinsslugmap[k] = &Coin{
last := ct.State.allCoinsSlugMap[k]
ct.State.allCoinsSlugMap[k] = &Coin{
ID: v.ID,
Name: v.Name,
Symbol: v.Symbol,
@ -81,23 +81,23 @@ func (ct *Cointop) processCoins(coins []types.Coin) {
LastUpdated: v.LastUpdated,
}
if last != nil {
ct.allcoinsslugmap[k].Favorite = last.Favorite
ct.State.allCoinsSlugMap[k].Favorite = last.Favorite
}
}
if len(ct.allcoins) < len(ct.allcoinsslugmap) {
if len(ct.State.allCoins) < len(ct.State.allCoinsSlugMap) {
list := []*Coin{}
for _, v := range coins {
k := v.Name
coin := ct.allcoinsslugmap[k]
coin := ct.State.allCoinsSlugMap[k]
list = append(list, coin)
}
ct.allcoins = append(ct.allcoins, list...)
ct.State.allCoins = append(ct.State.allCoins, list...)
} else {
// update list in place without changing order
for i := range ct.allcoinsslugmap {
cm := ct.allcoinsslugmap[i]
for k := range ct.allcoins {
c := ct.allcoins[k]
for i := range ct.State.allCoinsSlugMap {
cm := ct.State.allCoinsSlugMap[i]
for k := range ct.State.allCoins {
c := ct.State.allCoins[k]
if c.ID == cm.ID {
// TODO: improve this
c.ID = cm.ID
@ -120,7 +120,7 @@ func (ct *Cointop) processCoins(coins []types.Coin) {
}
time.AfterFunc(10*time.Millisecond, func() {
ct.sort(ct.sortby, ct.sortdesc, ct.coins, true)
ct.sort(ct.State.sortBy, ct.State.sortDesc, ct.State.coins, true)
ct.updateTable()
})
}

@ -13,26 +13,26 @@ import (
)
func (ct *Cointop) updateMarketbar() error {
if ct.marketbarview == nil {
if ct.Views.Marketbar.Backing == nil {
return nil
}
maxX := ct.width()
logo := "cointop"
if ct.colorschemename == "cointop" {
if ct.colorschemeName == "cointop" {
logo = fmt.Sprintf("%s%s%s%s", color.Green(""), color.Cyan(""), color.Green(""), color.Cyan("cointop"))
}
var content string
if ct.portfoliovisible {
if ct.State.portfolioVisible {
total := ct.getPortfolioTotal()
totalstr := humanize.Commaf(total)
if !(ct.currencyconversion == "BTC" || ct.currencyconversion == "ETH" || total < 1) {
if !(ct.State.currencyConversion == "BTC" || ct.State.currencyConversion == "ETH" || total < 1) {
total = math.Round(total*1e2) / 1e2
totalstr = humanize.Commaf2(total)
}
timeframe := ct.selectedchartrange
timeframe := ct.State.selectedChartRange
chartname := ct.selectedCoinName()
var charttitle string
if chartname == "" {
@ -63,7 +63,7 @@ func (ct *Cointop) updateMarketbar() error {
}
chartInfo := ""
if !ct.hideChart {
if !ct.State.hideChart {
chartInfo = fmt.Sprintf(
"[ Chart: %s %s ] ",
charttitle,
@ -93,7 +93,7 @@ func (ct *Cointop) updateMarketbar() error {
}
if market.TotalMarketCapUSD == 0 {
market, err = ct.api.GetGlobalMarketData(ct.currencyconversion)
market, err = ct.api.GetGlobalMarketData(ct.State.currencyConversion)
if err != nil {
filecache.Get(cachekey, &market)
}
@ -104,14 +104,14 @@ func (ct *Cointop) updateMarketbar() error {
}()
}
timeframe := ct.selectedchartrange
timeframe := ct.State.selectedChartRange
chartname := ct.selectedCoinName()
if chartname == "" {
chartname = "Global"
}
chartInfo := ""
if !ct.hideChart {
if !ct.State.hideChart {
chartInfo = fmt.Sprintf(
"[ Chart: %s %s ] ",
ct.colorscheme.MarketBarLabelActive(chartname),
@ -133,12 +133,12 @@ func (ct *Cointop) updateMarketbar() error {
content = ct.colorscheme.Marketbar(content)
ct.update(func() {
if ct.marketbarview == nil {
if ct.Views.Marketbar.Backing == nil {
return
}
ct.marketbarview.Clear()
fmt.Fprintln(ct.marketbarview, content)
ct.Views.Marketbar.Backing.Clear()
fmt.Fprintln(ct.Views.Marketbar.Backing, content)
})
return nil

@ -1,15 +1,15 @@
package cointop
func (ct *Cointop) currentPage() int {
return ct.page + 1
return ct.State.page + 1
}
func (ct *Cointop) currentDisplayPage() int {
return ct.page + 1
return ct.State.page + 1
}
func (ct *Cointop) totalPages() int {
return ct.getListCount() / ct.perpage
return ct.getListCount() / ct.State.perPage
}
func (ct *Cointop) totalPagesDisplay() int {
@ -17,48 +17,30 @@ func (ct *Cointop) totalPagesDisplay() int {
}
func (ct *Cointop) totalPerPage() int {
return ct.perpage
return ct.State.perPage
}
func (ct *Cointop) setPage(page int) int {
if (page*ct.perpage) < ct.getListCount() && page >= 0 {
ct.page = page
if (page*ct.State.perPage) < ct.getListCount() && page >= 0 {
ct.State.page = page
}
return ct.page
}
func (ct *Cointop) highlightRow(idx int) error {
ct.tableview.SetOrigin(0, 0)
ct.tableview.SetCursor(0, 0)
ox, _ := ct.tableview.Origin()
cx, _ := ct.tableview.Cursor()
_, sy := ct.tableview.Size()
perpage := ct.totalPerPage()
p := idx % perpage
oy := (p / sy) * sy
cy := p % sy
if oy > 0 {
ct.tableview.SetOrigin(ox, oy)
}
ct.tableview.SetCursor(cx, cy)
return nil
return ct.State.page
}
func (ct *Cointop) cursorDown() error {
if ct.tableview == nil {
if ct.Views.Table.Backing == nil {
return nil
}
_, y := ct.tableview.Origin()
cx, cy := ct.tableview.Cursor()
numRows := len(ct.coins) - 1
_, y := ct.Views.Table.Backing.Origin()
cx, cy := ct.Views.Table.Backing.Cursor()
numRows := len(ct.State.coins) - 1
if (cy + y + 1) > numRows {
return nil
}
if err := ct.tableview.SetCursor(cx, cy+1); err != nil {
ox, oy := ct.tableview.Origin()
if err := ct.Views.Table.Backing.SetCursor(cx, cy+1); err != nil {
ox, oy := ct.Views.Table.Backing.Origin()
// set origin scrolls
if err := ct.tableview.SetOrigin(ox, oy+1); err != nil {
if err := ct.Views.Table.Backing.SetOrigin(ox, oy+1); err != nil {
return err
}
}
@ -67,14 +49,14 @@ func (ct *Cointop) cursorDown() error {
}
func (ct *Cointop) cursorUp() error {
if ct.tableview == nil {
if ct.Views.Table.Backing == nil {
return nil
}
ox, oy := ct.tableview.Origin()
cx, cy := ct.tableview.Cursor()
if err := ct.tableview.SetCursor(cx, cy-1); err != nil && oy > 0 {
ox, oy := ct.Views.Table.Backing.Origin()
cx, cy := ct.Views.Table.Backing.Cursor()
if err := ct.Views.Table.Backing.SetCursor(cx, cy-1); err != nil && oy > 0 {
// set origin scrolls
if err := ct.tableview.SetOrigin(ox, oy-1); err != nil {
if err := ct.Views.Table.Backing.SetOrigin(ox, oy-1); err != nil {
return err
}
}
@ -83,14 +65,14 @@ func (ct *Cointop) cursorUp() error {
}
func (ct *Cointop) pageDown() error {
if ct.tableview == nil {
if ct.Views.Table.Backing == nil {
return nil
}
ox, oy := ct.tableview.Origin() // this is prev origin position
cx, _ := ct.tableview.Cursor() // relative cursor position
_, sy := ct.tableview.Size() // rows in visible view
ox, oy := ct.Views.Table.Backing.Origin() // this is prev origin position
cx, _ := ct.Views.Table.Backing.Cursor() // relative cursor position
_, sy := ct.Views.Table.Backing.Size() // rows in visible view
k := oy + sy
l := len(ct.coins)
l := len(ct.State.coins)
// end of table
if (oy + sy + sy) > l {
k = l - sy
@ -101,12 +83,12 @@ func (ct *Cointop) pageDown() error {
sy = l
}
if err := ct.tableview.SetOrigin(ox, k); err != nil {
if err := ct.Views.Table.Backing.SetOrigin(ox, k); err != nil {
return err
}
// move cursor to last line if can't scroll further
if k == oy {
if err := ct.tableview.SetCursor(cx, sy-1); err != nil {
if err := ct.Views.Table.Backing.SetCursor(cx, sy-1); err != nil {
return err
}
}
@ -115,22 +97,22 @@ func (ct *Cointop) pageDown() error {
}
func (ct *Cointop) pageUp() error {
if ct.tableview == nil {
if ct.Views.Table.Backing == nil {
return nil
}
ox, oy := ct.tableview.Origin()
cx, _ := ct.tableview.Cursor() // relative cursor position
_, sy := ct.tableview.Size() // rows in visible view
ox, oy := ct.Views.Table.Backing.Origin()
cx, _ := ct.Views.Table.Backing.Cursor() // relative cursor position
_, sy := ct.Views.Table.Backing.Size() // rows in visible view
k := oy - sy
if k < 0 {
k = 0
}
if err := ct.tableview.SetOrigin(ox, k); err != nil {
if err := ct.Views.Table.Backing.SetOrigin(ox, k); err != nil {
return err
}
// move cursor to first line if can't scroll further
if k == oy {
if err := ct.tableview.SetCursor(cx, 0); err != nil {
if err := ct.Views.Table.Backing.SetCursor(cx, 0); err != nil {
return err
}
}
@ -139,15 +121,15 @@ func (ct *Cointop) pageUp() error {
}
func (ct *Cointop) navigateFirstLine() error {
if ct.tableview == nil {
if ct.Views.Table.Backing == nil {
return nil
}
ox, _ := ct.tableview.Origin()
cx, _ := ct.tableview.Cursor()
if err := ct.tableview.SetOrigin(ox, 0); err != nil {
ox, _ := ct.Views.Table.Backing.Origin()
cx, _ := ct.Views.Table.Backing.Cursor()
if err := ct.Views.Table.Backing.SetOrigin(ox, 0); err != nil {
return err
}
if err := ct.tableview.SetCursor(cx, 0); err != nil {
if err := ct.Views.Table.Backing.SetCursor(cx, 0); err != nil {
return err
}
ct.rowChanged()
@ -155,18 +137,18 @@ func (ct *Cointop) navigateFirstLine() error {
}
func (ct *Cointop) navigateLastLine() error {
if ct.tableview == nil {
if ct.Views.Table.Backing == nil {
return nil
}
ox, _ := ct.tableview.Origin()
cx, _ := ct.tableview.Cursor()
_, sy := ct.tableview.Size()
l := len(ct.coins)
ox, _ := ct.Views.Table.Backing.Origin()
cx, _ := ct.Views.Table.Backing.Cursor()
_, sy := ct.Views.Table.Backing.Size()
l := len(ct.State.coins)
k := l - sy
if err := ct.tableview.SetOrigin(ox, k); err != nil {
if err := ct.Views.Table.Backing.SetOrigin(ox, k); err != nil {
return err
}
if err := ct.tableview.SetCursor(cx, sy-1); err != nil {
if err := ct.Views.Table.Backing.SetCursor(cx, sy-1); err != nil {
return err
}
ct.rowChanged()
@ -174,11 +156,11 @@ func (ct *Cointop) navigateLastLine() error {
}
func (ct *Cointop) navigatePageFirstLine() error {
if ct.tableview == nil {
if ct.Views.Table.Backing == nil {
return nil
}
cx, _ := ct.tableview.Cursor()
if err := ct.tableview.SetCursor(cx, 0); err != nil {
cx, _ := ct.Views.Table.Backing.Cursor()
if err := ct.Views.Table.Backing.SetCursor(cx, 0); err != nil {
return err
}
ct.rowChanged()
@ -186,12 +168,12 @@ func (ct *Cointop) navigatePageFirstLine() error {
}
func (ct *Cointop) navigatePageMiddleLine() error {
if ct.tableview == nil {
if ct.Views.Table.Backing == nil {
return nil
}
cx, _ := ct.tableview.Cursor()
_, sy := ct.tableview.Size()
if err := ct.tableview.SetCursor(cx, (sy/2)-1); err != nil {
cx, _ := ct.Views.Table.Backing.Cursor()
_, sy := ct.Views.Table.Backing.Size()
if err := ct.Views.Table.Backing.SetCursor(cx, (sy/2)-1); err != nil {
return err
}
ct.rowChanged()
@ -199,12 +181,12 @@ func (ct *Cointop) navigatePageMiddleLine() error {
}
func (ct *Cointop) navigatePageLastLine() error {
if ct.tableview == nil {
if ct.Views.Table.Backing == nil {
return nil
}
cx, _ := ct.tableview.Cursor()
_, sy := ct.tableview.Size()
if err := ct.tableview.SetCursor(cx, sy-1); err != nil {
cx, _ := ct.Views.Table.Backing.Cursor()
_, sy := ct.Views.Table.Backing.Size()
if err := ct.Views.Table.Backing.SetCursor(cx, sy-1); err != nil {
return err
}
ct.rowChanged()
@ -215,32 +197,32 @@ func (ct *Cointop) prevPage() error {
if ct.isFirstPage() {
return nil
}
ct.setPage(ct.page - 1)
ct.setPage(ct.State.page - 1)
ct.updateTable()
ct.rowChanged()
return nil
}
func (ct *Cointop) nextPage() error {
ct.setPage(ct.page + 1)
ct.setPage(ct.State.page + 1)
ct.updateTable()
ct.rowChanged()
return nil
}
func (ct *Cointop) firstPage() error {
ct.page = 0
ct.State.page = 0
ct.updateTable()
ct.rowChanged()
return nil
}
func (ct *Cointop) isFirstPage() bool {
return ct.page == 0
return ct.State.page == 0
}
func (ct *Cointop) lastPage() error {
ct.page = ct.getListCount() / ct.perpage
ct.State.page = ct.getListCount() / ct.State.perPage
ct.updateTable()
ct.rowChanged()
return nil
@ -255,3 +237,21 @@ func (ct *Cointop) goToGlobalIndex(idx int) error {
ct.updateTable()
return nil
}
func (ct *Cointop) highlightRow(idx int) error {
ct.Views.Table.Backing.SetOrigin(0, 0)
ct.Views.Table.Backing.SetCursor(0, 0)
ox, _ := ct.Views.Table.Backing.Origin()
cx, _ := ct.Views.Table.Backing.Cursor()
_, sy := ct.Views.Table.Backing.Size()
perpage := ct.totalPerPage()
p := idx % perpage
oy := (p / sy) * sy
cy := p % sy
if oy > 0 {
ct.Views.Table.Backing.SetOrigin(ox, oy)
}
ct.Views.Table.Backing.SetCursor(cx, cy)
return nil
}

@ -11,22 +11,22 @@ import (
)
func (ct *Cointop) togglePortfolio() error {
ct.filterByFavorites = false
ct.portfoliovisible = !ct.portfoliovisible
ct.State.filterByFavorites = false
ct.State.portfolioVisible = !ct.State.portfolioVisible
go ct.updateTable()
return nil
}
func (ct *Cointop) toggleShowPortfolio() error {
ct.filterByFavorites = false
ct.portfoliovisible = true
ct.State.filterByFavorites = false
ct.State.portfolioVisible = true
go ct.updateTable()
return nil
}
func (ct *Cointop) togglePortfolioUpdateMenu() error {
ct.portfolioupdatemenuvisible = !ct.portfolioupdatemenuvisible
if ct.portfolioupdatemenuvisible {
ct.State.portfolioUpdateMenuVisible = !ct.State.portfolioUpdateMenuVisible
if ct.State.portfolioUpdateMenuVisible {
return ct.showPortfolioUpdateMenu()
}
return ct.hidePortfolioUpdateMenu()
@ -34,7 +34,7 @@ func (ct *Cointop) togglePortfolioUpdateMenu() error {
func (ct *Cointop) updatePortfolioUpdateMenu() {
coin := ct.highlightedRowCoin()
exists := ct.portfolioEntryExists(coin)
exists := ct.PortfolioEntryExists(coin)
value := strconv.FormatFloat(coin.Holdings, 'f', -1, 64)
var mode string
var current string
@ -47,16 +47,16 @@ func (ct *Cointop) updatePortfolioUpdateMenu() {
mode = "Add"
submitText = "Add"
}
header := ct.colorscheme.MenuHeader(fmt.Sprintf(" %s Portfolio Entry %s\n\n", mode, pad.Left("[q] close ", ct.maxtablewidth-26, " ")))
header := ct.colorscheme.MenuHeader(fmt.Sprintf(" %s Portfolio Entry %s\n\n", mode, pad.Left("[q] close ", ct.maxTableWidth-26, " ")))
label := fmt.Sprintf(" Enter holdings for %s %s", ct.colorscheme.MenuLabel(coin.Name), current)
content := fmt.Sprintf("%s\n%s\n\n%s%s\n\n\n [Enter] %s [ESC] Cancel", header, label, strings.Repeat(" ", 29), coin.Symbol, submitText)
ct.update(func() {
ct.portfolioupdatemenuview.Clear()
ct.portfolioupdatemenuview.Frame = true
fmt.Fprintln(ct.portfolioupdatemenuview, content)
fmt.Fprintln(ct.inputview, value)
ct.inputview.SetCursor(len(value), 0)
ct.Views.PortfolioUpdateMenu.Backing.Clear()
ct.Views.PortfolioUpdateMenu.Backing.Frame = true
fmt.Fprintln(ct.Views.PortfolioUpdateMenu.Backing, content)
fmt.Fprintln(ct.Views.Input.Backing, value)
ct.Views.Input.Backing.SetCursor(len(value), 0)
})
}
@ -67,28 +67,28 @@ func (ct *Cointop) showPortfolioUpdateMenu() error {
return nil
}
ct.portfolioupdatemenuvisible = true
ct.State.portfolioUpdateMenuVisible = true
ct.updatePortfolioUpdateMenu()
ct.setActiveView(ct.portfolioupdatemenuviewname)
ct.setActiveView(ct.Views.PortfolioUpdateMenu.Name)
return nil
}
func (ct *Cointop) hidePortfolioUpdateMenu() error {
ct.portfolioupdatemenuvisible = false
ct.setViewOnBottom(ct.portfolioupdatemenuviewname)
ct.setViewOnBottom(ct.inputviewname)
ct.setActiveView(ct.tableviewname)
ct.State.portfolioUpdateMenuVisible = false
ct.setViewOnBottom(ct.Views.PortfolioUpdateMenu.Name)
ct.setViewOnBottom(ct.Views.Input.Name)
ct.setActiveView(ct.Views.Table.Name)
ct.update(func() {
if ct.portfolioupdatemenuview == nil {
if ct.Views.PortfolioUpdateMenu.Backing == nil {
return
}
ct.portfolioupdatemenuview.Clear()
ct.portfolioupdatemenuview.Frame = false
fmt.Fprintln(ct.portfolioupdatemenuview, "")
ct.Views.PortfolioUpdateMenu.Backing.Clear()
ct.Views.PortfolioUpdateMenu.Backing.Frame = false
fmt.Fprintln(ct.Views.PortfolioUpdateMenu.Backing, "")
ct.inputview.Clear()
fmt.Fprintln(ct.inputview, "")
ct.Views.Input.Backing.Clear()
fmt.Fprintln(ct.Views.Input.Backing, "")
})
return nil
}
@ -99,7 +99,7 @@ func (ct *Cointop) setPortfolioHoldings() error {
coin := ct.highlightedRowCoin()
b := make([]byte, 100)
n, err := ct.inputview.Read(b)
n, err := ct.Views.Input.Backing.Read(b)
if n == 0 {
return nil
}
@ -129,20 +129,20 @@ func (ct *Cointop) setPortfolioHoldings() error {
return nil
}
func (ct *Cointop) portfolioEntry(c *Coin) (*portfolioEntry, bool) {
func (ct *Cointop) PortfolioEntry(c *Coin) (*PortfolioEntry, bool) {
if c == nil {
return &portfolioEntry{}, true
return &PortfolioEntry{}, true
}
var p *portfolioEntry
var p *PortfolioEntry
var isNew bool
var ok bool
key := strings.ToLower(c.Name)
if p, ok = ct.portfolio.Entries[key]; !ok {
if p, ok = ct.State.portfolio.Entries[key]; !ok {
// NOTE: if not found then try the symbol
key := strings.ToLower(c.Symbol)
if p, ok = ct.portfolio.Entries[key]; !ok {
p = &portfolioEntry{
if p, ok = ct.State.portfolio.Entries[key]; !ok {
p = &PortfolioEntry{
Coin: c.Name,
Holdings: 0,
}
@ -154,11 +154,11 @@ func (ct *Cointop) portfolioEntry(c *Coin) (*portfolioEntry, bool) {
}
func (ct *Cointop) setPortfolioEntry(coin string, holdings float64) {
c, _ := ct.allcoinsslugmap[strings.ToLower(coin)]
p, isNew := ct.portfolioEntry(c)
c, _ := ct.State.allCoinsSlugMap[strings.ToLower(coin)]
p, isNew := ct.PortfolioEntry(c)
if isNew {
key := strings.ToLower(coin)
ct.portfolio.Entries[key] = &portfolioEntry{
ct.State.portfolio.Entries[key] = &PortfolioEntry{
Coin: coin,
Holdings: holdings,
}
@ -168,33 +168,33 @@ func (ct *Cointop) setPortfolioEntry(coin string, holdings float64) {
}
func (ct *Cointop) removePortfolioEntry(coin string) {
delete(ct.portfolio.Entries, strings.ToLower(coin))
delete(ct.State.portfolio.Entries, strings.ToLower(coin))
}
func (ct *Cointop) portfolioEntryExists(c *Coin) bool {
_, isNew := ct.portfolioEntry(c)
func (ct *Cointop) PortfolioEntryExists(c *Coin) bool {
_, isNew := ct.PortfolioEntry(c)
return !isNew
}
func (ct *Cointop) portfolioEntriesCount() int {
return len(ct.portfolio.Entries)
return len(ct.State.portfolio.Entries)
}
func (ct *Cointop) getPortfolioSlice() []*Coin {
sliced := []*Coin{}
for i := range ct.allcoins {
for i := range ct.State.allCoins {
if ct.portfolioEntriesCount() == 0 {
break
}
coin := ct.allcoins[i]
p, isNew := ct.portfolioEntry(coin)
coin := ct.State.allCoins[i]
p, isNew := ct.PortfolioEntry(coin)
if isNew {
continue
}
coin.Holdings = p.Holdings
balance := coin.Price * p.Holdings
balancestr := fmt.Sprintf("%.2f", balance)
if ct.currencyconversion == "ETH" || ct.currencyconversion == "BTC" {
if ct.State.currencyConversion == "ETH" || ct.State.currencyConversion == "BTC" {
balancestr = fmt.Sprintf("%.5f", balance)
}
balance, _ = strconv.ParseFloat(balancestr, 64)

@ -11,15 +11,15 @@ func (ct *Cointop) quit() error {
}
func (ct *Cointop) quitView() error {
if ct.portfoliovisible {
ct.portfoliovisible = false
if ct.State.portfolioVisible {
ct.State.portfolioVisible = false
return ct.updateTable()
}
if ct.filterByFavorites {
ct.filterByFavorites = false
if ct.State.filterByFavorites {
ct.State.filterByFavorites = false
return ct.updateTable()
}
if ct.activeViewName() == ct.tableviewname {
if ct.activeViewName() == ct.Views.Table.Name {
return ct.quit()
}

@ -8,16 +8,16 @@ import (
func (ct *Cointop) refresh() error {
go func() {
<-ct.limiter
ct.forcerefresh <- true
ct.forceRefresh <- true
}()
return nil
}
func (ct *Cointop) refreshAll() error {
ct.refreshmux.Lock()
defer ct.refreshmux.Unlock()
ct.refreshMux.Lock()
defer ct.refreshMux.Unlock()
ct.setRefreshStatus()
ct.cache.Delete("allcoinsslugmap")
ct.cache.Delete("allCoinsSlugMap")
ct.cache.Delete("market")
go func() {
ct.updateCoins()
@ -46,3 +46,16 @@ func (ct *Cointop) loadingTicks(s string, t int) {
}
}
}
func (ct *Cointop) intervalFetchData() {
go func() {
for {
select {
case <-ct.forceRefresh:
ct.refreshAll()
case <-ct.refreshTicker.C:
ct.refreshAll()
}
}
}()
}

@ -8,27 +8,27 @@ import (
)
func (ct *Cointop) openSearch() error {
ct.searchfieldvisible = true
ct.setActiveView(ct.searchfieldviewname)
ct.State.searchFieldVisible = true
ct.setActiveView(ct.Views.SearchField.Name)
return nil
}
func (ct *Cointop) cancelSearch() error {
ct.searchfieldvisible = false
ct.setActiveView(ct.tableviewname)
ct.State.searchFieldVisible = false
ct.setActiveView(ct.Views.Table.Name)
return nil
}
func (ct *Cointop) doSearch() error {
ct.searchfield.Rewind()
ct.Views.SearchField.Backing.Rewind()
b := make([]byte, 100)
n, err := ct.searchfield.Read(b)
n, err := ct.Views.SearchField.Backing.Read(b)
// TODO: do this a better way (SoC)
ct.filterByFavorites = false
ct.portfoliovisible = false
ct.State.filterByFavorites = false
ct.State.portfolioVisible = false
defer ct.setActiveView(ct.tableviewname)
defer ct.setActiveView(ct.Views.Table.Name)
if err != nil {
return nil
}
@ -51,8 +51,8 @@ func (ct *Cointop) search(q string) error {
min := -1
var hasprefixidx []int
var hasprefixdist []int
for i := range ct.allcoins {
coin := ct.allcoins[i]
for i := range ct.State.allCoins {
coin := ct.State.allCoins[i]
name := strings.ToLower(coin.Name)
symbol := strings.ToLower(coin.Symbol)
// if query matches symbol, return immediately

@ -0,0 +1,19 @@
package cointop
func (ct *Cointop) selectedCoinName() string {
coin := ct.State.selectedCoin
if coin != nil {
return coin.Name
}
return ""
}
func (ct *Cointop) selectedCoinSymbol() string {
coin := ct.State.selectedCoin
if coin != nil {
return coin.Symbol
}
return ""
}

@ -9,7 +9,7 @@ import (
var sortlock sync.Mutex
func (ct *Cointop) sort(sortby string, desc bool, list []*Coin, renderHeaders bool) {
func (ct *Cointop) sort(sortBy string, desc bool, list []*Coin, renderHeaders bool) {
sortlock.Lock()
defer sortlock.Unlock()
if list == nil {
@ -18,10 +18,10 @@ func (ct *Cointop) sort(sortby string, desc bool, list []*Coin, renderHeaders bo
if len(list) < 2 {
return
}
ct.sortby = sortby
ct.sortdesc = desc
ct.State.sortBy = sortBy
ct.State.sortDesc = desc
sort.Slice(list[:], func(i, j int) bool {
if ct.sortdesc {
if ct.State.sortDesc {
i, j = j, i
}
a := list[i]
@ -32,7 +32,7 @@ func (ct *Cointop) sort(sortby string, desc bool, list []*Coin, renderHeaders bo
if b == nil {
return false
}
switch sortby {
switch sortBy {
case "rank":
return a.Rank < b.Rank
case "name":
@ -71,66 +71,66 @@ func (ct *Cointop) sort(sortby string, desc bool, list []*Coin, renderHeaders bo
}
}
func (ct *Cointop) sortToggle(sortby string, desc bool) error {
if ct.sortby == sortby {
desc = !ct.sortdesc
}
ct.sort(sortby, desc, ct.coins, true)
ct.updateTable()
return nil
}
func (ct *Cointop) sortfn(sortby string, desc bool) func(g *gocui.Gui, v *gocui.View) error {
return func(g *gocui.Gui, v *gocui.View) error {
return ct.sortToggle(sortby, desc)
}
}
func (ct *Cointop) getSortColIndex() int {
for i, col := range ct.tablecolumnorder {
if ct.sortby == col {
return i
}
}
return 0
}
func (ct *Cointop) sortAsc() error {
ct.sortdesc = false
ct.State.sortDesc = false
ct.updateTable()
return nil
}
func (ct *Cointop) sortDesc() error {
ct.sortdesc = true
ct.State.sortDesc = true
ct.updateTable()
return nil
}
func (ct *Cointop) sortPrevCol() error {
nextsortby := ct.tablecolumnorder[0]
nextsortBy := ct.tableColumnOrder[0]
i := ct.getSortColIndex()
k := i - 1
if k < 0 {
k = 0
}
nextsortby = ct.tablecolumnorder[k]
ct.sort(nextsortby, ct.sortdesc, ct.coins, true)
nextsortBy = ct.tableColumnOrder[k]
ct.sort(nextsortBy, ct.State.sortDesc, ct.State.coins, true)
ct.updateTable()
return nil
}
func (ct *Cointop) sortNextCol() error {
nextsortby := ct.tablecolumnorder[0]
l := len(ct.tablecolumnorder)
nextsortBy := ct.tableColumnOrder[0]
l := len(ct.tableColumnOrder)
i := ct.getSortColIndex()
k := i + 1
if k > l-1 {
k = l - 1
}
nextsortby = ct.tablecolumnorder[k]
ct.sort(nextsortby, ct.sortdesc, ct.coins, true)
nextsortBy = ct.tableColumnOrder[k]
ct.sort(nextsortBy, ct.State.sortDesc, ct.State.coins, true)
ct.updateTable()
return nil
}
func (ct *Cointop) sortToggle(sortBy string, desc bool) error {
if ct.State.sortBy == sortBy {
desc = !ct.State.sortDesc
}
ct.sort(sortBy, desc, ct.State.coins, true)
ct.updateTable()
return nil
}
func (ct *Cointop) sortfn(sortBy string, desc bool) func(g *gocui.Gui, v *gocui.View) error {
return func(g *gocui.Gui, v *gocui.View) error {
return ct.sortToggle(sortBy, desc)
}
}
func (ct *Cointop) getSortColIndex() int {
for i, col := range ct.tableColumnOrder {
if ct.State.sortBy == col {
return i
}
}
return 0
}

@ -8,7 +8,7 @@ import (
)
func (ct *Cointop) updateStatusbar(s string) error {
if ct.statusbarview == nil {
if ct.Views.Statusbar.Backing == nil {
return nil
}
@ -17,33 +17,33 @@ func (ct *Cointop) updateStatusbar(s string) error {
var quitText string
var favoritesText string
var portfolioText string
if ct.portfoliovisible || ct.filterByFavorites {
if ct.State.portfolioVisible || ct.State.filterByFavorites {
quitText = "Return"
} else {
quitText = "Quit"
}
if ct.portfoliovisible {
if ct.State.portfolioVisible {
portfolioText = "[E]Edit"
} else {
portfolioText = "[P]Portfolio"
}
if ct.filterByFavorites {
if ct.State.filterByFavorites {
favoritesText = "[Space]Unfavorite"
} else {
favoritesText = "[F]Favorites"
}
ct.update(func() {
if ct.statusbarview == nil {
if ct.Views.Statusbar.Backing == nil {
return
}
ct.statusbarview.Clear()
ct.Views.Statusbar.Backing.Clear()
base := fmt.Sprintf("%s%s %sHelp %sChart %sRange %sSearch %sConvert %s %s %sSave", "[Q]", quitText, "[?]", "[Enter]", "[[ ]]", "[/]", "[C]", favoritesText, portfolioText, "[CTRL-S]")
str := pad.Right(fmt.Sprintf("%v %sPage %v/%v %s", base, "[← →]", currpage, totalpages, s), ct.maxtablewidth, " ")
str := pad.Right(fmt.Sprintf("%v %sPage %v/%v %s", base, "[← →]", currpage, totalpages, s), ct.maxTableWidth, " ")
v := fmt.Sprintf("v%s", ct.version())
str = str[:len(str)-len(v)+2] + v
fmt.Fprintln(ct.statusbarview, str)
fmt.Fprintln(ct.Views.Statusbar.Backing, str)
})
return nil

@ -18,7 +18,7 @@ func (ct *Cointop) refreshTable() error {
ct.table = table.New().SetWidth(maxX)
ct.table.HideColumHeaders = true
if ct.portfoliovisible {
if ct.State.portfolioVisible {
ct.table.AddCol("")
ct.table.AddCol("")
ct.table.AddCol("")
@ -31,7 +31,7 @@ func (ct *Cointop) refreshTable() error {
total := ct.getPortfolioTotal()
for _, coin := range ct.coins {
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
@ -80,7 +80,7 @@ func (ct *Cointop) refreshTable() error {
ct.table.AddCol("")
ct.table.AddCol("")
ct.table.AddCol("")
for _, coin := range ct.coins {
for _, coin := range ct.State.coins {
if coin == nil {
continue
}
@ -125,7 +125,7 @@ func (ct *Cointop) refreshTable() error {
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.filterByFavorites {
if ct.State.filterByFavorites {
symbolpadding = 6
}
ct.table.AddRow(
@ -148,17 +148,17 @@ func (ct *Cointop) refreshTable() error {
// highlight last row if current row is out of bounds (can happen when switching views)
currentrow := ct.highlightedRowIndex()
if len(ct.coins) > currentrow {
if len(ct.State.coins) > currentrow {
ct.highlightRow(currentrow)
}
ct.update(func() {
if ct.tableview == nil {
if ct.Views.Table.Backing == nil {
return
}
ct.tableview.Clear()
ct.table.Format().Fprint(ct.tableview)
ct.Views.Table.Backing.Clear()
ct.table.Format().Fprint(ct.Views.Table.Backing)
go ct.rowChanged()
go ct.updateHeaders()
go ct.updateMarketbar()
@ -171,36 +171,36 @@ func (ct *Cointop) refreshTable() error {
func (ct *Cointop) updateTable() error {
sliced := []*Coin{}
for i := range ct.allcoinsslugmap {
v := ct.allcoinsslugmap[i]
if ct.favorites[v.Name] {
for i := range ct.State.allCoinsSlugMap {
v := ct.State.allCoinsSlugMap[i]
if ct.State.favorites[v.Name] {
v.Favorite = true
}
}
if ct.filterByFavorites {
for i := range ct.allcoins {
coin := ct.allcoins[i]
if ct.State.filterByFavorites {
for i := range ct.State.allCoins {
coin := ct.State.allCoins[i]
if coin.Favorite {
sliced = append(sliced, coin)
}
}
ct.coins = sliced
ct.State.coins = sliced
go ct.refreshTable()
return nil
}
if ct.portfoliovisible {
if ct.State.portfolioVisible {
sliced = ct.getPortfolioSlice()
ct.coins = sliced
ct.State.coins = sliced
go ct.refreshTable()
return nil
}
start := ct.page * ct.perpage
end := start + ct.perpage
allcoins := ct.allCoins()
size := len(allcoins)
start := ct.State.page * ct.State.perPage
end := start + ct.State.perPage
allCoins := ct.allCoins()
size := len(allCoins)
if start < 0 {
start = 0
}
@ -221,34 +221,34 @@ func (ct *Cointop) updateTable() error {
return nil
}
if end > 0 {
sliced = allcoins[start:end]
sliced = allCoins[start:end]
}
ct.coins = sliced
ct.State.coins = sliced
ct.sort(ct.sortby, ct.sortdesc, ct.coins, true)
ct.sort(ct.State.sortBy, ct.State.sortDesc, ct.State.coins, true)
go ct.refreshTable()
return nil
}
func (ct *Cointop) highlightedRowIndex() int {
_, y := ct.tableview.Origin()
_, cy := ct.tableview.Cursor()
_, y := ct.Views.Table.Backing.Origin()
_, cy := ct.Views.Table.Backing.Cursor()
idx := y + cy
if idx < 0 {
idx = 0
}
if idx >= len(ct.coins) {
idx = len(ct.coins) - 1
if idx >= len(ct.State.coins) {
idx = len(ct.State.coins) - 1
}
return idx
}
func (ct *Cointop) highlightedRowCoin() *Coin {
idx := ct.highlightedRowIndex()
if len(ct.coins) == 0 {
if len(ct.State.coins) == 0 {
return nil
}
return ct.coins[idx]
return ct.State.coins[idx]
}
func (ct *Cointop) rowLink() string {
@ -282,46 +282,9 @@ func (ct *Cointop) rowLinkShort() string {
return ""
}
func (ct *Cointop) allCoins() []*Coin {
if ct.filterByFavorites {
var list []*Coin
for i := range ct.allcoins {
coin := ct.allcoins[i]
if coin.Favorite {
list = append(list, coin)
}
}
return list
}
if ct.portfoliovisible {
var list []*Coin
for i := range ct.allcoins {
coin := ct.allcoins[i]
if ct.portfolioEntryExists(coin) {
list = append(list, coin)
}
}
return list
}
return ct.allcoins
}
func (ct *Cointop) coinBySymbol(symbol string) *Coin {
for i := range ct.allcoins {
coin := ct.allcoins[i]
if coin.Symbol == symbol {
return coin
}
}
return nil
}
func (ct *Cointop) toggleTableFullscreen() error {
ct.onlyTable = !ct.onlyTable
if ct.onlyTable {
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
@ -329,17 +292,17 @@ func (ct *Cointop) toggleTableFullscreen() error {
onlyTable, _ := ct.cache.Get("onlyTable")
if onlyTable.(bool) {
ct.hideMarketbar = false
ct.hideChart = false
ct.hideStatusbar = false
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.hideMarketbar = hideMarketbar.(bool)
ct.State.hideMarketbar = hideMarketbar.(bool)
hideChart, _ := ct.cache.Get("hideChart")
ct.hideChart = hideChart.(bool)
ct.State.hideChart = hideChart.(bool)
hideStatusbar, _ := ct.cache.Get("hideStatusbar")
ct.hideStatusbar = hideStatusbar.(bool)
ct.State.hideStatusbar = hideStatusbar.(bool)
}
}

@ -37,9 +37,9 @@ func (ct *Cointop) updateHeaders() {
for k := range cm {
cm[k].arrow = " "
if ct.sortby == k {
if ct.State.sortBy == k {
cm[k].colorfn = ct.colorscheme.TableHeaderColumnActiveSprintf()
if ct.sortdesc {
if ct.State.sortDesc {
cm[k].arrow = "▼"
} else {
cm[k].arrow = "▲"
@ -47,7 +47,7 @@ func (ct *Cointop) updateHeaders() {
}
}
if ct.portfoliovisible {
if ct.State.portfolioVisible {
cols = []string{"rank", "name", "symbol", "price",
"holdings", "balance", "24hchange", "percentholdings", "lastupdated"}
} else {
@ -78,11 +78,11 @@ func (ct *Cointop) updateHeaders() {
}
ct.update(func() {
if ct.headersview == nil {
if ct.Views.Header.Backing == nil {
return
}
ct.headersview.Clear()
fmt.Fprintln(ct.headersview, strings.Join(headers, ""))
ct.Views.Header.Backing.Clear()
fmt.Fprintln(ct.Views.Header.Backing, strings.Join(headers, ""))
})
}

@ -1,24 +0,0 @@
package cointop
// Coin is the row structure
type Coin struct {
ID string
Name string
Slug string
Symbol string
Rank int
Price float64
Volume24H float64
MarketCap float64
AvailableSupply float64
TotalSupply float64
PercentChange1H float64
PercentChange24H float64
PercentChange7D float64
LastUpdated string
// for favorites
Favorite bool
// for portfolio
Holdings float64
Balance float64
}

@ -0,0 +1,39 @@
package cointop
import (
"fmt"
"github.com/jroimartin/gocui"
)
// View is a cointop view
type View struct {
Backing *gocui.View
Name string
}
func (ct *Cointop) setActiveView(v string) error {
ct.g.SetViewOnTop(v)
ct.g.SetCurrentView(v)
if v == ct.Views.SearchField.Name {
ct.Views.SearchField.Backing.Clear()
ct.Views.SearchField.Backing.SetCursor(1, 0)
fmt.Fprintf(ct.Views.SearchField.Backing, "%s", "/")
} else if v == ct.Views.Table.Name {
ct.g.SetViewOnTop(ct.Views.Statusbar.Name)
}
if v == ct.Views.PortfolioUpdateMenu.Name {
ct.g.SetViewOnTop(ct.Views.Input.Name)
ct.g.SetCurrentView(ct.Views.Input.Name)
}
return nil
}
func (ct *Cointop) activeViewName() string {
return ct.g.CurrentView().Name()
}
func (ct *Cointop) setViewOnBottom(v string) error {
_, err := ct.g.SetViewOnBottom(v)
return err
}
Loading…
Cancel
Save